一个基于 Spring Boot 的项目骨架

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约


每日英文

You can't just sit there and wait for life to come to you. You have to go get it.

你不能无所事事的坐等人生带给你一切,你必须得自己努力争取。


每日掏心话

生活总会给你另一个机会,这个机会叫明天。生活没有过去,也没有曾经,不管什么事只要过去了,就会慢慢忘掉。

作者:简单的土豆 | 责编:乐乐

来自:ImportNew

一个基于 Spring Boot 的项目骨架

程序员小乐(ID:study_tech)第 844 次推文 图片来自百度


往日回顾:手把手教你如何搭建一款自己的私有百度网盘?


正文


最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。


在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。


然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。


在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试。

项目地址&使用文档:https://github.com/lihengming/spring-boot-api-project-seed 。

如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。

特征&提供


最佳实践的项目结构、配置文件、精简的POM


一个基于 Spring Boot 的项目骨架


注:使用代码生成器生成代码后会创建model、dao、service、web等包。


统一响应结果封装及生成工具


/**
* 统一API响应结果封装
*/
publicclass Result {
privateint code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
returnthis;
}
//省略getter、setter方法
}


/**
* 响应码枚举,参考HTTP状态码的语义
*/
publicenum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误



publicint code;

ResultCode(int code) {
this.code = code;
}
}
/**
* 响应结果生成工具
*/
publicclass ResultGenerator {
privatestaticfinal String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
returnnew Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
returnnew Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
returnnew Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}


统一异常处理


public void configureHandlerExceptionResolvers(List exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
Result result = new Result();
if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());


logger.info(e.getMessage());
} elseif (e instanceof NoHandlerFoundException) {
result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
} elseif (e instanceof ServletException) {
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
} else {
result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
logger.error(message, e);
}
responseResult(response, result);
returnnew ModelAndView();
}

});
}


常用基础方法抽象封装
publicinterface Service {
void save(T model);//持久化
void save(List models);//批量持久化
void deleteById(Integer id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
List findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”


List findByCondition(Condition condition);//根据条件查找
List findAll();//获取所有
}


提供代码生成器来生成基础代码
publicabstractclass CodeGenerator {
...
public static void main(String[] args) {
genCode("输入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根据需求生成,不需要的注掉,模板有问题的话可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}


CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName)方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。


由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。


提供简单的接口签名认证
public void addInterceptors(InterceptorRegistry registry) {
//接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //开发环境忽略签名认证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证签名
boolean pass = validateSign(request);
if (pass) {
returntrue;
} else {
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
responseResult(response, result);
returnfalse;
}
}
});
}
}


/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {


String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
returnfalse;
}
List keys = new ArrayList(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'

String secret = "Potato";//密钥,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5

return StringUtils.equals(sign, requestSign);//比较
}


集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL
使用Druid Spring Boot Starter 集成Druid数据库连接池与监控
使用FastJsonHttpMessageConverter,提高JSON序列化速度

技术选型&文档


Spring Boot:https://www.jianshu.com/p/1a9fd8936bd8

MyBatis:http://www.mybatis.org/mybatis-3/zh/index.html

MyBatisb通用Mapper插件:https://mapperhelper.github.io/docs/


MyBatis PageHelper分页插件:https://pagehelper.github.io/

Druid Spring Boot Starter:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter/

Fastjson:https://github.com/Alibaba/fastjson/wiki/%E9%A6%96%E9%A1%B5


欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。


猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

5万字长文!SpringBoot 操作 ElasticSearch 详解

关于 MyBatis 我总结了 10 种通用的写法

分布式事务之 RocketMQ 事务消息详解

关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?


分享到:


相關文章: