前言
生成器下載地址:http://www.zrxlh.top:8088/coreCode/.
源碼地址:https://gitee.com/zrxjava/codeMan
由于工作和生活等多方面的影響垢啼,半年多沒有更新博客私爷,代碼生成器也沒有繼續(xù)維護(hù),后來(lái)不斷有朋友發(fā)私信催我更新膊夹,并且提出相關(guān)的優(yōu)化建議衬浑,今天終于得空把代碼生成器根據(jù)朋友們的建議整體改良了一下,以后我也會(huì)盡量保持健康的更新速度放刨,希望可以對(duì)大家有所幫助工秩!
全局跨域配置
之前生成的代碼每一個(gè)controller都會(huì)加上@CrossOrigin,現(xiàn)在則采用了整體的跨域配置进统,前者可以靈活控制助币,后者更加簡(jiǎn)單直接,但跨域一般都是為了前后端聯(lián)調(diào)方便螟碎,所以采用了后者眉菱,因?yàn)檎江h(huán)境上的前后臺(tái)一般會(huì)使用nginx統(tǒng)一做轉(zhuǎn)發(fā)處理,把跨域的接口寫成調(diào)本域的接口掉分,然后將這些接口轉(zhuǎn)發(fā)到真正的請(qǐng)求地址俭缓,也就不存在跨域的問題。
有一點(diǎn)需要注意(與跨域無(wú)關(guān))酥郭,現(xiàn)在新版goole瀏覽器加強(qiáng)了安全方面的監(jiān)控华坦,主要是為了防止注入類攻擊,如果發(fā)現(xiàn)訪問應(yīng)用的地址和應(yīng)用的父級(jí)地址不同源不从,登錄時(shí)會(huì)無(wú)法設(shè)置cookie惜姐,如果使用session控制用戶的登錄,就會(huì)出現(xiàn)獲取不到seesion的問題椿息,因?yàn)闊o(wú)法設(shè)置cookie歹袁,就沒有了seesionId坷衍,導(dǎo)致登錄無(wú)效。對(duì)此也有相應(yīng)的解決辦法:
1.打開Chrome設(shè)置条舔,將chrome://flags/#same-site-by-default-cookies禁用枫耳,然后重啟瀏覽器即可;
2.采用token代替cokkie做驗(yàn)證逞刷,也就是我們常用的使用redis保存token做驗(yàn)證的方式嘉涌。
跨域配置的代碼如下:
/**
* 跨域配置
*
* @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));
//過濾前會(huì)在攔截器之前執(zhí)行,就不會(huì)被攔截器影響
bean.setOrder(0);
return bean;
}
}
添加控制登錄和日志記錄的注解
在實(shí)際的項(xiàng)目開發(fā)中夸浅,往往一個(gè)項(xiàng)目后臺(tái)有很多api仑最,有些api需要登錄鑒權(quán),有些則不需要帆喇,為了靈活控制警医,采用注解的方式來(lái)對(duì)此進(jìn)行控制,原理很簡(jiǎn)單坯钦,在攔截器中獲取請(qǐng)求方法的注解信息预皇,如果注解存在則進(jìn)行登錄驗(yàn)證,如不存在則直接放行婉刀,相關(guān)代碼如下:
/**
* 該注解用于REST API
* 如果一個(gè)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ù)請(qǐng)求
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\":\"無(wú)授權(quán)訪問,請(qǐng)先登錄\"}");
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/**");
}
使用方法也及其簡(jiǎn)單突颊,直接在controller的方法上添加此注解即可鲁豪,例:
/**
* 查詢
*
* @return
*/
@ApiOperation(value = "查詢")
//添加此注解則表明調(diào)用此方法需要登錄方可調(diào)用
@LoginRequired
@PostMapping(value = "/select")
public List<TestTableEntity> select(@RequestBody TestTableEntity entity) {
return service.select(entity);
}
日志記錄注解與登錄注解同理,添加日志記錄注解可以實(shí)現(xiàn)方法級(jí)別的日志記錄律秃,使用方法同上爬橡,代碼如下:
/**
* 需要記錄的日志的注解
* 在需要記錄日志的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)一封裝到了對(duì)象當(dāng)中糙申,但沒有做到完全解耦,現(xiàn)在對(duì)響應(yīng)做了全局的進(jìn)一步封裝船惨,controller只需要書寫業(yè)務(wù)代碼柜裸,不需要再去new一個(gè)響應(yīng)體對(duì)象,進(jìn)一步降低了耦合度掷漱,對(duì)于程序異常只需要throw相應(yīng)的異常即可粘室,統(tǒng)一處理類會(huì)封裝異常信息給予前臺(tái)用戶提示,代碼如下:
@Slf4j
@ControllerAdvice
@ResponseBody
public class AllExceptionHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class clazz) {
return null == returnType.getMethodAnnotation(NoPack.class);
}
/**
* 響應(yīng)返回之前對(duì)響應(yīng)內(nèi)容進(jìn)行包裝
*/
@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());
}
/**
* 其他錯(cuò)誤
*
* @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);
}
}
/**
* 請(qǐng)求響應(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
*
* 如果一個(gè)API的返回不需要被ResponseWrapper包裝卜范,添加此注解
*
* @author zrx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface NoPack {
}
代碼如上所示,實(shí)現(xiàn)ResponseBodyAdvice接口中的beforeBodyWrite方法鹿榜,即可對(duì)返回的內(nèi)容進(jìn)行包裝海雪,spring中無(wú)時(shí)無(wú)刻都在滲透著aop的核心思想锦爵。
如果不想包裝響應(yīng)內(nèi)容,則可以在controller的方法上添加NoPack注解來(lái)實(shí)現(xiàn)奥裸,原理與上面提到的登錄注解一樣:實(shí)現(xiàn)ResponseBodyAdvice的supports方法险掀,如果不存在NoPack注解,則對(duì)響應(yīng)內(nèi)容做包裝湾宙,spring已經(jīng)幫我們實(shí)現(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>
其他更新
除了以上更新之外,生成器還升級(jí)了mysql的版本依賴至8.0.18伟恶,springboot版本至2.2.6.RELEASE碴开,spring版本至5.2.5.RELEASE,添加了lombok支持博秫,修復(fù)了一些已知的bug潦牛,下一步準(zhǔn)備加入權(quán)限配置的相關(guān)代碼生成,進(jìn)一步完善現(xiàn)有功能挡育。
生成的代碼展示
結(jié)語(yǔ)
本次更新介紹到這里就結(jié)束了巴碗,其中響應(yīng)的統(tǒng)一處理個(gè)人認(rèn)為還算是比較有意義的,也感謝大家提出的寶貴意見即寒,工作忙橡淆,加上生活上的瑣事,更新可能不會(huì)太及時(shí)蒿叠,還望見諒明垢,下次再見啦!
生成器下載地址:http://www.zrxlh.top:8088/coreCode/.
源碼地址:https://gitee.com/zrxjava/codeMan