开发Tips
新项目开发小点
最近开了一个新项目,过程中解决了一些小问题,随手记录一下。
统一异常处理
后台出错时返回一个统一的结果,并把错误信息传到前端。
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;
}