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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容