瑞吉外賣項目學習筆記(一)準備工作、員工登錄功能實現
文章目錄
- 3 項目組件優化
- 3.1 實現Swagger文檔輸出
- 3.2 實現logback日志打印
- 3.3 實現表單校驗功能
- 3.4 實現請求參數和響應參數的打印
3 項目組件優化
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文檔的調試功能中,可以直接進行測試:
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、操作員等信息收集起來存到數據庫,就可以實現常說的審計功能。
…
本節完,更多內容查閱:瑞吉外賣項目實戰