准备工作:
一、数据库设计
创建edu_teacher表,包含字段:id, name, intro, career, level, avatar, sort, is_deleted, gmt_create, gmt_modified。主键约束:id。sql语句详见:sql传送门
二、搭建项目工程:
父工程
父工程(guli_parent)创建可以使用Spring Initilizer,子工程创建最好使用Maven。
父工程的pom.xml:
- SpringBoot版本:2.2.1.RELEASE
- 添加
pom - 删除dependencies标签中的所有内容
- 添加
确定依赖的版本,对这个项目进行控版本控制 - 配置
锁定依赖的版本(父工程的pom.xml只是对版本的依赖做了定义,并没有用。真正的使用是在子模块中) - 删除父工程的src目录。
父工程的pom.xml全部内容见:pom.xml
子模块
在父工程guli-parent下面创建子模块service(删除src目录),pom.xml见:pom.xml
子子模块
在service模块下创建子模块service-edu(讲师管理模块)
编写application.properties配置文件
# 服务端口 server.port=8001 # 服务名 spring.application.name=service-edu # 环境设置:dev、test、prod spring.profiles.active=dev # mysql数据库连接 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=630586 # mybatis日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
生成相关代码
1-复习Mybatis-Plus专门讲了Mybatis-Plus(MP)的使用,就是因为使用MP可以大大简化开发效率,重复、固定的代码不需要再手写了。
根据edu_teacher表,使用MP代码生成器生成entity层、controller层、service层、mapper层的骨架代码,代码生成器不要求会写,仅要求会改。
官网:苞米豆
代码生成器的作用:和MP的逆向工程差不多,根据表生成代码。
代码生成器的依赖已在service模块的pom.xml中引过了,如下
<!-- velocity模板引擎, Mybatis Plus代码生成器需要 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> </dependency>
在service_edu模块的测试包下建立CodeGenerator.java
// 该代码只要求会改,不要求会写 public class CodeGenerator { @Test public void main1() { // 1、创建代码生成器 AutoGenerator mpg = new AutoGenerator(); // 2、全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir("C:\\Users\\87672\\Desktop\\桌面上的文件夹\\新建文件夹\\Java学习笔记\\guli_parent\\service\\service_edu" + "/src/main/java"); // 输出目录 gc.setAuthor("atguigu"); gc.setOpen(false); // 生成后是否打开资源管理器 gc.setFileOverride(false); // 重新生成时文件是否覆盖 /* * mp生成service层代码,默认接口名称第一个字母有 I * UserService * */ gc.setServiceName("%sService"); // 去掉Service接口的首字母I(没有用) gc.setIdType(IdType.ID_WORKER_STR); // 主键策略,因为id是char类型,所以用ID_WORKER_STR gc.setDateType(DateType.ONLY_DATE); // 定义生成的实体类中日期类型 gc.setSwagger2(true);//开启Swagger2模式 mpg.setGlobalConfig(gc); // 3、数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("630586"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 4、包配置 PackageConfig pc = new PackageConfig(); // 包名:com.atguigu.eduservice pc.setParent("com.atguigu"); pc.setModuleName("eduservice"); //模块名 // 包名:com.atguigu.eduservice.controller pc.setController("controller"); // 包名:com.atguigu.eduservice.entity pc.setEntity("entity"); // 包名:com.atguigu.eduservice.service pc.setService("service"); // 包名:com.atguigu.eduservice.mapper pc.setMapper("mapper"); mpg.setPackageInfo(pc); // 5、策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("edu_teacher"); strategy.setNaming(NamingStrategy.underline_to_camel);// 数据库表映射到实体的命名策略 strategy.setTablePrefix(pc.getModuleName() + "_"); // 生成实体时去掉表前缀 strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 数据库表字段映射到实体的命名策略 strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作 strategy.setRestControllerStyle(true); // restful api风格控制器 strategy.setControllerMappingHyphenStyle(true); // url中驼峰转连字符 mpg.setStrategy(strategy); // 6、执行 mpg.execute(); } }
上述代码执行完成后会在java包下生成:entity层、controller层、service层、mapper层
三、集成测试工具Swagger
浏览器仅支持post和get提交,不支持delete提交,因此需要借助工具进行测试(swagger、postman等)
本项目全部、统一采用swagger进行测试。
下面先引入swagger:
在父工程(guli_parent)下创建common模块,该模块中包含公共的方法、依赖、工具类等。
在common模块的pom.xml中引入依赖:
<!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency>
在common模块下创建子模块service_base,项目结构如下图所示:
在service_base模块中创建SwaggerConfig类
package com.atguigu.servicebase; @Configuration // 表明这是一个配置类 @EnableSwagger2 // 表明这是一个swagger注解 public class SwaggerConfig { @Bean public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() .paths(Predicates.not(PathSelectors.regex("/admin/.*"))) .paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("网站-课程中心API文档") .description("本文档描述了课程中心微服务接口定义") .version("1.0") .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com")) .build(); } }
在service的pom.xml中引入service_base(在其他模块使用本模块的方法,就得在其他模块中引入本模块的坐标)
<dependency> <groupId>com.atguigu</groupId> <artifactId>service_base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
四、edu-service模块准备工作
创建主启动类
刚刚说了,父工程可以直接用SpringBoot创建,因为它只负责依赖管理,并不写实际代码,所以怎么快捷怎么创建;而子工程采用maven创建,因此并不自带XxxApplication
@SpringBootApplication @ComponentScan(basePackages = {"com.atguigu"}) // 设置包扫描规则 @EnableDiscoveryClient // Nacos注册,该注解目前用不到 @EnableFeignClients // 服务调用,该注解目前用不到 public class EduApplication { public static void main(String[] args) { SpringApplication.run(EduApplication.class,args); } }
创建配置类,配置mapper扫描范围
@Configuration @MapperScan("com.atguigu.eduservice.mapper") public class EduConfig { }
统一返回json时间格式
默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时。
在application.properties中设置:
#返回json的全局时间格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8
五、统一返回数据格式
之前在controller中写返回值时,要不写的是String,要不就是自定义的其他类,这样做会使得开发缺乏标准(既没有统一的返回值类型,也没有统一的状态码),所以在此先统一返回的数据格式(R),今后项目中所有controller层的返回值均采用此格式。定义:
R.ok():成功(状态码:20000)
R.error():失败(状态码:20001)
例如:
json数据格式有两种:对象和数组。两种格式经常混合使用
{
"success":布尔 // 相应是否成功
"code":数字 // 响应码(状态码)
"message":字符串 // 返回消息
"data":HashMap // 返回数据 ,放在键值对中
}
在common模块中定义统一返回结果模块common_utils,创建R和ResultCode,定义返回数据的状态码和返回值类型,
public interface ResultCode {
Integer SUCCESS = 20000; // 操作成功
Integer ERROR = 20001; // 操作失败
}
/**
* @Deception 统一返回结果
* 写法固定
*/
@Data
public class R {
// @ApiModelProperty()用于方法、字段。value属性对字段说明
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "状态码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String,Object> data = new HashMap<>();
// 把构造方法私有化
// 私有空参构造去,让别人不能new R对象,而只能使用提供好的ok()和error()方法
private R(){}
// 成功的静态方法
public static R ok(){
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}
// 失败的静态方法
public static R error(){
R r = new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
// this:当前类的对象
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R message(String message) {
this.setMessage(message);
return this;
}
public R code(Integer code) {
this.setCode(code);
return this;
}
public R data(String key, Object value) {
this.data.put(key, value);
return this;
}
public R data(Map<String, Object> map) {
this.setData(map);
return this;
}
}
使用统一结果
在service中引入common_utils的坐标
把controller层的返回值类型都改成R
public R findAllTeacher(){ // 调用service里面的方法,实现查询全部的操作 List<EduTeacher> list = teacherService.list(null); return R.ok().data("items",list); } public R removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id){ boolean flag = teacherService.removeById(id); return flag ? R.ok() : R.error(); }
六、异常处理
全局异常处理
在service_base模块下新建exceptionhandler包,在里面写统一处理异常的类,在项目报错时,执行我们自己定义的异常。
引入依赖
<dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>common_utils</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
统一异常处理
@ControllerAdvice public class GlobalExceptionHandler { // 指定出现什么异常执行这个方法 @ExceptionHandler(Exception.class) @ResponseBody // 为了能够返回数据,相当于把该类放在controller中 public R error(Exception e){ e.printStackTrace(); return R.error().message("执行了全局异常处理"); } }
特定异常处理
把Exception换成具体的XxxException即可
创建自定义异常类,继承RuntimeException,在其中写异常属性
// 自定义异常处理 @Data @NoArgsConstructor @AllArgsConstructor public class GuliException extends RuntimeException{ private Integer code; // 异常状态码 private String msg; // 异常信息 }
添加规则
// 自定义异常 @ExceptionHandler(GuliException.class) @ResponseBody public R error(GuliException e){ e.printStackTrace(); return R.error().code(e.getCode()).message(e.getMsg()); }
执行自定义异常
// 手动模拟异常 try { int i = 10 / 0; }catch (Exception e){ throw new GuliException(20001,"执行了自定义异常处理..."); }
七、统一日志处理
日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
# 配置日志级别 logging.level.root=WARN
Logback日志:把日志不仅输出到控制台,也可以输出到文件中,使用日志工具
# 1.把如下的日志配置全部去掉 # mybatis日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # 配置日志级别 logging.level.root=WARN # 2.在resource中创建logback-spring.xml
使用
// 1.在GlobalExceptionHandler上加注解@Slf4j // 2.在自定义异常类中,加:log.error(e.getMsg()); // 3.手动模拟异常 try { int i = 10 / 0; }catch (Exception e){ throw new GuliException(20001,"执行了自定义异常处理..."); } // 4.访问localhost:8001/swagger-ui.html#的 pageListTeacher // 5.可以看到 log_error.error中有错误日志
项目代码
一、查询所有讲师
controller层
// controller调 service,service调 mapper @RestController // 返回json数据 @Api(description = "讲师管理") @RequestMapping("/eduservice/edu-teacher") @SuppressWarnings("all") public class EduTeacherController { // 把service注入 @Autowired private EduTeacherService teacherService; @ApiOperation(value = "所有讲师列表") // 名字提示 // 1.查询讲师表中的所有数据 // restful风格 @GetMapping("/findAll") public List<EduTeacher> findAllTeacher(){ // 调用service里面的方法,实现查询全部的操作 List<EduTeacher> list = teacherService.list(null); return list; } }
Swagger测试
http://localhost:8001/eduservice/teacher/findAll
二、讲师逻辑删除功能
在实体类上添加注解@TableLogic、配置文件、配置类
@TableLogic @ApiModelProperty(value = "逻辑删除 1(true)已删除,0(false)未删除") private Boolean isDeleted;
# 逻辑删除配置 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0
@Configuration @MapperScan("com.atguigu.eduservice.mapper") public class EduConfig { // 配置逻辑删除 @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); } }
逻辑删除讲师的方法
// 通过路径传递id值 @RestController // 返回json数据 @Api(description = "讲师管理") @RequestMapping("/eduservice/edu-teacher") @SuppressWarnings("all") public class EduTeacherController { // 把service注入 @Autowired private EduTeacherService teacherService; @ApiOperation(value = "逻辑删除讲师") // 名字提示 // 2.逻辑删除讲师的方法 // 通过路径传递id值 @DeleteMapping("{id}") // @ApiParam:名字提示 public boolean removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id){ boolean flag = teacherService.removeById(id); return flag; } }
Swagger测试
http://localhost:8001/swagger-ui.html
三、分页和多条件查询
分页
配置分页插件
@Configuration @MapperScan("com.atguigu.eduservice.mapper") public class EduConfig { // 配置讲师分页插件 @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); } }
编写讲师分页查询接口的方法
/** * 讲师分页查询的方法 * 通过路径传递 * @param current 当前页 * @param limit 每页记录数 * @return */ @GetMapping("/pageTeacher/{current}/{limit}") public R pageListTeacher(@PathVariable long current, @PathVariable long limit){ // 创建page对象 Page<EduTeacher> pageTeacher = new Page<>(current,limit); // 调用方法实现分页 teacherService.page(pageTeacher,null); long total = pageTeacher.getTotal(); // 总记录数 List<EduTeacher> records = pageTeacher.getRecords(); // 数据list集合 return R.ok().data("total",total).data("rows",records); }
Swagger测试
多条件查询带分页功能
要求:根据讲师名称name,讲师头衔level、讲师入驻时间gmt_create(时间段)查询
把条件值封装到对象中,把对象传递到接口里面(vo)
@Data public class TeacherQuery { @ApiModelProperty(value = "教师名字,模糊查询") private String name; @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师") private Integer level; @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10") // 注意:这里使用的是String类型,前端传过来的数据无需进行类型转换 private String begin; @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10") private String end; }
Q:为啥还专门造个实体类(TeacherQuery)来封装数据?原来的实体类(EduTeacher)还不够用吗?
A:EduTeacher中包含数据库表中的全部字段,如果拿EduTeacher来封装数据,则除了name、level、begin、end四个需要的字段外,还多出了冗余字段,通过前端整合页面后的效果也能看出,只需要封装这四个字段。
根据条件值进行判断,拼接条件
/** * 多条件组合查询带分页功能 * @param current 当前页 * @param limit 每页记录数 * @param teacherQuery 把条件值封装到对象中,把对象传递到接口里面 * @return */ @GetMapping("/pageTeacherCondition/{current}/{limit}") public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit, TeacherQuery teacherQuery){ // 创建page对象 Page<EduTeacher> page = new Page<>(current,limit); // 构造条件 QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>(); // 判断条件值是否为空,如果不为空则拼接条件 String name = teacherQuery.getName(); Integer level = teacherQuery.getLevel(); String begin = teacherQuery.getBegin(); String end = teacherQuery.getEnd(); if (!StringUtils.isEmpty(name)){ wrapper.like("name",name); //参数1:字段名;参数2:具体值 } if (!StringUtils.isEmpty(level)){ wrapper.eq("level",level); } if (!StringUtils.isEmpty(begin)){ wrapper.ge("gmt_create",begin); } if (!StringUtils.isEmpty(end)){ wrapper.le("gmt_create",end); } // 调用方法实现条件分页查询 teacherService.page(page,wrapper); long total = page.getTotal(); List<EduTeacher> records = page.getRecords(); return R.ok().data("total",total).data("rows",records); }
Swagger测试:
四、添加讲师
在实体类的时间上加注解
@ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @TableField(fill = FieldFill.DEFAULT) @ApiModelProperty(value = "更新时间") private Date gmtModified;
在service_base中创建handler包,在里面配置时间自动填充
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("gmtCreate",new Date(),metaObject); this.setFieldValByName("gmtModified",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("gmtModified",new Date(),metaObject); } }
编写controller
// 添加讲师的方法 @PostMapping("/addTeacher") public R addTeacher(@RequestBody EduTeacher eduTeacher){ boolean save = teacherService.save(eduTeacher); return save ? R.ok() : R.error(); }
Swagger测试
{ "avatar": "string", "career": "string", "intro": "string", "isDeleted": false, "level": 0, "name": "string1010", "sort": 0 }
五、修改讲师
根据讲师id进行查询(回显)
@GetMapping("/getTeacher/{id}") public R getTeacher(@PathVariable String id){ EduTeacher eduTeacher = teacherService.getById(id); return R.ok().data("teacher",eduTeacher); }
讲师修改(先查再改)
@PostMapping("/updateTeacher") public R updateTeacher(@RequestBody EduTeacher eduTeacher){ boolen flg = teacherService.updateById(eduTeacher); return flg ? R.ok() : R.error(); }
Swagegr测试
{ "avatar": "string", "career": "string", "id": "1189390295668469762", "intro": "string", "isDeleted": false, "level": 0, "name": "1010upup", "sort": 0 }