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格式
时间这块用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里面注意返回插入主键,以保证返回插入主键
- 多参数取值要注意用@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 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表情,不然是创建不成功的)
博客链接