# hblg **Repository Path**: hanweiwn/hblg ## Basic Information - **Project Name**: hblg - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-31 - **Last Updated**: 2026-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 东软环保公众监督项目 可以成立小组,但是必须独立完成都只能做这个项目。 ![image-20260831084832444](images/image-20260831084832444.png) 实训: 三周 韩炜。 1:讲需求,设计方面的知识。 2:Java+Vue+Mysql的开发知识 3:AI在项目中的应用。 ### 项目需求 设计+开发+测试 UML:统一建模语言。 #### 用例图: 建模现实世界的简化,可以由外部系统内部进行一个用例的调用。 ``` https://www.processon.com/ ``` ``` 包含include: 一个用例中重用另外一个用例的步骤 扩展extends: 允许对已有的用例增加步骤创建新的用例。 泛化:一个用例集成了另外一个用例。 ``` 最基础的用例信息。 ![image-20260831092544280](images/image-20260831092544280.png) #### 类图: ![image-20260831095733799](images/image-20260831095733799.png) #### 时序图: 按照时间顺序模拟流程控制,演示了时间序列在对象直接的交互。 ![image-20260831100712913](images/image-20260831100712913.png) ![image-20260831101654149](images/image-20260831101654149.png) #### 状态图: image-20260831101821855 此处你们写代码的时候,可以直接在数据表中用一个字段来描述状态。这个是最简单的方式。 如果有能力的同学,可以上Active() 就是做工作流的。 ### 数据库 常见的SQL的问题: 你们设计数据表的时候不要给我用外键。 初级程序员:SQL的查询。推荐你们好好去看一下SQL优化。 钱,不要用float,double。包含Java。原因是以二进制存储的,有一定的精度丢失。 ```mssql #货币一定要用decimal #创建数据库的表 create table a( id int auto_increment primary key, a float, #那么此处改成double,仍然会存在精度丢失 b decimal(9,2) #长度为9,保留两位小数 货币类型 )engine innodb; #插入一些数据 insert into a values (null,1234567.23,1234567.23); #查询SQL语句 select * from a; ``` 开发中如果你用了主键,主键采用了雪花算法等,或者Long等,长度比较大的值的时候,你一定要用String ``` 安全范围外会精度丢失‌:JavaScript 的 Number 遵循 IEEE 754 双精度浮点标准,仅能‌精确表示‌ ±(2⁵³ − 1) 即 ‌±9,007,199,254,740,991‌(约 16 位十进制整数); Java long 最大值为 2⁶³−1 ≈ 9.22×10¹⁸(可达 19 位),‌超过安全整数的值(如雪花 ID、大金额)在 JSON 解析时会被静默截断/四舍五入‌,导致数据错误。 ``` 把数据类型改成String就彻底没有这个问题了。 设计数据库表的时候,再加一个范式,主键无关性。 性别应该用 char(2) varchar(2) bit [男,女] int 下面这个图,是我用PowerDesinger画的物理数据模型。能够直接生成SQL语句,有兴趣自己建立一下pdm。当然你们给我提交的时候,可以用sql语句给我,用word文档给我,用pdm给我都可以。 image-20260831154124712 ![image-20260831154253346](images/image-20260831154253346.png) ### 作业 1:给我把该装的软件先装上, 2:需要你们把需求分析文档完成。相关UML图像完成。 3:完成数据库表的构建。需要你们自己把这个表建完。 ## 数据库 数据库的索引。index。B+树。索引为什么这么快? 海量数据如何分页? ``` mysql的默认分页方式 select * from tblteacher #mysql 使用limit关键字进行分页 select * from tblteacher limit 8,3 ``` 需要扫描并丢弃第一个参数offset的数据。深分页的时候表开销巨大。数据量特别大的时候,查询前100页是没有问题的,但是你查询最后几页的时候,速度特别慢。 先模拟网数据库里插入大量数据。 ``` #建表的语句 create table testindex( uid int auto_increment primary key, #主键 username varchar(20), #用户名 age int #年龄 )ENGINE=innodb charset=UTF8; #模拟插入一些数据 #随机生名字 select SUBSTRING('赵钱孙李周吴郑王',3,1) #孙 select SUBSTRING('赵钱孙李周吴郑王',4,1) #李 #把上面的第二个参数改为随机数 #100以内的随机数 select FLOOR(RAND()*100) select FLOOR(1+RAND()*8) ##随机的姓 select SUBSTRING('赵钱孙李周吴郑王',FLOOR(1+RAND()*8),1) #随机的姓 #随机的名 select SUBSTRING('玲凤菜富贵钱国军才华',FLOOR(1+RAND()*10),1) #随机姓名 mysql字符串链接要用concat函数 select concat('a','b') #完全随机的名字 select concat(SUBSTRING('赵钱孙李周吴郑王',FLOOR(1+RAND()*8),1),SUBSTRING('玲凤菜富贵钱国军才华',FLOOR(1+RAND()*10),1) ,SUBSTRING('玲凤菜富贵钱国军才华',FLOOR(1+RAND()*10),1) ) #随机的年龄 select FLOOR(1+RAND()*100) #插入数据 insert into testindex values (null,concat(SUBSTRING('赵钱孙李周吴郑王',FLOOR(1+RAND()*8),1),SUBSTRING('玲凤菜富贵钱国军才华',FLOOR(1+RAND()*10),1) ,SUBSTRING('玲凤菜富贵钱国军才华',FLOOR(1+RAND()*10),1) ),FLOOR(1+RAND()*100)) select * from testindex; #直接用表复制,来提供速度 select null,username,age from testindex; #然后你就使劲的运行下面的这个语句,至到你觉得够了为止 insert into testindex select null,username,FLOOR(1+RAND()*100) from testindex ``` ``` select * from testindex; select count(*) from testindex; #数据量特别大的时候分页 select * from testindex limit 20,10 select * from testindex limit 9000000,10 #查询效率比前几页低了10几倍 #而且如何我加了where条件之后,这个查询结果就很恐怖 。 #不想更改任何内容,就在现有的条件下,提高查询效率 #游标分页 这个是推荐的方式 select * from testindex where uid>=9327540 limit 10 上面这个SQL语句和上上面的执行结果是一致的,但是执行效率快了10几倍 #延迟关联 #先查询出分页的uid,再做关联查询 #这种关联查询速度会快于直接limit但是会慢于游标分页 select * from testindex a inner join (select uid from testindex limit 9000000,10) b on a.uid=b.uid ``` **#B+树索引 只适合建立高基数的列中** 因为像性别,学历,婚否这些东西都属于低基数的列,加了索引反而速度不快。 那么低基数的列,应该用BitMap索引。但是免费版没有。 ``` select * from testindex where uid=9000000 update testindex set age =102 where uid=9000000 #800多万数据 没有加索引 查询时间 2.23秒 select * from testindex where age=102; #给当前age列加索引 create index idx_test_age on testindex(age); #索引已经建立过了 索引建立之后,会自动使用,只要你的SQL写的ok #索引建立后,时间变为0.12秒 select * from testindex where age=102; ``` 数据结构的学习网站 ``` https://www.cs.usfca.edu/~galles/visualization/Algorithms.html ``` 任务:把需求分析,概要设计,详细设计三个文档。 ## SpringBoot ### 最基础的SpringBoot: 一个控制器+Swagger。 1: 建立项目 image-20260901140932216 2:新建模块 image-20260901141014718 3:设置 image-20260901141147586 4:更改模型中的pom.xml文件 image-20260901141439600 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.2.8 com.han demo 0.0.1-SNAPSHOT demo demo 17 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin ``` 5: 一定要刷新依赖 image-20260901141655277 6:Springboot基础环境就搭建完了。 image-20260901141809899 在核心启动类上鼠标右键,运行,就ok了。 ``` Tomcat started on port 8080 (http) with context path '' ``` 404也是成功 image-20260901141949613 ### 最简单的控制器 新建Java类 ![image-20260901142423122](images/image-20260901142423122.png) 写入controller.TestController,则完成控制器类的构建。 ```java package com.han.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; //所有的SpringBoot的控制器,都必须添加注解 @RestController //只有加了这个注解才能被SpringBoot扫描到 public class TestController { @GetMapping("/say") //将来你也页面的请求地址:http://localhost:8080/say //http://127.0.0.1:8080/say public String say(){ return "你好,河北理工大学!"; } } ``` ``` localhost 本机 127.0.0.1 本机 192.168.43.189 这个是我电脑的ip不是你的 主机名也可以正常访问 ``` ``` package com.han.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; //所有的SpringBoot的控制器,都必须添加注解 @RestController //只有加了这个注解才能被SpringBoot扫描到 public class TestController { @GetMapping("/say") //将来你也页面的请求地址:http://localhost:8080/say //http://127.0.0.1:8080/say public String say(){ //这个代码返回的事String return "你好,河北理工大学!"; } @GetMapping("/say2") public List say2(){ List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); return list; } } ``` ![image-20260901143822918](images/image-20260901143822918.png) 第三段代码 ```java package com.han.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; //所有的SpringBoot的控制器,都必须添加注解 @RestController //只有加了这个注解才能被SpringBoot扫描到 public class TestController { @GetMapping("/say") //将来你也页面的请求地址:http://localhost:8080/say //http://127.0.0.1:8080/say public String say(){ //这个代码返回的事String return "你好,河北理工大学!"; } @GetMapping("/say2") public List say2(){ List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); return list; } //新版本的Post参数接受,必须要加注解 //如果你是基础数据类型 @RequestParam //如果你是对象类型 @RequestBody 我要把接受的数据全部转成json @PostMapping("/save") //这个代码的请求方式是Post 你是不可以在页面直接敲地址 public String save(@RequestParam("username") String username){ //就是我们将来的增加代码 return "保存成功"; } } ``` ### Swagger 写完项目后,要写接口文档。swagger能够帮助你生成接口文档(直接给前端了),也便于我们自己来做测试。 #### 1:改pom.xml 加入 ``` org.springdoc springdoc-openapi-starter-webmvc-ui 2.6.0 ``` #### 2:更改核心配置文件 application.propertiese 改名为 application.yml 或者 application.yaml ![image-20260901150341619](images/image-20260901150341619.png) ```yaml server: port: 8080 springdoc: api-docs: path: /api-docs swagger-ui: path: /swagger-ui.html ``` #### 3: 配置类 新建config/SpringDocCongfig 文件 ![image-20260901150738071](images/image-20260901150738071.png) ![image-20260901150800189](images/image-20260901150800189.png) ```java package com.han.demo.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //这个类就是swagger和核心配置类 @Configuration public class SpringDocConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() .info(new Info() .title("东软环境监测系统 API") .description("老子的 API") .version("1.0.0")); } } ``` 我们现在就可以启动开始测试了。 下面是整个控制器的代码 ```java package com.han.demo.controller; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; //所有的SpringBoot的控制器,都必须添加注解 @RestController //只有加了这个注解才能被SpringBoot扫描到 public class TestController { @GetMapping("/say") //将来你也页面的请求地址:http://localhost:8080/say //http://127.0.0.1:8080/say public String say(){ //这个代码返回的事String return "你好,河北理工大学!"; } @GetMapping("/say2") public List say2(){ List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); return list; } //新版本的Post参数接受,必须要加注解 //如果你是基础数据类型 @RequestParam //如果你是对象类型 @RequestBody 我要把接受的数据全部转成json @PostMapping("/save") //这个代码的请求方式是Post 你是不可以在页面直接敲地址 public String save(@RequestParam("username") String username){ //就是我们将来的增加代码 return "保存成功"+username; } //写一个新方法,传入的事对象,而不是字符串 @PostMapping("/save2") //如果你是对象类型 @RequestBody 我要把接受的数据全部转成json public User save2(@RequestBody User user){ return user; } } //偷懒,用一种赖皮的方法在此处写一个对象 class User{ private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` 我们现在的swagger是默认吧用户的所有方法,都暴漏出来来。这是不合理的。应该是选择性的暴露。 最终版本的控制器代码 ```java package com.han.demo.controller; import io.swagger.v3.oas.annotations.Operation; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; //所有的SpringBoot的控制器,都必须添加注解 @RestController //只有加了这个注解才能被SpringBoot扫描到 public class TestController { @GetMapping("/say") //将来你也页面的请求地址:http://localhost:8080/say //http://127.0.0.1:8080/say @Operation(summary = "这是测试方法1",description = "这是测试方法1的描述") public String say(){ //这个代码返回的事String return "你好,河北理工大学!"; } @GetMapping("/say2") @Operation(summary = "这是测试方法2",description = "这是测试方法2的描述") public List say2(){ List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); return list; } //新版本的Post参数接受,必须要加注解 //如果你是基础数据类型 @RequestParam //如果你是对象类型 @RequestBody 我要把接受的数据全部转成json @PostMapping("/save") //这个代码的请求方式是Post 你是不可以在页面直接敲地址 @Operation(hidden = true) //这个注解是隐藏这个方法 public String save(@RequestParam("username") String username){ //就是我们将来的增加代码 return "保存成功"+username; } //写一个新方法,传入的事对象,而不是字符串 @PostMapping("/save2") @Operation(summary = "增加方法",description = "传递的参数必须是一个JSON") //如果你是对象类型 @RequestBody 我要把接受的数据全部转成json public User save2(@RequestBody User user){ return user; } } //偷懒,用一种赖皮的方法在此处写一个对象 class User{ private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` ![image-20260901152452734](images/image-20260901152452734.png) ## 企业中怎么发布项目 SpringBoot项目里面包含了Tomcat(Java的web容器)。所以是能够独立运行的。 往往是把这个SpringBoot打成jar包运行的。Tomcat容器。 前端代码(Vue,React,Angular,Html)放在哪里:Nginx里面 Nginx里面:前端 Tomcat: Java代码 ``` https://app.diagrams.net/# 画图的网站 ``` 典型的集中式的项目的部署架构。 image-20260902085406016 ### 如何打jar包 更改你的pom.xml ``` org.springframework.boot spring-boot-maven-plugin 3.2.8 com.han.demo.DemoApplication ``` 先双击maven中的clean,然后再双击package,包就打好了。 ``` java -jar demo-0.0.1-SNAPSHOT.jar ``` SpringBoot配置文件的加载机制 ``` 当前路径 当前classes路径 bootstrap.properties 可以支持配置文件云部署 配置文件可以放在nacos服务器中 bootstrap.yml application.properties application.yml 如果你有profile的话,也会加载profile的配置文件 spring: profiles: active: dev 也会加载 application_dev.properties application_dev.yml ``` 那么在实际中长这个样子 ![image-20260902095627450](images/image-20260902095627450.png) ### 自动装配的3种方式 Spring本事就是一个庞大的容器,提供了按需分配的方式,只要容器里面有的,你可以用自动装配来拥有。 (以前的方式:需要一个类的时候,你需要自己去new,自己负责这个类创建和销毁等所有行为) @AutoWired ``` @RestController public class UserinfoController { @AutoWired private UserinfoService userinfoService; @GetMapping("/test") public String test(){ return userinfoService.name; } } ``` @Resource ``` @RestController public class UserinfoController { @Resource //@Autowired private UserinfoService userinfoService; @GetMapping("/test") public String test(){ return userinfoService.name; } } ``` 构造函数注入 ``` @RestController public class UserinfoController { private final UserinfoService userinfoService; public UserinfoController(UserinfoService userinfoService) { this.userinfoService = userinfoService; } @GetMapping("/test") public String test(){ return userinfoService.name; } } ``` 都可以能够把类自动装配进来。你用哪一种其实都可以实现。 @Autowired 是SpringBoot提供的,默认按照类型装配,在按照name装配 先byType再byName 都存在NPE的问题 可以允许循环依赖 通过三级缓存 @Resource 是Java jsr提供的。适用的范围更广 byName找不到,再来byType 都存在NPE的问题 也可以允许循环依赖 **构造函数注入是官方推荐的方式** 如果发生了循环依赖,那么在项目启动的时候就直接死锁,报错。默认也是按照byType来找。 ## SpringBoot+MyBatis的CRUD ### Lombok 非常简单,就是来替换各种set get方法和构造函数的。 ``` org.projectlombok lombok ``` ``` @Data @AllArgsConstructor @NoArgsConstructor public class Userinfo { private Integer uid; private String username; private String password; private String phone; public static void main(String[] args) { Userinfo userinfo = new Userinfo(); System.out.println(userinfo.getUsername()); } } ``` ### 持久层 跟数据库打交道的这一层。 JPA Hibernate TopLink MyBatis MyBatisPlus 都是ORM(对象关系映射)框架 底层原理就是利用反射和映射关系自动帮你构造SQL语句。 Mybatis: ``` Method1: 数据表和实体的映射关系 程序会自动做你的实体和数据表的映射。并且调用SQL语句 Method2: public class Userinfo { private Integer uid; private String username; private String password; } 程序会自动做你的实体和数据表的映射。并且调用SQL语句 ``` 反射 能够调用类或者对象的任何一个字段,或者任何一个方法。纯粹就是让你们看一下。 ``` package com.han.demo.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.apache.catalina.User; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Userinfo { private String username = "张三"; private String password = "123456"; // public void eat(){ // System.out.println("我正在吃"); // } public static void main(String[] args) throws Exception{ // 可以得到你类的所有的字段和值 Object obj = Userinfo.class.newInstance(); Field[] fields = Userinfo.class.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName() ); field.setAccessible(true); System.out.println(field.get(obj)); } //反射是一种动态加载调用类的行为 几乎所有的框架最底层都是使用反射 //得到类的所有字段 // Field[] fields = Userinfo.class.getDeclaredFields();//得到当前类的所有字段 反射+设计模式就是核武器 // for (Field field : fields) { // System.out.println(field.getName()); // } // //得到类的所有方法 // Method[] methods = Userinfo.class.getDeclaredMethods() ; // for (Method method : methods) { // System.out.println(method.getName()); // } // //得到指定的方法 // Method eat = Userinfo.class.getDeclaredMethod("eat"); // Object obj = Userinfo.class.newInstance(); //通过反射调用对方的构造函数创建shili // eat.invoke(obj); } } ``` ### MyBatisPlus 制作增加,不做修改,你可以把它当Mybatis用,也可以用增强的功能。 ``` https://baomidou.com/ ``` ### SpringBoot整合MybatisPlus #### 1: 改Pom.xml ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.2.8 com.han demo 0.0.1-SNAPSHOT demo demo 17 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.springdoc springdoc-openapi-starter-webmvc-ui 2.6.0 org.projectlombok lombok com.baomidou mybatis-plus-spring-boot3-starter 3.5.12 com.baomidou mybatis-plus-generator 3.5.3.1 org.freemarker freemarker 2.3.30 com.mysql mysql-connector-j org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin 3.2.8 com.han.demo.DemoApplication ``` #### 2: application.yml ``` server: port: 9000 springdoc: api-docs: path: /api-docs swagger-ui: path: /swagger-ui.html #数据库的链接配置 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动 url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowPublicKeyRetrieval=true&useInformationSchema=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRet username: root password: ilovelaohan #这个你们一定要改成自己的 ``` #### 3: 自动生成类 **1:必须改包名 2:必须改你的数据库链接的数据库名称和密码** ``` package com.han.demo; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.io.File; import java.net.URLDecoder; import java.util.Collections; import java.util.Scanner; public class CodeGenerator2 { private static final String PACKAGE_NAME = "com.han.demo"; private static final String DB_NAME = "test"; // 全局唯一Scanner,解决多Scanner阻塞输入 private static final Scanner SCANNER = new Scanner(System.in); public static String scanner(String tip) { System.out.print("请输入" + tip + ":"); String ipt = SCANNER.nextLine().trim(); if (StringUtils.isNotBlank(ipt)) { return ipt; } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) throws Exception { // 修复:当前类是CodeGenerator2,不是CodeGenerator String classPath = CodeGenerator2.class.getProtectionDomain().getCodeSource().getLocation().getPath(); classPath = URLDecoder.decode(classPath, "UTF-8"); File classDir = new File(classPath); String classAbsPath = classDir.getAbsolutePath(); if (!classAbsPath.contains("target" + File.separator + "classes")) { throw new MybatisPlusException("仅支持IDE正常编译后运行,未识别到target/classes目录"); } // 拼接源码根 String srcMainJava = classAbsPath .replace(File.separator + "target" + File.separator + "classes", "") + File.separator + "src" + File.separator + "main" + File.separator + "java"; File javaSrcDir = new File(srcMainJava); if (!javaSrcDir.exists()) { throw new MybatisPlusException("源码目录不存在:" + srcMainJava); } // xml目录 String srcMainResources = classAbsPath .replace(File.separator + "target" + File.separator + "classes", "") + File.separator + "src" + File.separator + "main" + File.separator + "resources"; String xmlOutputDir = srcMainResources + File.separator + "mappers"; new File(xmlOutputDir).mkdirs(); System.out.println("\n===== 路径信息 ====="); System.out.println("class编译目录:" + classAbsPath); System.out.println("Java源码输出根:" + srcMainJava); System.out.println("Mapper XML输出目录:" + xmlOutputDir); System.out.println("====================\n"); // 这里正常调用scanner方法,会阻塞等待你输入表名 String tableStr = scanner("表名,多个英文逗号分割"); String[] tableArr = tableStr.split(","); FastAutoGenerator.create( "jdbc:mysql://localhost:3306/" + DB_NAME + "?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true", "root", "ilovelaohan") .globalConfig(builder -> builder .outputDir(srcMainJava) .author("laohan") .disableOpenDir() ) .packageConfig(builder -> builder .parent(PACKAGE_NAME) .moduleName("") .pathInfo(Collections.singletonMap(OutputFile.xml, xmlOutputDir)) ) .strategyConfig(builder -> builder .addInclude(tableArr) .entityBuilder() .enableLombok() .naming(NamingStrategy.underline_to_camel) .columnNaming(NamingStrategy.underline_to_camel) .mapperBuilder() .enableMapperAnnotation() .controllerBuilder() .enableRestStyle() ) .templateEngine(new FreemarkerTemplateEngine()) .execute(); System.out.println("代码生成完成!文件已写入源代码目录"); SCANNER.close(); } } ``` image-20260903095126196image-20260903095126196 已经生完了。 mapper/AqiFeedBackMapper: MybatisPlus官方的持久层类 如果你今后要写改SQL应该在此处 service/IAqiFeedBackService: 服务层的接口类,逻辑层的接口类 ​ AqiFeedBackServiceImpl: 上面接口的实现类,你今后有逻辑的代码,写在此处。 controller/AqiFeedbackController: 这个就是控制器的类,这个类得你自己写了。 #### Junit 就是一个单元测试的工具。来测试你的代码有没有问题的地方。仅仅只用来做测试。 @Test注解必须加在每个方法上。 ```java package com.han.demo; import com.han.demo.entity.AqiFeedback; import com.han.demo.service.IAqiFeedbackService; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class DemoApplicationTests { @Test void contextLoads() { } @Resource private IAqiFeedbackService aqiFeedbackService; @Test void testselect(){ System.out.println("测试"); List list = aqiFeedbackService.list(); for (AqiFeedback aqiFeedback : list) { System.out.println(aqiFeedback); } } @Test void testadd(){ System.out.println("测试增加"); AqiFeedback aqiFeedback = new AqiFeedback(); aqiFeedback.setTelId("13800000000"); aqiFeedback.setProvinceId(1); aqiFeedback.setCityId(1); aqiFeedback.setAddress("河北理工"); aqiFeedback.setInformation("今天天气白天转多云"); aqiFeedback.setEstimatedGrade(1); aqiFeedback.setAfDate("2026-09-03"); aqiFeedback.setAfTime("10:00:00"); aqiFeedbackService.save(aqiFeedback); } @Test void testfindById(){ System.out.println("测试查询"); AqiFeedback feedback = aqiFeedbackService.getById(1); System.out.println(feedback); } @Test void testdelete(){ System.out.println("测试删除"); aqiFeedbackService.removeById(1); } @Test void testUpdate(){ System.out.println("测试修改"); AqiFeedback feedback = aqiFeedbackService.getById(8); feedback.setAddress("河北理工真好"); aqiFeedbackService.updateById( feedback); } } ``` #### 控制器代码 ```java package com.han.demo.controller; import com.han.demo.service.IAqiFeedbackService; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** *

