springboot项目目前设计的技术点

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表情,不然是创建不成功的)
博客链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容