springboot项目目前设计的技术点

作者: 简单coder | 来源:发表于2018-10-31 14:27 被阅读331次

    maven依赖

    • gson格式化json使用
    • freemarker模板引擎渲染
    • common-langs工具包使用
    • druid数据库连接池使用
    • mybatis-pagehelper使用
    • mybatis-generator逆向pojo,mapper,dao功能使用
    • mybatis-plugin mapper和xml文件对应插件使用\
    • mybatis使用(早期使用jpa,后改成mybatis注解,最后改为使用mybatis-xml配置)
    • redis缓存使用(这块暂时未涉及实际功能,已尝试过测试demo)

    项目配置方面

    • 使用正测试环境分离


    • log日志文件化



      这块解决了我在线上遇到的很多bug,还是非常的有用的

    • mybatis逆向pojo,dao,mapper层


    • 线上脚本自动发布

    项目模块(从dao层往前翻)

    数据库这块(这些都是我踩过的坑)


    文章部分这里使用text格式存储,这里要注意一点,text在逆向时会生成blob格式,这不是我们想要的varchar格式,我们得指定转成varchar格式

    <table tableName="t_article" domainObjectName="Article" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
                <columnOverride column="content" jdbcType="VARCHAR" />
            </table>
    

    时间这块用datetime,创建有默认时间,更新随时间更新更新

    CREATE TABLE `t_article` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `title` varchar(200) DEFAULT NULL COMMENT '标题',
      `titlePic` varchar(100) DEFAULT NULL COMMENT '标题图片',
      `content` text COMMENT '主题内容',
      `author_id` int(11) DEFAULT NULL COMMENT '用户id',
      `type` varchar(16) DEFAULT NULL COMMENT '类型',
      `status` int(1) DEFAULT NULL COMMENT '状态:0-未发布,1-已发布,2-已撤销',
      `category_id` int(11) DEFAULT NULL COMMENT '类型id',
      `category_name` varchar(20) DEFAULT NULL COMMENT '类型名称',
      `allow_comment` tinyint(1) DEFAULT '0' COMMENT '允许评论:0-允许,1-不允许',
      `like_count` int(11) DEFAULT '0' COMMENT '点赞数',
      `collect_count` int(11) DEFAULT '0' COMMENT '收藏数',
      `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    

    评论

    评论这款我加了个ip字段,因为评论不需要登录,为了防止有人狂刷接口,这里我用ip禁止,每个ip不能重复发表评论

    CREATE TABLE `t_comment` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `address_ip` varchar(30) NOT NULL COMMENT '评论ip,防刷接口',
      `article_id` int(11) NOT NULL COMMENT '文章id',
      `parent_id` int(11) DEFAULT NULL COMMENT '父评论',
      `content` varchar(50) NOT NULL COMMENT '评论内容',
      `person_id` int(11) DEFAULT NULL COMMENT '评论人id',
      `status` int(2) DEFAULT '0' COMMENT '评论状态:0-正常,1-删除',
      `like_count` int(11) DEFAULT '0' COMMENT '点赞数',
      `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `name` varchar(50) DEFAULT NULL COMMENT '昵称',
      `icon` varchar(200) DEFAULT 'https://upload.jianshu.io/users/upload_avatars/7290998/f64f5ef0-def0-4b26-beb3-b9d88f060ba0.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240' COMMENT '头像',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
    

    t_log这块,之前我想把错误数据打到数据库内,后来懒得做了,直接本地化存储.........其实要做也是可以的,我做了errorlog处理,这个后面再谈

    数据库总结:
    第一次设计数据库,真的很没有经验,我基本上是边写边改字段,然后边重新生成mapper,xml,pojo,这里我希望给没有那么多经验的人一个警示:设计一个表的时候,一定要考虑好状态这个点,这个表对应的实体有什么状态呢,比如上架,下架,审核未审核,删除未删除,这些都要有状态!

    DAO层:

    • mapper里面注意返回插入主键,以保证返回插入主键
    <insert id="insert" parameterType="com.ipaozha.crm.pojo.Category" useGeneratedKeys="true" keyColumn="category_id"
              keyProperty="categoryId">
    
    • 多参数取值要注意用@Param键,取值用#{}
    User login(@Param("username") String username,@Param("password") String md5Password);
    

    DAO层总结:
    这块儿没什么好说,sql多写写即可

    Service层:

    • 不要在for循环里面做select操作,这种是很纯的做法,比如一组文章记录中for循环去查对应的分类id对应的分类名,这种幂次操作很傻
    • 注意库存概念,这是我之前做商城项目时遇到的,要注意下单前,库存余量,撤销订单时要注意库存余量等
    • 在执行sql把可能遇到的error都给处理掉,不要什么都不想就去做sql操作,对sql的操作都进行try-catch,然后抛出我们的CrmException等,然后做统一返回,并且得把这个error落地到日志去处理,这里我写的比较粗糙,大致意思就是这样
    //这里还需要当前用户的信息
            User user = CrmUtils.getLoginUser(request);
            if (null == user) {
                throw new CrmAuthException();
            }
            try {
                //取值
                Article article = new Article();
                BeanUtils.copyProperties(articleForm, article);
                article.setContent(articleForm.getText());
                article.setAuthorId(user.getId());
    
                int result = articleMapper.insertSelective(article);
                if (result > 0) {
                    return article;
                } else {
                    return null;
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new CrmException(RespEnum.article_insert_error);
            }
    
    } catch (Exception e) {
           e.printStackTrace();
           throw new CrmException(450, e.getMessage());
    }
    
    • 避免横向越权,对数据的操作一定要验证当前用户是不是这个文章的用户等,这个是基础,不要随便来个接口请求就能把文章给删了!!!!!

    service总结:
    这块真的很重要,很多很多的逻辑都是这块儿处理的,希望大家做的时候多思考

    controller层

    • 对api处理和对url处理分层,可以做层继承什么的(这些都是基础,相信大家都这么做的)


    • 可以用@Reqparam去取参,也可以用抽出一层form层取参,我这里为了好调用BeanUtils.copyProperties(articleForm, article);这个方法多了层form层处理,lombok不错,希望没用过得人都去用用
    import lombok.Data;
    
    import javax.validation.constraints.NotNull;
    
    @Data
    public class CommentForm {
    
        @NotNull(message = "文章id不能为空")
        private Integer articleId;
    
        @NotNull(message = "昵称不能为空")
        private String name;
    
        @NotNull(message = "评论内容不能为空")
        private String content;
    
        private Integer parentId;
    
        private Integer personId;
    
        private String addressIp;
    
    }
    
    • 路径匹配简单用法
    @RequestMapping("/article/{id}")
        public String article(@PathVariable("id") Integer id, Map<String, Object> map) {
    

    controller总结
    注意结构清晰即可

    exception处理

    @Data
    public class CrmException extends Exception{
    
        private Integer code;
    
        public CrmException(RespEnum respEnum) {
            super(respEnum.getMsg());
            this.code = respEnum.getCode();
        }
    
        public CrmException(Integer code, String msg) {
            super(msg);
            this.code = code;
        }
    }
    
    @ControllerAdvice
    public class MyExceptionHandler {
    
        @ExceptionHandler(CrmException.class)
        @ResponseBody
        public Resp handleException(CrmException exception) {
            return Resp.error(exception.getCode(), exception.getMessage());
        }
    
        @ExceptionHandler(CrmAuthException.class)
        public String hanleAuthException(CrmAuthException exception) {
            return "/admin/login";
        }
    
    }
    

    spring已经为我们做好了一切,只需稍微配置即可

    拦截器处理

    @Slf4j
    @Component
    public class AdminInterceptor implements HandlerInterceptor {
    
        private  String baseImg = "/upload";
    
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
            String uri = request.getRequestURI();
            // 这里传过去basePath的一些参数
            request.getSession().setAttribute("base", "");
            request.getSession().setAttribute("baseImg", baseImg);
    
    //        log.info(String.valueOf(request.getSession().getMaxInactiveInterval()));
            log.info("UserAgent: {}", request.getHeader("user-agent"));
            log.info("用户访问地址: {}, 来路地址: {}", uri, IPKit.getIpAddrByRequest(request));
            //请求拦截处理
            User user = CrmUtils.getLoginUser(request);
            if (uri.startsWith("/admin") && !uri.startsWith("/admin/login") && null == user
                    && !uri.startsWith("/admin/css") && !uri.startsWith("/admin/images")
                    && !uri.startsWith("/admin/js")) {
                response.sendRedirect(request.getContextPath() + "/admin/login");
                return false;
            }
    
            //先在这里存一下user
            request.setAttribute("user", user);
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    
        }
    }
    

    @Component
    public class WebMVCConfig implements WebMvcConfigurer {

    @Autowired
    private AdminInterceptor adminInterceptor;
    
    
    @Value("${web.upload}")
    private String rootPath;
    
    /**
     * 不需要登录拦截的url:登录注册
     */
    final String[] notLoginInterceptPaths = {"/upload/**","/admin/login","/admin/regist"};
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(adminInterceptor).addPathPatterns("/**").excludePathPatterns("/upload/**","/admin/login","/admin/regist","/error");
    
    }
    
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //这里是用来处理upload文件的文件地址访问,因为上传的图片等在项目外
        registry.addResourceHandler("/upload/**").addResourceLocations("file:"+rootPath + "/");
    }
    

    }

    项目目录:


    其实目前我的boot项目弊端已经体现出来了,controller,service,dao层太过厚重了,一个功能可能要多很多个文件,项目也越来越厚,但是没办法,目前我这个是单模块web项目,后续我会再起一个项目(或者直接在这个项目上修改),直接越过maven模块(因为这块已经做过),向spring cloud前进!

    这篇博客我后面也会同步到我搭建的博客网站,欢迎大家比较,指正!(本博客暂未开通emoji表情,希望大家文章内别带emoji表情,不然是创建不成功的)
    博客链接

    相关文章

      网友评论

        本文标题:springboot项目目前设计的技术点

        本文链接:https://www.haomeiwen.com/subject/xkpwtqtx.html