* 前端控制器 *

* * @author laohan * @since 2026-09-03 */ @RestController @RequestMapping("/aqiFeedback") @AllArgsConstructor public class AqiFeedbackController { private final IAqiFeedbackService aqiFeedbackService; @GetMapping("/select") public List select(){ return aqiFeedbackService.list() ; } } ``` 启动类 ```java package com.han.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @MapperScan("com.han.demo.mapper") //告诉srpging容器 Mybatis在哪里找 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` ``` http://localhost:9000/aqiFeedback/select ``` ![image-20260903101934325](images/image-20260903101934325.png) 早上的问题: ``` jdk17 两个地方牵扯数据库: application.yml CodeGenerate2.java 文件中 ``` #### 控制器CRUD的方法AqiFeedbackController ```java package com.han.demo.controller; import com.han.demo.entity.AqiFeedback; import com.han.demo.service.IAqiFeedbackService; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; /** *

* 前端控制器 *

* * @author laohan * @since 2026-09-03 */ @RestController @RequestMapping("/aqiFeedback") @AllArgsConstructor public class AqiFeedbackController { private final IAqiFeedbackService aqiFeedbackService; /** * 查询所有反馈 * @return */ @GetMapping("/select") public List select(){ return aqiFeedbackService.list() ; } /** * 根据id查询反馈信息 * @param afId * @return */ // @GetMapping("/find") // find?afid=1 @GetMapping("/find/{afId}") //RestFul 风格 、/find/1 public AqiFeedback findById(@PathVariable Integer afId ){ AqiFeedback feedback = aqiFeedbackService.getById(afId); return feedback; } @GetMapping("/delete/{afId}") //RestFul 风格 、/find/1 public boolean delete(@PathVariable Integer afId ){ return aqiFeedbackService.removeById(afId); } @PostMapping("/save") @ResponseBody public boolean save(@RequestBody AqiFeedback feedback){ return aqiFeedbackService.save( feedback); } @PostMapping("/update") @ResponseBody public boolean update(@RequestBody AqiFeedback feedback){ return aqiFeedbackService.updateById( feedback); } } ``` #### common/ResultVO ``` package com.han.demo.common; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @NoArgsConstructor @AllArgsConstructor @Data public class ResultVO implements Serializable { //能够灵活的返回结果,包括成功、失败、异常等 private Integer code; //状态码 private String message; //状态信息 private E data; //数据 } ``` 利用ResultVO重写控制器代码 ```java package com.han.demo.controller; import com.han.demo.common.ResultVO; import com.han.demo.entity.AqiFeedback; import com.han.demo.service.IAqiFeedbackService; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; /** *

* 前端控制器 *

* * @author laohan * @since 2026-09-03 */ @RestController @RequestMapping("/aqiFeedback") @AllArgsConstructor public class AqiFeedbackController { private final IAqiFeedbackService aqiFeedbackService; /** * 查询所有反馈 * @return */ @GetMapping("/select") public ResultVO select(){ List list = aqiFeedbackService.list() ; return new ResultVO(200,"查询成功",list); } /** * 根据id查询反馈信息 * @param afId * @return */ // @GetMapping("/find") // find?afid=1 @GetMapping("/find/{afId}") //RestFul 风格 、/find/1 public ResultVO findById(@PathVariable Integer afId ){ AqiFeedback feedback = aqiFeedbackService.getById(afId); return new ResultVO(200,"查询成功",feedback); } @GetMapping("/delete/{afId}") //RestFul 风格 、/find/1 public ResultVO delete(@PathVariable Integer afId ){ boolean success = aqiFeedbackService.removeById(afId); return new ResultVO(200,"删除成功",success); } @PostMapping("/save") @ResponseBody public ResultVO save(@RequestBody AqiFeedback feedback){ boolean success = aqiFeedbackService.save( feedback); return new ResultVO(200,"保存成功",success); } @PostMapping("/update") @ResponseBody public ResultVO update(@RequestBody AqiFeedback feedback){ boolean success = aqiFeedbackService.updateById( feedback); return new ResultVO(200,"更新成功",success); } } ``` #### 异常处理 ```java package com.han.demo.common; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice //会在所有controller中进行异常处理 public class GlobalExceptionHandler { //处理所有的异常 @ExceptionHandler(Exception.class) public ResultVO handleException(Exception e){ return new ResultVO(500,"操作失败",e.getMessage()); } //处理参数错误的异常 @ExceptionHandler(IllegalArgumentException.class) public ResultVO handleIllegalArgumentException(IllegalArgumentException e){ return new ResultVO(400,"参数错误",e.getMessage()); } } ``` 有了这个全局异常处理类,今后你就不用管异常了。 最后的控制器 ```java package com.han.demo.controller; import com.han.demo.common.ResultVO; import com.han.demo.entity.AqiFeedback; import com.han.demo.service.IAqiFeedbackService; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; /** *

* 前端控制器 *

* * @author laohan * @since 2026-09-03 */ @RestController @RequestMapping("/aqiFeedback") @AllArgsConstructor public class AqiFeedbackController { private final IAqiFeedbackService aqiFeedbackService; /** * 查询所有反馈 * @return */ @GetMapping("/select") public ResultVO select(){ List list = aqiFeedbackService.list() ; return new ResultVO(200,"查询成功",list); } /** * 根据id查询反馈信息 * @param afId * @return */ // @GetMapping("/find") // find?afid=1 @GetMapping("/find/{afId}") //RestFul 风格 、/find/1 public ResultVO findById(@PathVariable Integer afId ){ AqiFeedback feedback = aqiFeedbackService.getById(afId); return new ResultVO(200,"查询成功",feedback); } @GetMapping("/delete/{afId}") //RestFul 风格 、/find/1 public ResultVO delete(@PathVariable Integer afId ){ boolean success = aqiFeedbackService.removeById(afId); return new ResultVO(200,"删除成功",success); } @PostMapping("/save") @ResponseBody public ResultVO save(@RequestBody AqiFeedback feedback){ boolean success = aqiFeedbackService.save( feedback); return new ResultVO(200,"保存成功",success); } @PostMapping("/update") @ResponseBody public ResultVO update(@RequestBody AqiFeedback feedback){ boolean success = aqiFeedbackService.updateById( feedback); return new ResultVO(200,"更新成功",success); } } ``` #### 日志 很简单。日志很重要。程序出错看控制台。日志中的数据才最完整。 SLF4J application.yml ``` server: port: 9000 springdoc: api-docs: path: /api-docs swagger-ui: path: /swagger-ui.html #数据库的链接配置 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动 url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowPublicKeyRetrieval=true&useInformationSchema=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&allowPublicKeyRet username: root password: ilovelaohan #这个你们一定要改成自己的 #配置日志 logging: level: root: info #日志级别为 info 几乎是所有日志都是info级别 全部默认已经使用上了 com.han.demo: debug #额外设置这个包的日志级别 debug级别 #还希望经常能看到mybatis生成执行的SQL语句 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #其实也可以把日志放在文件中 #日志的级别 #OFF 全部关闭 #ERROR 系统发生的错误,需要立即关注 #WARN 警告,需要关注,但不 影响系统运行 #INFO 信息,一般不需要关注 #DEBUG 调试信息,一般最多在开发过程中,程序需要调试的时候才打开。 #TRACE 最细颗粒度的日志输出,需要看的内容,需要看的非常详细才打开。 ``` AqiFeedBackController.java ```java @Slf4j public class AqiFeedbackController { private final IAqiFeedbackService aqiFeedbackService; /** * 查询所有反馈 * @return */ @GetMapping("/select") public ResultVO select(){ List list = aqiFeedbackService.list() ; log.info("查询所有反馈信息,级别为info:{}",list); return new ResultVO(200,"查询成功",list); } /** * 根据id查询反馈信息 * @param afId * @return */ @GetMapping("/find/{afId}") //RestFul 风格 、/find/1 public ResultVO findById(@PathVariable Integer afId ){ AqiFeedback feedback = aqiFeedbackService.getById(afId); log.debug("查询反馈信息,级别为info:{}",feedback); return new ResultVO(200,"查询成功",feedback); } } ``` 常识 ``` public class AqiFeedbackController { private Integer age; private Userinfo info; #但实际全部生命周期中只有一份,全局共享。 如果你这个类没有受spring注解描述,没有在spring管理中,只是普通的java类这样写是可以的。 如果是controller,service,mapper你这么写就是找死。 } @Scope("singleton") //单例 spring整个生命中这个对象只有一份实例 只new一次 如果你不该,默认就是单例 @Scope("prototype") //原型 每次请求都new一个对象 在Spring中,你写的实例变量其实就是静态变量。 如果要写变量,请你一定写在参数中。 ``` 1+40000这个结果竟然是随机的 ``` package com.han.demo; public class Test { //从1+1加到40000.理论上应该就是40000. CAS 算法 public static int count = 0; public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 2000; j++) { count++; } }).start(); } //等到上面的代码执行完毕,我想看count的值是多少 while (Thread.activeCount() > 2){ //idea启动运行main,默认2个线程。 Thread.yield(); //线程礼让 让出当前cpu的执行权,让其它线程有执行的机会 } System.out.println(count); } } ``` i++天然存在这个问题。 ``` i = 0 A:getField 0 B:getField 0 C:getField 0 A:add 1 B:add 1 C:add 1 A: putfield 1 B: putfield 1 C: putfield 1 ``` 解决方式1:上锁。 解决方式2:CAS。 ``` i = 0 A:getField 0 存储了主存中的值 0 B:getField 0 存储了主存中的值 0 C:getField 0 存储了主存中的值 0 A:add 1 A: putfield 1 检测我存储的主存的值0 和实际主存值是否相等。如果相等,证明没有人改过 执行 i=1 B:add 1 B: putfield 1 检测我存储的主存的值0 和实际主存值是1。不相等,自旋(再来一遍) B:getField 1 存储了主存中的值 1 B:add 2 B: putfield 2 检测我存储的主存的值1 和实际主存值是1。如果相等,证明没有人改过 执行 这个执行效率远高于synchronized。但是毕竟耗费CPU。 ``` 只要改成AtomicInteger就不存在多线程的问题了。 List集合删除要用从尾删法 ``` package com.han.demo; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add("丁书豪"); list.add("陈帅"); list.add("陈帅"); list.add("陈帅"); list.add("陈优"); // list.remove("陈帅"); for (int i = list.size()-1; i >=0 ; i--) { String s = list.get(i); if("陈帅".equals(s)){ list.remove(i); } } System.out.println(Arrays.toString(list.toArray())); } } ``` ``` package com.han.demo; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Test { public static void main(String[] args) { //货币计算 // double a = 0.02; // double b = 0.03; // System.out.println(b-a); //应该用BigDecimal 不要用数字类型做参数 // BigDecimal a = new BigDecimal(0.2); // BigDecimal b = new BigDecimal(0.3); // System.out.println(b.subtract(a)); //一定要用字符串 BigDecimal a = new BigDecimal("0.2"); BigDecimal b = new BigDecimal("0.3"); System.out.println(b.subtract(a)); } } ``` #### volatile 请你今后如何还是要用static变量,多线程环境下请你给变量加上volatile。 ![image-20260904141841490](images/image-20260904141841490.png) ``` public class Test { private volatile int num = 0; public void add(){ num = num+60; } public static void main(String[] args) { //一个线程用来给这个变量加值 //另外一个线程用来看这个变量,如果这个变量》=60,就打印出来 Test test = new Test(); new Thread(new Runnable() { @Override public void run() { System.out.println("增加方法开始运行了"); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } test.add(); System.out.println("增加方法运行完了"); } }).start(); //开启线程 //写一个死循环 while(test.num==0){ } System.out.println("num的值是:"+test.num+"所有代码执行ok"); } } ``` volatile关键字的 作用1就是,保持内存可见,读数据从主存中获取,而不是工作空间。 作用2 禁止指令重排 ``` 计算机CPU在执行指令的时候,会根据指令的条件可能重新排列指令的执行顺序。禁止重排:禁止优化。 ``` #### Mybatis中多表操作 MyBatisPLus默认生成的就是单表。多表需要我们自己来搞。 ``` 方式1;完全面向对象 AqiFeedBack对象中,包含Province对象(直接包含City对象), 方式2:不用面向对象,面向结果 自己写多表查询,构建DTO对象就行。 ``` ``` #查询所有网格员监督信息 SELECT * FROM `aqi_feedback` #查询所有网格员监督信息带省 SELECT aqi.*,p.province_name FROM `aqi_feedback` aqi inner join grid_province p on aqi.province_id = p.province_id #查询所有网格员监督信息带省和市 SELECT aqi.*,p.province_name,c.city_name FROM `aqi_feedback` aqi ,grid_province p ,grid_city c where aqi.province_id = p.province_id and aqi.city_id = c.city_id ``` ##### AqiFeedBackMapper.java ```java @Mapper public interface AqiFeedbackMapper extends BaseMapper { //制作增加不做修改 //默认只有传统的基础的CRUD的方法 //如果我想要特殊的方法,需要你在这里自己写 @Select("准备要搞的SQL语句") public List findAll(); } ``` 你原来的Controller调用的事Service的List方法,而你的Service调用的事Mapper的List方法。 需要改IAqiFeedbackService和AqiFeedbackServiceImpl和AQiFeedbackController的三个类才行。 IAqiFeedbackService ``` public interface IAqiFeedbackService extends IService { public List findAll(); } ``` AqiFeedbackServiceImpl ``` public class AqiFeedbackServiceImpl extends ServiceImpl implements IAqiFeedbackService { public List findAll(){ return baseMapper.findAll(); } } ``` AQiFeedbackController ``` @GetMapping("/select") public ResultVO select(){ List list = aqiFeedbackService.findAll() ; //这句话是被修改的 log.info("查询所有反馈信息,级别为info:{}",list); return new ResultVO(200,"查询成功",list); } ``` AqiFeedback.java 新加入两个字段 ``` private String provinceName; private String cityName; ``` ### 缓存 数据放在内存中,不用每次都产生磁盘IO。 缓存:Redis MemCache EhCache等等很多。 #### SpringCache: 启动非常简单。缓存奇效的效果 第一次的时候会从数据库查询,放入缓存。 后面的查询,都从缓存中命中。 ``` 1:启动类上加注解 @EnableCaching 2:控制器方法上加注解 @GetMapping("/select") @Cacheable("aqifeedback") public ResultVO select(){ List list = aqiFeedbackService.findAll() ; log.info("查询所有反馈信息,级别为info:{}",list); return new ResultVO(200,"查询成功",list); } ``` 缓存只存在在内存中,服务器中重启,缓存丢失。 如果想缓存不丢失,必须要采用第三方的缓存。 Redis 1: 修改pom.xml 加入 ``` org.springframework.boot spring-boot-starter-data-redis ``` 2:修改application.yml ``` spring: cache: type: redis #这样缓存就切换到了Redis当中 ``` 这个周末:你们把VsCode装好。NodeJs装好。 ## Vue VsCode安装看笔记。官网下载,一路Next就ok了。 NodeJs安装看笔记。官网下载,一路Next就ok了。 ​ 你里面就有npm命令了,为了提高速度可以下载cnpm ### 1:创建项目 vue create front ![image-20260907084147078](images/image-20260907084147078.png) 选择第三个,手工选择。 ![image-20260907084237773](images/image-20260907084237773.png) 一路回车,就OK了。 在你创建的目录中,就启动了前端的项目。 ``` npm run serve ``` 打包 ``` npm run build ``` 打包完就可以把dist里面的内容直接放在nginx里面了。 ### 2:静态页面 src/views/AqiFeedBackList.vue ``` ``` 目前这个地址无法访问,必须要声明路由。才能写链接。 src/router/index.js 加入 ``` { path: '/aqiFeedback', name: 'aqiFeedback', component: () => import('../views/AqiFeedBackList.vue') } ``` 你只要在浏览器中/aqiFeedback就可以访问了。 src/App.vue 首页 ``` AQI反馈列表 ``` 静态页面就可以正常访问了。 ### 3:列表数据 安装axios, 就是来发远程请求的。 ``` npm install axios --save ``` 在src目录下新建api目录,新建aqiFeedback.js文件 src/api/aqiFeedback.js ``` import axios from 'axios' const request = axios.create({ baseURL: 'http://localhost:9000/', timeout: 5000 }) //请求aqi反馈列表的方法 export function getAqiFeedbackList() { return request({ url: '/aqiFeedback/select', method: 'get' }) } ``` 后端的方法上要加入@CrossOrigin,来解决跨域的问题。 ``` 只要你看到CROS报错,就需要。原因:两个不同的服务器之间的访问,就需要加跨域。 还有一种加法:写一个配置类,在配置类里面加了全局的跨域设置。 ``` AqiFeedBackList.vue 注意我写的过程,先写的Js,写完之后测试用控制台打印的方式,看数据到底有没有,再写html。 ```html ``` ### 4:删除 正式项目不会有物理删除,都是逻辑删除。 src/api/aqiFeedback.js ``` export function deleteByAfid(afId) { return request({ url: `/aqiFeedback/delete/{afId}`, method: 'get', }) } ``` src/views/AqiFeedBackList.vue ``` async deleteItem(afId){ const res = await deleteByAfid(afId); // console.log(res.data) if(res.data.code === 200){ // 删除成功 this.fetchList(); // 重新获取列表数据 } }, ``` 完整代码AqiFeedBackList.vue ```html ``` ![image-20260907100937392](images/image-20260907100937392.png) ### 5:增加 后端地址 ``` http://localhost:9000/aqiFeedback/save method:post ``` 静态增加页面 src/views/AqiFeedBackForm.vue ``` ``` src/router/index.js ``` { path: '/aqiFeedbackForm', name: 'aqiFeedbackForm', component: () => import('../views/AqiFeedBackForm.vue') } ``` src/views/AqkFeedBackList.vue ``` ``` src/api/aqifeedback.js ``` //增加aqi反馈的方法 export function saveAqiFeedback(data) { return request({ url: `/aqiFeedback/save`, method: 'post', data }) } ``` src/views/AqiFeedBackForm.vue ```html ``` 完整的AqiFeedBackForm.vue ``` ``` ### 6:修改 先查询,再修改。修改页面和增加页面往往是同一个页面。用判断来进行区分。 src/api/aqiFeedback.js ``` //根据afId查询aqi反馈的方法 export function getAqiFeedbackById(afId) { return request({ url: `/aqiFeedback/find/${afId}`, method: 'get' }) } //修改aqi反馈的方法 export function updateAqiFeedback(data) { return request({ url: `/aqiFeedback/update`, method: 'post', data }) } ``` 填充数据 src/views/AqiFeedbackForm.vue ``` ``` 提交修改 src/views/AqiFeedbackForm.vue ``` ``` 完整的单表的CRUD就写完了。 ## 省市 市的数据不全,你自己用ai补齐。 #### 后端 查询所有省,查询某省下的所有市。里面代码生成,生成省市的代码。 GridProvinceController.java ```java package com.han.demo.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.han.demo.common.ResultVO; import com.han.demo.entity.GridCity; import com.han.demo.entity.GridProvince; import com.han.demo.service.IGridCityService; import com.han.demo.service.IGridProvinceService; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import java.sql.Wrapper; import java.util.List; /** *

* 前端控制器 *

* * @author laohan * @since 2026-09-08 */ @RestController @RequestMapping("/province") @AllArgsConstructor public class GridProvinceController { private final IGridProvinceService provinceService; //省的service private final IGridCityService cityService; //市的 service @GetMapping("list") // 查询所有省 @CrossOrigin public ResultVO list() { List list = provinceService.list() ; return new ResultVO(200,"查询成功",list); } /** * 根据省id查询城市 * @param provinceId * @return */ @GetMapping("getCitys") @CrossOrigin public ResultVO getCitys(@RequestParam("provinceId") Integer provinceId){ QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("province_id",provinceId);//省id市传入的值 //上面两行是查询条件 List list = cityService.list(wrapper); return new ResultVO(200,"查询成功",list); } } ``` #### 前端 ##### 省 src/api/aqiFeedback.js ```js //获取所有省份的方法 export function getProvinces(){ return request({ url: `/province/list`, method: 'get' }) } //根据省id获取所有城市的方法 export function getCityByPid(provinceId){ return request({ url: `/province/getCitys?provinceId=${provinceId}`, method: 'get' }) } ``` src/views/AqiFeedBackForm.vue 省希望页面一加载的时候就出现,所以应该写在created mounted这两个方法之一中。 ```html ``` ##### 市 src/views/AqiFeedBackForm.vue ```html ``` ## AI Codex :用不起。 Trae(字节): chat build build with mcp 每天80-100次自动修改代码 cloudcode: 你用手机发一条指令。 项目---Setting---Plug in---输入trae 安装就ok了。 ![image-20260908094435051](images/image-20260908094435051.png) 使用的ai的时候,条件尽量给足。你需要干什么,本身有什么资源,尽量都提供给AI。 Vscode ![image-20260908095130467](images/image-20260908095130467.png)