讲师管理模块(后端):
1.导入(设计)数据库
设计库设计规约(参考了《阿里巴巴java开发手册》):
创建父工程 pom类型 管理依赖的版本,放公共依赖
guli_parent
子工程:详细的功能模块
service
子子模块:更细分的功能模块
service_edu
application.yml
server:
port: 8001
spring:
application:
name: service-edu
profiles:
active: dev #环境设置: dev,test,prod
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
username: root
password: 148963
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_UUID
编写controller service mapper
使用mp的代码生成器
个人使用mybatisX插件进行自动生成
先在idea导入mysql
选中一个表,点这个选项
按照管理只要改这4个就行
拼接成一个完整的路径
module path(模块路径名)/base path(项目路径名)/base package(包名)/relative package(实体类的包名)
注意:1.可能各个路径会自动给错,需要检查手动修改
2.base package要用点隔离
3.实体类包可以写dto或者entity
上面斟酌选择(我加了一个Lombok,maven导入的是3以上的版本,选择mybatis-plus3),下面可以删掉不需要的内容
导入成功
如果出现导的包报错,就调整这两个地方的maven版本一致就行
注意修改一些字段,有些字段和属性不能匹配
编写第一个controller
1.创建类,添加注解(默认Rest风格)
@RestController
@RequestMapping(“/eduservice/teacher”)
2.注入Service
private EduTeacherService eduTeacherService;
创建一个findAllTeacher方法,使用的GetMapping方法
package com.lkw.serviceedu.controller;
import com.lkw.serviceedu.entity.EduTeacher;
import com.lkw.serviceedu.service.EduTeacherService;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/eduservice/teacher")
public class EduTeacherController {
//注入service
@Autowired
private EduTeacherService eduTeacherService;
//http://localhost:8001/eduservice/teacher/findAll
//GetMapping,获取全部的教师列表
@GetMapping("findAll")
public List<EduTeacher> findAllTeacher(){
//调用service的list,查询条件为null(查到的所有都返回)
List<EduTeacher> list=eduTeacherService.list(null);
return list;
}
}
里面可以加分页,逻辑删除,性能测试之类的配置
package com.lkw.eduservice.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.lkw.eduservice.mapper")
public class EduConfig {
//乐观锁
//自动填充
//分页
//逻辑删除
//性能插件
}
注意把service的pom.xml的这些多余依赖注释掉,然后刷新maven
启动并且查询成功
检查链接:http://localhost:8001/eduservice/teacher/findAll
在application.yml添加
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
使用的逻辑删除,在配置类添加逻辑删除插件
//mp版本大于3.1.1不需要配置逻辑删除插件
//给实体类的逻辑删除属性添加逻辑删除注解
继续编写EduTeacherController,使用@deleteMapping(“{id}”)
//http://localhost:8001/eduservice/teacher/delete/1
//逻辑删除讲师
@DeleteMapping("{id}")
public boolean removeTeacher(@PathVariable String id){
return eduTeacherService.removeById(id);
}
本项目使用的swagger2,3版本暂时不能配上
丝袜哥的作用:获取项目的api,生成准确实时的接口文档
依赖:
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
添加一个配置类
package com.lkw.servicebase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(true)
.groupName("这是组名")
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build();
}
@SuppressWarnings("all")
public ApiInfo apiInfo(){
return new ApiInfo(
"这是标题",
"这是描述",
"v1.0",
"2279719702@qq.com", //开发者团队的邮箱
"lkw",
"许可证", //许可证
"http://www.baidu.com" //许可证链接
);
}
}
在启动类添加注解**@EnableWebMvc**
启动并访问:http://localhost:8080/swagger-ui/index.html
其他的交给翻译,然后自己看吧
建立一个common子模块和service_base子子模块,为了所有模块都能用
引入common模块的依赖,删除common的src目录:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
dependencies>
创建SwaggerConfig
package com.lkw.servicebase;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
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模块的maven引入service_base模块
<dependency>
<groupId>com.lkw.servicebasegroupId>
<artifactId>service_baseartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
在EduApplication启动类添加组件扫描注解
@ComponentScan(basePackages={“com.lkw.*”})
http://localhost:8001/swagger-ui.html
直接改依赖和注解就行
http://localhost:8001/swagger-ui/
最后注意给Controller添加注解:@Api(value=“讲师管理模块”)
给方法添加注解
将响应封装成一个json使得所有的接口数据格式都统一,方便前端使用,方便维护
因为json数据格式只有两种(对象,数组),可以设计出较为合适的返回值类型
{
"success": 布尔,//相应是否成功
"code": 数字,//响应码
"message": 字符串,//返回消息
"data": HashMap //返回数据,放在键值对里
}
在common创建子模块:common_utils
创建interface,定义返回状态码
例如成功:20001 失败20001
package com.lkw.commonutils;
public interface ResultCode {
public static Integer SUCCESS = 20000;//成功
public static Integer ERROR = 20001;//失败
}
统一返回结果类:R
package com.lkw.commonutils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class R {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String, Object> data = new HashMap<String, Object>();
//构造器私有化,使得不能new该对象
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;
}
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
<dependency>
<groupId>com.lkw.commonutilsgroupId>
<artifactId>common_utilsartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
修改Controller的返回结果
package com.lkw.eduservice.controller;
import com.lkw.commonutils.R;
import com.lkw.eduservice.entity.EduTeacher;
import com.lkw.eduservice.service.EduTeacherService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(value = "讲师管理功能")//注解还有其他输出功能,暂时不写
@RestController
@RequestMapping("/eduservice/teacher")
public class EduTeacherController {
//注入service
@Autowired
private EduTeacherService eduTeacherService;
//GetMapping,获取全部的教师列表
//http://localhost:8001/eduservice/teacher/findAll
@ApiOperation(value = "所有讲师列表")
@GetMapping("findAll")
public R findAllTeacher(){
//调用service的list,查询条件为null(查到的所有都返回)
List<EduTeacher> list=eduTeacherService.list(null);
return R.ok().data("items",list);
}
//http://localhost:8001/eduservice/teacher/delete/1
@ApiOperation(value = "逻辑删除讲师")
@DeleteMapping("{id}")
public R removeTeacher(@PathVariable String id){
boolean b = eduTeacherService.removeById(id);
if(b){
return R.ok();
}else {
return R.error();
}
}
}
配置mybatis的分页插件,在service_edu的config里
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false
pageInterceptor.setOverflow(false);
// 单页分页条数限制,默认无限制
pageInterceptor.setMaxLimit(500L);
// 设置数据库类型
pageInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
添加新方法:
//{current}/{limit}======当前页/最多限制页数
@ApiOperation(value = "教师列表分页" )
@GetMapping("pageTeacher/{current}/{limit}")
public R pageListTeacher(@PathVariable long current ,@PathVariable long limit){
//创建page对象
Page<EduTeacher> eduTeacherPage=new Page<>(current,limit);
//调用方法实现分页,会把得到的数据封装给eduTeacherPage,查询条件为null
eduTeacherService.page(eduTeacherPage,null);
//得到总记录数
long total = eduTeacherPage.getTotal();
//得到list数据集合
List<EduTeacher> records = eduTeacherPage.getRecords();
//返回的可以自己创建一个map集合,然后将两个对象合成一个对象,但是这里用的链式编程,两种结果都一样
return R.ok().data("total",total).data("rows",records);
}
返回总数量17,两个讲师数据
把条件封装成对象,再传递到接口,所以应该创建一个vo类,
DTO(Data Transfer Object)数据传输对象
VO (view object/value object)表示层对象
BO(bussines object)业务层对象//由PO继续包装成的对象
PO(persistent object)持久对象
DO(domain object)领域实体对象
在entity创建一个vo.TeacherQuery.java
package com.lkw.eduservice.entity.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class TeacherQuery {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "教师名称,模糊查询")
private String name;
@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;
@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
private String end;
}
编写controller
//4.条件查询分页方法
@ApiOperation(value = "条件查询分页方法")
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@PathVariable Long current,
@PathVariable Long limit,
@RequestBody(required = false) TeacherQuery teacherQuery) {
//创建page
Page<EduTeacher> pageCondition = new Page<>(current, limit);
//QueryWrapper,构建条件
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
//多条件组合查询,动态sql
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
//判断条件是否为空,拼接条件
if (!StringUtils.isEmpty(level)) {
wrapper.eq("level", level);
}
if (!StringUtils.isEmpty(name)) {
wrapper.like("name", name);
}
if (!StringUtils.isEmpty(begin)) {
wrapper.ge("gmt_create", begin);//大于等于
}
if (!StringUtils.isEmpty(end)) {
wrapper.le("gmt_create", end);//小于等于
}
wrapper.orderByDesc("gmt_create");
//调用方法,实现分页查询
eduTeacherService.page(pageCondition, wrapper);
long total = pageCondition.getTotal();//获取总记录数
List<EduTeacher> records = pageCondition.getRecords();//获取分页后的list集合
HashMap<String, Object> map = new HashMap<>();
map.put("total", total);
map.put("rows", records);
return R.ok().data(map);
}
@RequestMapping:请求映射,
用于映射(匹配?) url地址 与 类或方法,
一般放在Controller类上
@RequestBody:请求,
使用json接收传输,转化封装成对应对象
(后面的(required=false表示该值不是必须的,可以忽略))
@ResponseBody:响应,
java对象转json直接写入到HTTP响应正文,
一般放在Controller的方法上
单条件测试
符合的数据有3个,返回两条数据
全条件测试
在eduteacher类添加自动填充的注解,
@TableField(fill= FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
创建一个自动填充类
添加内容:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//fieldName不是字段名(gmt_create),而是类的属性名(gmtCreate)
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
添加接口方法
@ApiOperation("添加教师")
@PostMapping("addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher) {
//可能缺一点数据判断
boolean save = eduTeacherService.save(eduTeacher);
if (save) {
return R.ok();
} else
return R.error();
}
@ApiOperation("根据ID查询教师")
@GetMapping("getTeacher/{id}")
public R getTeacher(@PathVariable String id) {
EduTeacher eduTeacher = eduTeacherService.getById(id);
return R.ok().data("teacher", eduTeacher);
}
@ApiOperation("修改教师")
@PostMapping("updateTeacher")
public R updateTeacher(@RequestBody EduTeacher eduTeacher) {
boolean b = eduTeacherService.updateById(eduTeacher);
if (b) {
return R.ok();
} else
return R.error();
}
根据id查询讲师
id写2,时间删掉,其他改改
如果一直500,检查一下eduTeacher类的属性和字段是不是匹配的
创建两个类在service_base模块
package com.lkw.servicebase.exceptionhandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 自定义异常类
*/
@Data
@AllArgsConstructor //有参数构造器
@NoArgsConstructor //生成无参数构造
public class GuliException extends RuntimeException {
private Integer code;//状态码
private String msg;//输出消息
}
package com.lkw.servicebase.exceptionhandler;
import com.lkw.commonutils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//全局异常处理
@ExceptionHandler(Exception.class)
public R error(Exception e){
e.printStackTrace();
return R.error().message("执行全局异常处理");
}
//指定异常执行方法(算术异常)
@ExceptionHandler(ArithmeticException.class)
@ResponseBody//为了能够返回数据
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("方法执行ArithmeticException异常!");
}
//自定义的异常处理
@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
log.error(e.getMessage());
e.printStackTrace();
return R.error().code(e.getCode()).message(e.getMsg());
}
}
随便在一个controller里加int i=10/0;,来模拟算术异常
再在随便一个方法里模拟一个自定义异常:
/*
//模拟的自定义异常
try {
int i=10/0;
} catch (Exception e) {
throw new GuliException(20001,"执行了自定义异常");
}
*/
全局异常处理:
略
指定异常处理
自定义异常处理
初学用全局就行吧
详细异常处理待补全
日志级别
高 低
OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL
在yml里配置当前的控制台的日志级别:
logging:
level:
root: warn
默认是info级别
,一个类似于log4j的东西
配置时需要注释掉其他两个的日志配置
在resources下创建logback-spring.xml
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logbackcontextName>
<property name="log.path" value="D:/guli_log/edu"/>
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFOlevel>
filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}Pattern>
<charset>UTF-8charset>
encoder>
appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_info.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFOlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_warn.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warnlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_error.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERRORlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<springProfile name="dev">
<logger name="com.guli" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
root>
springProfile>
<springProfile name="pro">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="WARN_FILE"/>
root>
springProfile>
configuration>
在第十行改日志路径
有文件,有内容,搞定,
红色是因为没有拉近git
在GlobalExceptionHandler类添加注解:@Slf4j
补充上这个代码:
打上这个注解,用log.info()来代替System.out.println();更省性能(好像是自带多线程)
略
详细看我的主页里有关git上传的博客
创建远程仓库:
复制链接
输入链接
成功:
也可以在下面这写好README