开发中的踩坑事项
接口相关
1、参数校验
使用 JSR330 提供的参数校验方式
<!--引入validation的场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
这里需要注意:需要添加对应的版本信息
检验相关注解
@Null 校验对象是否为null
@NotBlank 校验字符串去头尾空格后的长度是否大于0或是否为null
@NotEmpty 校验字符串是否为null或是否为empty
@Size(min,max) 校验数组/集合/字符串长度是否在范围之内
@Email 校验字符串是否符合电子邮箱格式
@URL(protocol,host,port) 校验字符串是否符合URL地址格式
@Pattern(regexp) 校验字符串是否符合正则表达式的规则
将注解添加到所属的 class 的对应字段上,随后在接口添加 @Validated 注解
这里需要注意,是否需要对参数的长度进行校验
基于责任链模式实现参数校验请参考 12306 项目
基于 fluent-api 实现参数校验
2、请求方式
Get: 获取资源
Post:提交资源,修改资源
DELETE: 删除资源
PUT: 修改资源
Get:方法天然具有幂等性,当然要防止外部攻击的情况,使用 Get 请求时,需要通过 @RequestParam 注解对参数名称进行指定
Post:在传递参数过多的时候,可以考虑使用 @RequestBody 进行统一封装
3、BaseController
public abstract class BaseController {
protected abstract String setBasePath();
protected <T> Result<T> success(T data) {
return success(data, null);
}
protected <T> Result<T> success(T data , String msg) {
return Results.success(data);
}
protected <T> Result<T> fail(String msg) {
return Results.fail(msg);
}
protected HttpSession getHttpSession(HttpServletRequest req) {
return req.getSession();
}
}
4、返回 Result 类需要添加一个 isSuccess 方法
public boolean isSuccess() {
return SUCCESS_CODE.equals(code);
}
5、对接口进行权限验证,结合 Spring Security 配置权限路径前缀
6、重要接口需要考虑做限流处理
接口限流,接口需要考虑做限流 | 短信接口,短信接口、邮件接口、认证接口 (这样的接口需要考虑做限流)
AOP + 自定义注解 + LUA 去考虑做限流,本地 Guava 去做限流,分布式的 Redission 、Gateway 网关、Sentinel
7、接口幂等
幂等这个概念数学里面的 f(f(x)) = f(x) 那么我们就叫这个函数是幂等的
1、第一个如果不保证幂等性,可能会导致我们的资源重复消耗 ()
给大家一个提示幂等实现有很多种方式:但是现在一般都考虑使用美团的 GTIS 去做幂等设计
RestAPI 、MQ | 表单(参数),Token,SPEL 大家就这三种
8、接口兼容
old 老的接口,和新的接口| 尤其是对于开源产品来说你新的接口需要去做老的接口的兼容
去加一个方法过期的注解 @Link 新的方法
9、接口熔断
保险丝的作用就是保证,你的系统内部的系统安全性
10、对于一些远程的接口比如支付宝提供的接口、短信接口
需要去考虑引入重试机制,easy-retry、spring-retry | (得考虑时间,调用次数)
11、参数名不要轻易去做修改
12、接口需要考虑做防刷机制
13、对于一些查询的接口,是不是考虑封装一个
BaseQueryDTO ,startNo , pageSize, 是否有下一页,当前页
业务相关
1、日志打印
日志的作用是啥 : 排查错误,保留证据,业务解耦,监控
不要去打印重复的日志
日志级别 error warn debug trace
第级别的日志需要去检查配置文件种的日志级别
2、代码复用
比如说,通过模板设计模式去封装统一的模板
方法 dto -> po UserDTO -> new User().set().set();
BeanCopy , 或者说 convert , mapstruct 通过这些手段
需要去考虑对于通过的 service 进行封装
3、try catch finally
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
try catch 我们都知道是用户处理异常的
catch 基于业务考虑是不是应该去捕获具体的 exception Exception
catch 里面可能会设计到业务处理,或者说 throw new RuntionException();
对于一些资源类的操作使用 try with resouce 的方式 IO input,output
finally 肯定是会被执行的,那么是不是可以放一些释放资源,锁操作的代码
事务方法,如果去手动 catch 异常可能会导致你的事务失效
4、错误码
错误码通常来说都有对应的业务标识 ID A \ B \ C \ D \ E 5 位异常码,A0001 A0002 A0003, 是不是就得设计到异常信息
错误码与异常信息
考虑使用接口保存常量
5、事务处理
1、spring 事务 : ACID 原子性、一致性、隔离性、持久性 | ACD 好像是手段 -》 一致性 (好像是在周志明大佬的凤凰架构里面说的)
2、事务在业务种需要注意的东西
3、什么时候使用声明式事务,什么时候使用编程式事务 ? 知识星球里面有答案 !!!这是重点
声明式、与编程式的差异性,以及他们的优劣 ? ???
spring cache | @Lock ????
4、@Transactional 默认捕获的是 RuntimeException,所以可能需要手动改为 Exception
5、@Transactional 什么时候设置成 Exception 、Throwable ???
如果: Exception 一般在没有远程方法调用,或者 PRC 的情况下使用
否则: Throwable
6、如果你使用了声明式事务的方式,而且你业务里面涉及到与其他的 MQ、Redis 的交互的话,那么可以考虑使用声明式,然后在事务 commit 之后,进行 MQ , Redis 的相关操作
7、声明式事务与编程式事务的区别,作用域 (前则大,后则可以手动进行控制)
6、参数判空
NullpointException
如果是前端传过来的数据:那么你默认当他是不可靠的,因为你数据可能会被别人进行拦截,然后进行修改
1、所以后端在获取数据的时候,都需要对数据进行校验 XSS 工具啊 ,SQL 注入啊
2、记住,获取的手一定需要先进行判断
3、在开发的时候,可能有一些数据他必填,有一些数据他不用填 ,比如说你去做查询的时候名称、类别、颜色
获取的时候需要进行判空
4、使用统一的工具类 hutool ColUtil, ObjectUtil , MapUtil,StrUtil
5、然后的话神器, Optional 去做数据包装,我们是什么数据都需要通过 Optional 包装一下在去拿 ? 我认为不是,我认为只有我们不确定的数据才去对他进行包装 mark -> 一等座、二等座、三等做 | Optional.ofNullable(mark).orElse() | throw() | , 或者说对于一些远程方法调用你也可以通过 Optional 进行包装
7、泛型的使用
1、泛型的使用 | 首先我们明确,JAVA 的泛型真的是我们看见的那样 ? 实际上不是的 List
LIst.class
2、泛型的作用:可以为我们提供动态的策略,apply(int n) 只能接受 int 的数据 , 那如果你写个
3、第三个怎么去获取参数泛型 ParameterizedTypeImpl 我们可以去获取对象的泛型
4、如果在父类种获取子类的泛型
参考 mybatis-plus ???