最近开了一个新项目,过程中解决了一些小问题,随手记录一下。
后台出错时返回一个统一的结果,并把错误信息传到前端。
/** * 统一异常处理 */ @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("系统错误"); } }
返回的结果要加密处理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处理类:
/** * 加密返回类型中的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; } }
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
调用他人接口时,别人装数据的字段不一定是什么值,这时就需要泛型
NucResDto<NucDataDto> dto = JSON.parseObject(json, new TypeReference<NucResDto<NucDataDto>>(){});
我接手项目的时候,没有用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 官方文档整合即可。
整合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
@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; } } } }