摘要:
文章介绍了四个后端开发关键技术点:1) 自定义枚举类型转换器,通过实现ConverterFactory接口实现字符串到枚举类型的自动转换;2) JWT令牌拦截器实现用户认证,解析token并存储线程级用户信息;3) 定时任务使用cron表达式定期更新租约状态;4) 数据安全处理包括MD5加密敏感字段和MyBatis Plus字段查询忽略。这些技术点涵盖了前后端数据交互、系统安全认证、定时任务执行等常见业务场景,具有较高的实用价值。
一,补充类型转换(自定义转换器)
tips:众所周知,前后端的数据交互通常是以字符串类型进行的。这是因为HTTP协议是基于文本的,所以所有的数据都必须以字符串的形式传输。
当后端接收到一个枚举类的字符串数据,springMVC无法直接将字符串转化到对应的枚举对象,所以此时就需要我们自定义StringToBaseEnum转换器,要求所有枚举类都实现了BaseEnum接口,这样所有的枚举字符串数据可以直接转换到对应的枚举实例。
(1)实现ConverterFactory接口自定义转换器,实现Converter方法。
(2)利用反射得到所有枚举实例,然后循环比较数据中的code与所有枚举实例中的code,如果相同则返回那个枚举实例。
(3)注册自定义转换器:
public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(this.stringToBaseEnum); }
@Component
//所有枚举类都实现了BaseEnum接口,所以这里可以传入BaseEnum(精妙)
public class StringToBaseEnum implements ConverterFactory<String, BaseEnum> {
@Override
//T->具体的枚举类对象,Class<T> targetType->T的class类对象(反射)
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
return new Converter<String, T>() {
@Override
public T convert(String code) {
//通过反射得到所有枚举实例
T[] enumConstants = targetType.getEnumConstants();
//如果前端数据(1,2等)有在枚举类中,返回具体的枚举对象
for (T enumConstant : enumConstants) {
if(enumConstant.getCode().equals(Integer.valueOf(code))){
return enumConstant;
}
}
throw new IllegalArgumentException(code+"类型转换失败,code非法");
}
};
}
}
二,拦截器
该项目登录拦截使用的是Jwt令牌+token的方式来验证用户,token中存放了usrId和username信息,逻辑是每当有请求访问,取出请求头的token进行解析,如果解析中抛出异常就无法执行到最后的return而被拦截,解析成功就将token中的用户信息存入本地线程中,方便后续使用。
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("access-token");
Claims claims = JwtUtils.parseToken(token);
Long userId = claims.get("userId", Long.class);
String username = claims.get("username", String.class);
LoginUserHolder.setLoginUser(new LoginUser(userId, username));
//执行return说明解析成功,没有抛出异常
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清除线程变量中的用户信息
LoginUserHolder.clear();
}
}
三,定时任务
该项目中定时任务的作用是为了定时更新租约状态,每天0时0分会通过updateWrapper.le()自动对比租约最后日期与当前日期时间,如果已经过期就更新状态到已到期
tips:在启动类上添加注解@EnableScheduling开启定时任务,cron表达式中分别代表:秒,分 ,时 ,日 ,月 ,周(0-7),*表示每个对应时间段,如:每周每天.....
@Component
public class ScheduleTasks {
@Autowired
private LeaseAgreementService service;
//定时任务方法 ,cron表达式->秒 分 时 日 月 周(0-7)
@Scheduled(cron = "0 0 0 * * *")
public void checkLeaseStatus(){
LambdaUpdateWrapper<LeaseAgreement> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.le(LeaseAgreement::getLeaseEndDate, new Date())
.in(LeaseAgreement::getStatus, LeaseStatus.SIGNED, LeaseStatus.WITHDRAWING)
.set(LeaseAgreement::getStatus, LeaseStatus.EXPIRED);
service.update(updateWrapper);
}
四,数据加密和查询忽略
数据加密指对一些敏感字段,如:密码,利用单向函数进行加密(DigestUtils.md5Hex()函数);查询忽略其实也是对用户隐私的一种保障,通过mybatisplus自带的注解@TableField中的select=false使得所有通过查询忽略该字段。
@TableField(value = "password",select = false)
private String password;
public Result saveOrUpdate(@RequestBody SystemUser systemUser) {
//md5Hex单向函数”加密“
if(systemUser.getPassword() != null){
systemUser.setPassword(DigestUtils.md5Hex(systemUser.getPassword()));
}
//实体为空,默认更新语句中不包含该字段,更改需要改变更新策略->
//ignore, not_null, not_empty, never
service.saveOrUpdate(systemUser);
eturn Result.ok();
}