瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现
3 项目组件优化
Swagger_6">3.1 实现Swagger文档输出
- 1)在
application.yml
中增加knife4j配置
spring:mvc:pathmatch:matching-strategy: ANT_PATH_MATCHER
knife4j:enable: truetitle: 瑞吉外卖group: ruiji_takeoutdescription: 瑞吉外卖version: 1.0name: itweidurl:email:base-package: com.itweid.takeout.controller
- 2)创建配置类
SwaggerProperties
类接收配置
@Data
@ConfigurationProperties(prefix = "knife4j")
public class SwaggerProperties {private String title = ""; //标题private String group = ""; //组名private String description = ""; //描述private String version = ""; //版本private String name = ""; // 联系人private String url = ""; // 联系人urlprivate String email = ""; // 联系人emailprivate String basePackage = ""; //swagger会解析的包路径private List<String> basePath = new ArrayList<>(); //swagger会解析的url规则private List<String> excludePath = new ArrayList<>(); //在basePath基础上需要排除的url// 如果没有填写组名,则直接用标题作为组名public String getGroup() {if (group == null || group.isEmpty()) {return title;}return group;}
}
- 3)创建自动配置类
SwaggerAutoConfiguration
进行初始化
@Configuration
@ConditionalOnProperty(name = "knife4j.enable", havingValue = "true", matchIfMissing = true)
@EnableSwagger2
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerAutoConfiguration implements BeanFactoryAware {@Autowiredprivate SwaggerProperties swaggerProperties;@Autowiredprivate BeanFactory beanFactory;@Bean@ConditionalOnMissingBeanpublic List<Docket> createRestApi(){ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;List<Docket> docketList = new LinkedList<>();ApiInfo apiInfo = new ApiInfoBuilder()// 页面标题.title(swaggerProperties.getTitle())// 创建人.contact(new Contact(swaggerProperties.getName(),swaggerProperties.getUrl(),swaggerProperties.getEmail()))// 版本号.version(swaggerProperties.getVersion())// 描述.description(swaggerProperties.getDescription()).build();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).groupName(swaggerProperties.getGroup()).select()// 为当前包路径.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage())).paths(PathSelectors.any()).build();configurableBeanFactory.registerSingleton(swaggerProperties.getGroup(), docket);docketList.add(docket);return docketList;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}
}
- 4)重新启动项目,在浏览器访问
http://localhost:8081/doc.html
,即可查看Swagger文档:
在Swagger文档的调试功能中,可以直接进行测试:
logback_111">3.2 实现logback日志打印
- 1)引入依赖
<!--logback-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.3</version>
</dependency>
-
2)在
resources
目录下创建配置文件 -
logback-base.xml
<?xml version="1.0" encoding="UTF-8"?>
<included><contextName>logback</contextName><!--name的值是变量的名称,value的值时变量定义的值定义变量后,可以使“${}”来使用变量--><property name="log.path" value="logs" /><!-- 彩色日志 --><!-- 彩色日志依赖的渲染类 --><conversionRuleconversionWord="clr"converterClass="org.springframework.boot.logging.logback.ColorConverter" /><conversionRuleconversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /><conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /><!-- 彩色日志格式 --><property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/><!--输出到控制台--><appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/Business.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></appender>
</included>
- logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--引入其他配置文件--><include resource="logback-base.xml" /><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。addtivity:是否向上级logger传递打印信息。默认是true。--><!--开发环境--><springProfile name="dev"><logger name="com.itweid.takeout" additivity="false" level="debug"><appender-ref ref="LOG_CONSOLE"/></logger></springProfile><!--生产环境--><springProfile name="pro"><logger name="com.itweid.takeout" additivity="false" level="info"><appender-ref ref="LOG_FILE"/></logger></springProfile><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 默认是DEBUG可以包含零个或多个元素,标识这个appender将会添加到这个logger。--><root level="info"><appender-ref ref="LOG_CONSOLE" /><appender-ref ref="LOG_FILE" /></root>
</configuration>
- 3)重新启动项目,可以看到根目录下生成了
logs
文件夹及日志文件:
3.3 实现表单校验功能
员工登录时,必须输入用户名和密码,虽然前端JS进行了校验,但对于后端来说,前端传来的数据是不可信的。
前端很容易获取到后端的接口,如果有人直接调用接口,就可能会出现非法数据,因此服务端也要数据校验。总的来说:
- 前端校验:主要是提高用户体验
- 后端校验:主要是保证数据安全可靠
Hibernate Validator框架可以以很优雅的方式实现参数的校验,让业务代码和校验逻辑分开,不再编写重复的校验逻辑。
更详细的用法可参考:后台管理系统的通用权限解决方案(五)SpringBoot整合hibernate-validator实现表单校验
- 1)首先,在
LoginForm
类中加入表单校验的注解,如字符串类型的参数则用@NotBlank
:
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("登录表单")
public class LoginForm {@ApiModelProperty("用户名")@NotBlank(message = "用户名不能为空")private String username;@ApiModelProperty("密码")@NotBlank(message = "密码不能为空")private String password;
}
- 2)在
EmployeeController
类中使用@Validated
注解开启校验功能:
- 3)需要特别注意的是,在2.3.0版本之前,
spring-boot-starter-web
是集成了validation检验的,但是在2.3.0开始就去掉了该依赖,所以根据实际版本决定是否添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 4)重启服务,发起登录请求,如果用户名为空,则会报错:
但此时前端提示不太友好(报400)。我们还要继续完善一下,对异常进行统一处理。
- 5)自定义一个
CustomException
异常类来统一处理已知的异常。未来在业务逻辑中,使用try...catch...
捕获异常后,再抛出一个CustomException
异常:
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class CustomException extends RuntimeException {private BaseResult result;/*** 指定一个是否追踪信息栈的异常*/public CustomException(BaseResult result, boolean writableStackTrace) {super(result.getMsg(), null, false, writableStackTrace);this.result = result;}/*** 指定一个不追踪信息栈的异常*/public CustomException(BaseResult result) {super(result.getMsg(), null, false, false);this.result = result;}/*** 指定一个不追踪栈信息的异常*/public CustomException(ErrorCode errorCode) {super(errorCode.getMsg(), null, false, false);this.result = BaseResult.error(errorCode);}
}
- 6)创建一个全局异常处理类
GlobalExceptionHandler
,对自定义异常和参数绑定异常进行统一处理:
@ControllerAdvice(annotations = { RestController.class, Controller.class })
@Slf4j
public class GlobalExceptionHandler {/*** 自定义异常的处理*/@ExceptionHandler(CustomException.class)@ResponseBodypublic BaseResult customExceptionHandler(CustomException customException) {log.error("捕获自定义异常:{}", customException.getResult().getMsg(), customException);return customException.getResult();}/*** 参数绑定异常的处理*/@ExceptionHandler({ConstraintViolationException.class, BindException.class})@ResponseBodypublic String validateException(Exception e, HttpServletRequest request) {log.error("捕获参数异常:{}", e.getMessage(), e);String msg = null;if (e instanceof ConstraintViolationException) {ConstraintViolationException constraintViolationException =(ConstraintViolationException) e;Set<ConstraintViolation<?>> violations =constraintViolationException.getConstraintViolations();ConstraintViolation<?> next = violations.iterator().next();msg = next.getMessage();} else if (e instanceof BindException) {BindException bindException = (BindException) e;msg = bindException.getBindingResult().getFieldError().getDefaultMessage();}log.error("参数异常信息:{}", msg);return msg;}}
- 7)重启服务,再次调用登录请求,当参数不符合要求时,则会返回更加友好的提示:
3.4 实现请求参数和响应参数的打印
页面的每个请求都有请求参数和响应参数,如果每个请求都单独打印这些参数,则显得非常冗余。
为此我们可以基于注解和切面编程,实现请求参数和响应参数的打印。
- 1)引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--hutool工具-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version>
</dependency>
- 2)创建切面类
OptLogAspect
,配置切入点拦截规则,拦截所有Controller方法
@Aspect
@Slf4j
public class OptLogAspect {/*** 定义Controller切入点拦截规则,拦截 @OptLog 注解的方法*/@Pointcut("execution(public * com.itweid.takeout.controller.*Controller.*(..))")public void optLogAspect() {}
}
- 3)在
OptLogAspect
的前置通知方法中,打印请求参数信息
/*** 前置通知*/
@Before(value = "optLogAspect()")
public void doBefore(JoinPoint joinPoint) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();// 请求参数Object[] args = joinPoint.getArgs();String strArgs = "";try {if (!request.getContentType().contains("multipart/form-data")) {strArgs = JSONUtil.toJsonStr(args);}} catch (Exception e) {try {strArgs = Arrays.toString(args);} catch (Exception ex) {log.warn("解析参数异常", ex);}}log.info("请求参数:{}", StrUtil.sub(strArgs, 0, 65535));
}
- 4)在成功返回通知方法和异常返回通知中,打印响应参数信息
/*** 成功返回通知*/
@AfterReturning(returning = "ret", pointcut = "optLogAspect()")
public void doAfterReturning(Object ret) {BaseResult baseResult = Convert.convert(BaseResult.class, ret);log.info("响应参数:{}", baseResult);
}/*** 异常返回通知*/
@AfterThrowing(throwing = "e", pointcut = "optLogAspect()")
public void doAfterThrowable(Throwable e) {log.info("响应异常:{}", getStackTrace(e));
}public static String getStackTrace(Throwable throwable) {StringWriter sw = new StringWriter();try (PrintWriter pw = new PrintWriter(sw)) {throwable.printStackTrace(pw);return sw.toString();}
}
- 5)在
WebMvcConfig
配置类中注册切面类为Bean
@Bean
public OptLogAspect optLogAspect() {return new OptLogAspect();
}
- 6)重启服务,测试登录功能
可见,请求参数和响应参数成功打印。后续还可以将请求IP、操作员等信息收集起来存到数据库,就可以实现常说的审计功能。
…
本节完,更多内容查阅:瑞吉外卖项目实战