Springboot代码生成器V2.21版本更新——升级相关依赖版本、加入全局响应处理、使用注解控制登录和日志记录等

前言

生成器下载地址: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样式依赖,样式如下:
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,下一步准备加入权限配置的相关代码生成,进一步完善现有功能。

生成的代码展示

v2.21版

代码展示

结语

本次更新介绍到这里就结束了,其中响应的统一处理个人认为还算是比较有意义的,也感谢大家提出的宝贵意见,工作忙,加上生活上的琐事,更新可能不会太及时,还望见谅,下次再见啦!

生成器下载地址:http://www.zrxlh.top:8088/coreCode/.
源码地址:https://gitee.com/zrxjava/codeMan

猜你喜欢

转载自blog.csdn.net/m0_37719874/article/details/112427728