引言
最近花了大概20天来完成了一个单体的前后端分离的项目,从8.25开始,到现在,也是挺心累的。整体的代码没致命的问题,但是也有些小bug没有解决。总体来说也算是一个小阶段性成果吧。
数据库建设
1 spring报错sql语法异常
确保表名、数据库名、字段名都不使用关键词
数据库名,表的表名,字段名都不允许使用mysql相关的关键词,否则会报sql语法异常的错误!
这个坑导致我前前后后找了3个小时,原因是把一个表名设成了order ,我们都知道,order 是mysql里面的关键字,因此当Mysql处理sql语句时把这个order 当作order by 来处理了!
具体的mysql关键字在这个网址里:mysql关键词查询
2 字段名数量
首先在设计整个项目的数据库时就要考虑清楚,哪些字段名是传参时候就必须的,哪些是使用DTO来扩展的,哪些是使用公共字段自动填充的,哪些是直接丢在Redis里面的或者在mongodb里面的,否则,当你发现前端传回来的值有多余或者缺少时是件非常麻烦的事情。
DTO解决简化了多次数据传输的冗余以及简化了数据库
3 公共字段自动处理
最新操作时间、最新操作人这类所有操作都会触发的字段,可以封装成一个全局公共字段自动处理类来使用(没错,就是aop),简化了业务层的开发
第三方中间件缓存的坑
当前端传回一个值时,我们在后端需要有相应的地址来接收,并且处理相应的参数值缓存到第三方中间件内(比如redis),而前端传回的值参差不齐,如果没有统一的前后端交互协议以及结果处理,那么很有可能导致后期代码扩展性差
我们需要一个共有的字段来处理同一个缓存结果,否则其它的更新/删除操作而产生的数据的更新将不及时(会按照cache的储存时长TTL来更新)
这个共有字段的问题困扰了我一个晚上,最终因前端页面写死,无法大刀阔斧地改动而罢休
通用处理类
1 线程副本
可以使用ThrealLocal线程副本来保存登录信息 但是这种方法在分布式中无法使用
2 自定义业务异常类处理
在全局异常里面使用
这样的好处是可以捕获运行时的异常进行相应的处理
3 Json序列化器
这个序列化器是用于保证json正确地将值在spring框架中传送的
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
this.registerModule(simpleModule);
}
}
4 前后端协议联调
设置一个R结果处理集,在R内的是T任意类型,以保证其通用性
@Data
public class R<T> {
private Integer code;
private String msg;
private T data;
private Map map = new HashMap();
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R<T> r = new R<T>();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
通用配置类
1 mybatisplus分页配置类
若要使插件生效,必须加上配置类
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
2 redis序列化配置类
为保证redis的key名能够被接收并调用,必须对key进行序列化,否则会出现/0x/6x 之类的代码(value不需要,当然,加上也没关系)
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
3 前端JS精度丢失问题
由于我们的数据库id是雪花算法生成的,因此使用Intenger已经不再能接收值(Long才可以),对于JS来说,更为致命(由于数字过长,JS会将后两位处理为0,即精度丢失)
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("创建转换器");
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(new JacksonObjectMapper());
converters.add(0, messageConverter);
}
}
4 拦截器
拦截器可以有多个,这里因为代码是在本地跑的,就只需要一个了
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
log.info("拦截到请求: {}", requestURI);
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/favicon.ico",
"front/**",
"common/**",
"/user/**"
};
boolean check = check(urls, requestURI);
if (check) {
log.info("放行{}", requestURI);
filterChain.doFilter(request, response);
return;
}
if (request.getSession().getAttribute("employee") != null) {
log.info("用户已登录,id为{}", request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request, response);
return;
}
if (request.getSession().getAttribute("user") != null) {
log.info("用户已登录,id为{}", request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request, response);
return;
}
log.info("用户未登录");
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
public boolean check(String[] urls, String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
return true;
}
}
return false;
}
}
其它
1 实体类
实体类必须序列化,以保证数据的安全性implements Serializable 再自定义一个UIDprivate static final long serialVersionUID = 1L;
如果遇到公共字段,需要使用@TableField 注解来使公共类自动装配生效
2 工具类
工具类也要注意需要加上@Component 否则无法被spring给装配到core容器内
3 事务
如果业务需要调用事务的,那么需要在业务层加上事务注解
并且在启动项里开启事务
SpringCloud版本问题
|