关于初次使用SpringBoot的注意事项

引言

最近花了大概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解决简化了多次数据传输的冗余以及简化了数据库DTO解决简化了多次数据传输的冗余以及简化了数据库

3 公共字段自动处理

最新操作时间、最新操作人这类所有操作都会触发的字段,可以封装成一个全局公共字段自动处理类来使用(没错,就是aop),简化了业务层的开发
在这里插入图片描述

第三方中间件缓存的坑

当前端传回一个值时,我们在后端需要有相应的地址来接收,并且处理相应的参数值缓存到第三方中间件内(比如redis),而前端传回的值参差不齐,如果没有统一的前后端交互协议以及结果处理,那么很有可能导致后期代码扩展性差

在这里插入图片描述
我们需要一个共有的字段来处理同一个缓存结果,否则其它的更新/删除操作而产生的数据的更新将不及时(会按照cache的储存时长TTL来更新)

这个共有字段的问题困扰了我一个晚上,最终因前端页面写死,无法大刀阔斧地改动而罢休

通用处理类

1 线程副本

可以使用ThrealLocal线程副本来保存登录信息
在这里插入图片描述但是这种方法在分布式中无法使用

2 自定义业务异常类处理

在这里插入图片描述

在全局异常里面使用
在这里插入图片描述

这样的好处是可以捕获运行时的异常进行相应的处理

3 Json序列化器

这个序列化器是用于保证json正确地将值在spring框架中传送的

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
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> {

    /**
     * 状态码:1成功,0失败
     */
    private Integer code;

    /**
     * 错误信息
     */
    private String msg;

    /**
     * 数据
     */
    private T data;

    /**
     * 动态数据
     */
    private Map map = new HashMap();

    /**
     * 响应成功状态
     *
     * @param object
     * @param <T>
     * @return r
     */
    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    /**
     * 响应失败状态
     *
     * @param msg
     * @param <T>
     * @return r
     */
    public static <T> R<T> error(String msg) {
        R<T> r = new R<T>();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    /**
     * 增加动态数据
     * @param key
     * @param value
     * @return R
     */
    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }
}

通用配置类

1 mybatisplus分页配置类

在这里插入图片描述

若要使插件生效,必须加上配置类

@Configuration
public class MybatisPlusConfig {

    /**
     * 分页查询插件的配置类
     *
     * @return
     */
    @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
//springboot建这个项目的写这个mvc配置类要实现WebMvcConfigurer接口,而不是继承WebMvcConfigurationSupport
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 扩展mvc框架的消息转换器,可以兼容前端js与后端java的数据类型,处理精度丢失等问题
     *
     * @param converters
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("创建转换器");
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器内
        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;

        //1.获取本次请求的uri
        String requestURI = request.getRequestURI();

        log.info("拦截到请求: {}", requestURI);

        //2.定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/favicon.ico",
                "front/**",
                "common/**",
                "/user/**"
        };

        //3.判断本次请求是否需要处理
        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;
    }

    /**
     * 路径匹配,检查本次请求是否需要放行,匹配的上与否
     *
     * @param urls
     * @param requestURI
     * @return boolean
     */
    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版本问题

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值