前言
生成器下載地址:http://www.zrxlh.top:8088/coreCode/.
源碼地址:https://gitee.com/zrxjava/codeMan
由于工作和生活等多方面的影響,半年多沒有更新博客,代碼生成器也沒有繼續(xù)維護,后來不斷有朋友發(fā)私信催我更新,并且提出相關(guān)的優(yōu)化建議,今天終于得空把代碼生成器根據(jù)朋友們的建議整體改良了一下,以后我也會盡量保持健康的更新速度,希望可以對大家有所幫助!
全局跨域配置
之前生成的代碼每一個controller都會加上@CrossOrigin,現(xiàn)在則采用了整體的跨域配置,前者可以靈活控制,后者更加簡單直接,但跨域一般都是為了前后端聯(lián)調(diào)方便,所以采用了后者,因為正式環(huán)境上的前后臺一般會使用nginx統(tǒng)一做轉(zhuǎn)發(fā)處理,把跨域的接口寫成調(diào)本域的接口,然后將這些接口轉(zhuǎn)發(fā)到真正的請求地址,也就不存在跨域的問題。
有一點需要注意(與跨域無關(guān)),現(xiàn)在新版goole瀏覽器加強了安全方面的監(jiān)控,主要是為了防止注入類攻擊,如果發(fā)現(xiàn)訪問應(yīng)用的地址和應(yīng)用的父級地址不同源,登錄時會無法設(shè)置cookie,如果使用session控制用戶的登錄,就會出現(xiàn)獲取不到seesion的問題,因為無法設(shè)置cookie,就沒有了seesionId,導(dǎo)致登錄無效。對此也有相應(yīng)的解決辦法:
1.打開Chrome設(shè)置,將chrome://flags/#same-site-by-default-cookies禁用,然后重啟瀏覽器即可;
2.采用token代替cokkie做驗證,也就是我們常用的使用redis保存token做驗證的方式。
跨域配置的代碼如下:
/**
* 跨域配置
*
* @author zrx
*/
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("token");
config.addAllowedHeader("Content-Type");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("OPTIONS");
config.setMaxAge(1000L * 60 * 60);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
//過濾前會在攔截器之前執(zhí)行,就不會被攔截器影響
bean.setOrder(0);
return bean;
}
}
添加控制登錄和日志記錄的注解
在實際的項目開發(fā)中,往往一個項目后臺有很多api,有些api需要登錄鑒權(quán),有些則不需要,為了靈活控制,采用注解的方式來對此進行控制,原理很簡單,在攔截器中獲取請求方法的注解信息,如果注解存在則進行登錄驗證,如不存在則直接放行,相關(guān)代碼如下:
/**
* 該注解用于REST API
* 如果一個API需要用戶用戶登錄,添加此注解
*
* @author zrx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface LoginRequired {
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
LoginRequired loginRequired = handlerMethod.getMethodAnnotation(LoginRequired.class);
if (null == loginRequired) {
return true;
}
// 預(yù)請求
if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
return true;
}
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter pw = response.getWriter();
pw.write("{\"code\":" + HttpServletResponse.SC_UNAUTHORIZED + ",\"status\":\"no\",\"msg\":\"無授權(quán)訪問,請先登錄\"}");
pw.flush();
pw.close();
return false;
}
}
return true;
}
}).addPathPatterns("/**").excludePathPatterns("/login", "/register", "/login/doLogin", "/user/register",
"/mystatic/**", "/druid/**", "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
}
使用方法也及其簡單,直接在controller的方法上添加此注解即可,例:
/**
* 查詢
*
* @return
*/
@ApiOperation(value = "查詢")
//添加此注解則表明調(diào)用此方法需要登錄方可調(diào)用
@LoginRequired
@PostMapping(value = "/select")
public List<TestTableEntity> select(@RequestBody TestTableEntity entity) {
return service.select(entity);
}
日志記錄注解與登錄注解同理,添加日志記錄注解可以實現(xiàn)方法級別的日志記錄,使用方法同上,代碼如下:
/**
* 需要記錄的日志的注解
* 在需要記錄日志的controller上添加該注解,可以記錄日志
*
* @author zrx
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordLog {
String value() default "";
}
@Aspect
@Component
public class LogAopAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);
@Around("@annotation(bootdemo.core.annotation.RecordLog)")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
Class<?> currentClass = pjp.getTarget().getClass();
MethodSignature signature = (MethodSignature) (pjp.getSignature());
String className = currentClass.getSimpleName();
String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
logger.info("======= 開始執(zhí)行:" + className + " — " + methodName + " ========");
Object obj = pjp.proceed();
logger.info("======= 執(zhí)行結(jié)束:" + className + " — " + methodName + " ========");
return obj;
}
}
響應(yīng)統(tǒng)一處理
之前代碼生成器的響應(yīng)已經(jīng)做了一層抽取,把返回的信息統(tǒng)一封裝到了對象當中,但沒有做到完全解耦,現(xiàn)在對響應(yīng)做了全局的進一步封裝,controller只需要書寫業(yè)務(wù)代碼,不需要再去new一個響應(yīng)體對象,進一步降低了耦合度,對于程序異常只需要throw相應(yīng)的異常即可,統(tǒng)一處理類會封裝異常信息給予前臺用戶提示,代碼如下:
@Slf4j
@ControllerAdvice
@ResponseBody
public class AllExceptionHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class clazz) {
return null == returnType.getMethodAnnotation(NoPack.class);
}
/**
* 響應(yīng)返回之前對響應(yīng)內(nèi)容進行包裝
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (MediaType.APPLICATION_JSON.equals(selectedContentType) || MediaType.APPLICATION_JSON_UTF8.equals(selectedContentType)) {
Method method = (Method) returnType.getExecutable();
if (ResponseEntity.class.equals(method.getReturnType())) {
return body;
}
if (null == body) {
return ResponseResult.success();
}
if (body instanceof ResponseResult) {
return body;
}
return ResponseResult.success(body);
}
return body;
}
....
/**
* 普通業(yè)務(wù)異常
*
* @param ex
* @return
*/
@ExceptionHandler(BusinessException.class)
public ResponseResult businessExceptionHandler(HttpServletResponse response, BusinessException ex) {
log.error(ex.getMessage(), ex);
response.setStatus(ResponseStatus.BUSINESS_EXCEPTION.hCode);
return ResponseResult.failed(ex.getCode(), ex.getMessage());
}
/**
* 其他錯誤
*
* @param ex
* @return
*/
@ExceptionHandler({Exception.class})
public ResponseResult exception(HttpServletResponse response, Exception ex) {
log.error(ResponseStatus.OTHER_EXCEPTION.valueLog, ex);
response.setStatus(ResponseStatus.OTHER_EXCEPTION.hCode);
return ResponseResult.failed(ResponseStatus.OTHER_EXCEPTION.bCode, ResponseStatus.OTHER_EXCEPTION.valueZh);
}
}
/**
* 請求響應(yīng)體
*
* @author zrx
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResponseResult implements Serializable {
private static final long serialVersionUID = 6041766238120354185L;
private int code;
private String status;
private String msg;
private Object data;
public static ResponseResult success() {
return success(null);
}
public static ResponseResult success(Object data) {
return ResponseResult.builder()
.status(ResponseStatus.SUCCESS.valueEn)
.msg(ResponseStatus.SUCCESS.valueZh)
.code(ResponseStatus.SUCCESS.bCode)
.data(data)
.build();
}
public static ResponseResult failed() {
return failed("失敗");
}
public static ResponseResult failed(String msg) {
return failed(ResponseStatus.FAILED.bCode, msg);
}
public static ResponseResult failed(int code, String msg) {
return ResponseResult.builder()
.status(ResponseStatus.FAILED.valueEn)
.msg(msg)
.code(code)
.build();
}
}
/**
* 該注解用于REST API
*
* 如果一個API的返回不需要被ResponseWrapper包裝,添加此注解
*
* @author zrx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface NoPack {
}
代碼如上所示,實現(xiàn)ResponseBodyAdvice接口中的beforeBodyWrite方法,即可對返回的內(nèi)容進行包裝,spring中無時無刻都在滲透著aop的核心思想。
如果不想包裝響應(yīng)內(nèi)容,則可以在controller的方法上添加NoPack注解來實現(xiàn),原理與上面提到的登錄注解一樣:實現(xiàn)ResponseBodyAdvice的supports方法,如果不存在NoPack注解,則對響應(yīng)內(nèi)容做包裝,spring已經(jīng)幫我們實現(xiàn)了整體的功能,我們只需要重寫方法加入相關(guān)業(yè)務(wù)即可。
swagger優(yōu)化
swagger官方的樣式比較丑,所以添加了新的swagger樣式依賴,樣式如下:
依賴如下:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.5</version>
</dependency>
其他更新
除了以上更新之外,生成器還升級了mysql的版本依賴至8.0.18,springboot版本至2.2.6.RELEASE,spring版本至5.2.5.RELEASE,添加了lombok支持,修復(fù)了一些已知的bug,下一步準備加入權(quán)限配置的相關(guān)代碼生成,進一步完善現(xiàn)有功能。
生成的代碼展示
結(jié)語
本次更新介紹到這里就結(jié)束了,其中響應(yīng)的統(tǒng)一處理個人認為還算是比較有意義的,也感謝大家提出的寶貴意見,工作忙,加上生活上的瑣事,更新可能不會太及時,還望見諒,下次再見啦!
生成器下載地址:http://www.zrxlh.top:8088/coreCode/.
源碼地址:https://gitee.com/zrxjava/codeMan