前言
生成器下载地址:http://www.zrxlh.top:8088/coreCode/.
源码地址:https://gitee.com/zrxjava/codeMan
由于工作和生活等多方面的影响,半年多没有更新博客,代码生成器也没有继续维护,后来不断有朋友发私信催我更新,并且提出相关的优化建议,今天终于得空把代码生成器根据朋友们的建议整体改良了一下,以后我也会尽量保持健康的更新速度,希望可以对大家有所帮助!
全局跨域配置
之前生成的代码每一个controller都会加上@CrossOrigin,现在则采用了整体的跨域配置,前者可以灵活控制,后者更加简单直接,但跨域一般都是为了前后端联调方便,所以采用了后者,因为正式环境上的前后台一般会使用nginx统一做转发处理,把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址,也就不存在跨域的问题。
有一点需要注意(与跨域无关),现在新版goole浏览器加强了安全方面的监控,主要是为了防止注入类攻击,如果发现访问应用的地址和应用的父级地址不同源,登录时会无法设置cookie,如果使用session控制用户的登录,就会出现获取不到seesion的问题,因为无法设置cookie,就没有了seesionId,导致登录无效。对此也有相应的解决办法:
1.打开Chrome设置,将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));
//过滤前会在拦截器之前执行,就不会被拦截器影响
bean.setOrder(0);
return bean;
}
}
添加控制登录和日志记录的注解
在实际的项目开发中,往往一个项目后台有很多api,有些api需要登录鉴权,有些则不需要,为了灵活控制,采用注解的方式来对此进行控制,原理很简单,在拦截器中获取请求方法的注解信息,如果注解存在则进行登录验证,如不存在则直接放行,相关代码如下:
/**
* 该注解用于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;
}
// 预请求
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\":\"无授权访问,请先登录\"}");
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 = "查询")
//添加此注解则表明调用此方法需要登录方可调用
@LoginRequired
@PostMapping(value = "/select")
public List<TestTableEntity> select(@RequestBody TestTableEntity entity) {
return service.select(entity);
}
日志记录注解与登录注解同理,添加日志记录注解可以实现方法级别的日志记录,使用方法同上,代码如下:
/**
* 需要记录的日志的注解
* 在需要记录日志的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("======= 开始执行:" + className + " — " + methodName + " ========");
Object obj = pjp.proceed();
logger.info("======= 执行结束:" + className + " — " + methodName + " ========");
return obj;
}
}
响应统一处理
之前代码生成器的响应已经做了一层抽取,把返回的信息统一封装到了对象当中,但没有做到完全解耦,现在对响应做了全局的进一步封装,controller只需要书写业务代码,不需要再去new一个响应体对象,进一步降低了耦合度,对于程序异常只需要throw相应的异常即可,统一处理类会封装异常信息给予前台用户提示,代码如下:
@Slf4j
@ControllerAdvice
@ResponseBody
public class AllExceptionHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class clazz) {
return null == returnType.getMethodAnnotation(NoPack.class);
}
/**
* 响应返回之前对响应内容进行包装
*/
@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;
}
....
/**
* 普通业务异常
*
* @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);
}
}
/**
* 请求响应体
*
* @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 {
}
代码如上所示,实现ResponseBodyAdvice接口中的beforeBodyWrite方法,即可对返回的内容进行包装,spring中无时无刻都在渗透着aop的核心思想。
如果不想包装响应内容,则可以在controller的方法上添加NoPack注解来实现,原理与上面提到的登录注解一样:实现ResponseBodyAdvice的supports方法,如果不存在NoPack注解,则对响应内容做包装,spring已经帮我们实现了整体的功能,我们只需要重写方法加入相关业务即可。
swagger优化
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支持,修复了一些已知的bug,下一步准备加入权限配置的相关代码生成,进一步完善现有功能。
生成的代码展示
结语
本次更新介绍到这里就结束了,其中响应的统一处理个人认为还算是比较有意义的,也感谢大家提出的宝贵意见,工作忙,加上生活上的琐事,更新可能不会太及时,还望见谅,下次再见啦!
生成器下载地址:http://www.zrxlh.top:8088/coreCode/.
源码地址:https://gitee.com/zrxjava/codeMan