第二天:
讲师管理模块(后端):
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插件进行自动生成
MybatisX导入edu_teacher
先在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;
}
}
config包
里面可以加分页,逻辑删除,性能测试之类的配置
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版本暂时不能配上
swagger3快速上手
丝袜哥的作用:获取项目的api,生成准确实时的接口文档
依赖:
<!--swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</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
其他的交给翻译,然后自己看吧
springcloud+swagger2
建立一个common子模块和service_base子子模块,为了所有模块都能用
引入common模块的依赖,删除common的src目录:
<dependencies>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided </scope>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</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.servicebase</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在EduApplication启动类添加组件扫描注解
@ComponentScan(basePackages={“com.lkw.*”})
http://localhost:8001/swagger-ui.html
springcloud+swagger3
直接改依赖和注解就行
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.commonutils</groupId>
<artifactId>common_utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</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)领域实体对象
创建一个vo类
在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,@RequestBody与@ResponseBody
@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();
}
根据id查询讲师,修改讲师
@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级别
Logback
,一个类似于log4j的东西
配置时需要注释掉其他两个的日志配置
在resources下创建logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="log.path" value="D:/guli_log/edu"/>
<!-- 彩色日志 -->
<!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<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">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
如果未设置此属性,那么当前logger将会继承上级的级别。
-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
-->
<!--开发环境:打印控制台-->
<springProfile name="dev">
<!--可以输出项目中的debug日志,包括mybatis的sql日志-->
<logger name="com.guli" level="INFO"/>
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
可以包含零个或多个appender元素。
-->
<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();更省性能(好像是自带多线程)
启动测试
略
添加到github
详细看我的主页里有关git上传的博客
创建远程仓库:
复制链接
输入链接
成功:
也可以在下面这写好README