Springboot代碼生成器V2.21版本更新——升級相關(guān)依賴版本、加入全局響應(yīng)處理、使用注解控制登錄和日志記錄等

前言

生成器下載地址: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樣式依賴,樣式如下:


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)有功能。

生成的代碼展示

v2.21版
代碼展示

結(jié)語

本次更新介紹到這里就結(jié)束了,其中響應(yīng)的統(tǒng)一處理個人認為還算是比較有意義的,也感謝大家提出的寶貴意見,工作忙,加上生活上的瑣事,更新可能不會太及時,還望見諒,下次再見啦!

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容