新项目开发小点

最近开了一个新项目,过程中解决了一些小问题,随手记录一下。

统一异常处理

后台出错时返回一个统一的结果,并把错误信息传到前端。


Spring AOP统一异常处理

/**
 * 统一异常处理
 */
@ControllerAdvice
public class ExceptionAdvice {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public BaseResponse handleException(Exception e) {
        logger.warn("接口出现异常", e);

        // 参数不正异常
        if (e instanceof MissingServletRequestParameterException) {
            return new ResultResponse(ResultCode.ARGUMENT_ERROR, ResultCode.ARGUMENT_ERROR_MSG).setInfo(e.getMessage());
        }

        // 请求方法不支持异常
        if (e instanceof HttpRequestMethodNotSupportedException) {
            return new BaseResponse()
                    .setCode(ResultCode.METHOD_NOT_SUPPORTED)
                    .setMessage(ResultCode.METHOD_NOT_SUPPORTED_MSG);
        }

        return new ResultResponse()
                .setInfo(e.getMessage())
                .setCode(ResultCode.FAIL)
                .setMessage("系统错误");
    }
}

Spring MVC 返回对象处理

返回的结果要加密处理result字段,原理同样是AOP

首先定义个注解表示哪些Controller要加密:

/**
 * 加密 ResultResponse 的 result 字段
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptBody {
}

再定义一个注解表示哪些方法不加密:

/**
 * 不加密 body 的方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotEncryptBody {
}

然后是AOP处理类:


实现 ResponseBodyAdvice 接口


/**
 * 加密返回类型中的result字段
 */
@Component
//这个注解代表处理哪些 Controller,可以根据包名或注解控制,这里用了注解
@ControllerAdvice(annotations = EncryptBody.class)
// 接口上的泛型代表方法的返回类型,一般来说 后端接口会有一个统一的返回类型
public class ResponseAdvice implements ResponseBodyAdvice<BaseResponse> {
    private static final Logger logger = LoggerFactory.getLogger(ResponseAdvice.class);

    // 决定方法是否处理
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 这里的逻辑是被 NotEncryptBody 的方法不处理
        if (Arrays.stream(returnType.getMethodAnnotations())
                .anyMatch(s -> s.annotationType().equals(NotEncryptBody.class))) {
            return false;
        }

        return true;
    }

    @Override
    public BaseResponse beforeBodyWrite(BaseResponse body,
                                        MethodParameter returnType,
                                        MediaType selectedContentType,
                                        Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                        ServerHttpRequest request,
                                        ServerHttpResponse response) {
        if (body == null) {
            final String msg = "返回 body 为 null";
            logger.error(msg);
            return new ResultResponse()
                    .setInfo(msg)
                    .setCode(ResultCode.FAIL)
                    .setMessage(ResultCode.FAIL_MSG);
        }

        if (body instanceof ResultResponse) {
            ResultResponse theRes = (ResultResponse) body;

            Object result = theRes.getResult();

            if (result != null) {
                String toBeEncode = "";
                if (result instanceof String) {
                    toBeEncode = (String) result;
                } else {
                    toBeEncode = JSON.toJSONString(result, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteMapNullValue);
                }
                // 试用sm4加密返回类型中的result字段
                String encode = Sm4Util.encryptEcbDefault(toBeEncode);

                theRes.setResult(encode);

                return theRes;
            }
        }

        return body;
    }
}

Json

Json 时间格式化

spring 默认使用的是jackson依赖,时间类型会有时区问题,在yml中使用如下配置解决:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

如果使用fastjson,可以在实体类上标注如下注解:

@JSONField(format = "yyyy-MM-dd")
private Date inputTime;

如果需要统一修改,直接修改公共变量JSON.DEFFAULT_DATE_FORMAT

Json 格式化泛型

调用他人接口时,别人装数据的字段不一定是什么值,这时就需要泛型

NucResDto<NucDataDto> dto = JSON.parseObject(json, new TypeReference<NucResDto<NucDataDto>>(){});

springboot mybatis-plus 整合

我接手项目的时候,没有用mybatis-plus,我直接加了进来。

添加依赖,然后修改注入 SqlSessionFactory 的方法。

主要是把

final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

改为

final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();

同时设置主键策略(oracle数据库,主键试用序列):

// 设置 mybatis-plus 主键生成策略
GlobalConfig conf = new GlobalConfig();
List<IKeyGenerator> list = new ArrayList<>(1);
list.add(new OracleKeyGenerator());
conf.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerators(list));
sessionFactory.setGlobalConfig(conf);

如果不是手动注入的 SqlSessionFactory 直接按照mybatis-plus 官方文档整合即可。

PageHelper 分页插件整合

整合mybatis-plus之后,添加分页插件依赖时需要排除Mybatis:

<!-- 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>

然后yml中配置:

# 分页插件配置
pagehelper:
  helper-dialect: oracle
  pageSizeZero: true
  params: count=countSql
  # 分页合理化,设置为true时,当前页大于总页数时会返回最后一页数据,并不需要这样因此设置为false
  reasonable: false
  support-methods-arguments: true

yml 配置转 Bean

@Component
@ConfigurationProperties(prefix = "nucleate-config")
public class NucleateConfig {
}

此时IDEA会出现提醒(忘记什么提醒了)

去设置 -> Build, Exe... -> Compiler -> Annotation Processors 中 enable 一下就可以,这样yml中也不会报黄。

线程池工具类

有时候会在特定的调用点执行一些任务,就需要用线程池了。


线程池工具类


/**
 * 线程池工具类
 */
public class ThreadPoolUtil {

    private static ThreadPoolExecutor threadPool;

    /**
     * 无返回值直接执行
     *
     * @param runnable
     */
    public static void execute(Runnable runnable) {
        getThreadPool().execute(runnable);
    }

    /**
     * 返回值直接执行
     *
     * @param callable
     */
    public static <T> Future<T> submit(Callable<T> callable) {
        return getThreadPool().submit(callable);
    }

    /**
     * 关闭线程池
     */
    public static void shutdown() {
        getThreadPool().shutdown();
    }

    /**
     * dcs获取线程池
     *
     * @return 线程池对象
     */
    public static ThreadPoolExecutor getThreadPool() {
        if (threadPool != null) {
            return threadPool;
        } else {
            synchronized (ThreadPoolUtil.class) {
                if (threadPool == null) {
                    threadPool = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(64), new ThreadPoolExecutor.CallerRunsPolicy());
                }
                return threadPool;
            }
        }
    }
}

Nginx 部署前端项目404

location 修改为:

location / {
    root       /home/xxx/www;
    index      index.html index.htm index;
    try_files $uri $uri/ /index.html;
}

标签: Tips, 开发

添加新评论