前言
最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。
在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。
然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。
在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试。
如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
特征&提供
最佳实践的项目结构、配置文件、精简的POM
注:使用代码生成器生成代码后会创建model、dao、service、web等包。
统一响应结果封装及生成工具
<code>
public
class
Result
{private
int code;private
String message;private
Objectdata
;public
Result setCode(ResultCode resultCode) {this
.code = resultCode.code;return
this
; } }/<code><code>
public
enum
200
), FAIL(400
), UNAUTHORIZED(401
), NOT_FOUND(404
), INTERNAL_SERVER_ERROR(500
);public
int
code; ResultCode(int
code) {this
.code = code; } }/<code><code>
public
class
ResultGenerator
{private
static
final
String DEFAULT_SUCCESS_MESSAGE ="SUCCESS"
;public
static
ResultgenSuccessResult
()
{return
new
Result() .setCode(ResultCode.SUCCESS) .setMessage(DEFAULT_SUCCESS_MESSAGE); }public
static
ResultgenSuccessResult
(Object data)
{return
new
Result() .setCode(ResultCode.SUCCESS) .setMessage(DEFAULT_SUCCESS_MESSAGE) .setData(data); }public
static
ResultgenFailResult
(String message)
{return
new
Result() .setCode(ResultCode.FAIL) .setMessage(message); } }/<code>统一异常处理
<code>
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
(einstanceof
ServiceException) { result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); logger.info(e.getMessage()); }else
if
instanceof
NoHandlerFoundException) { result.setCode(ResultCode.NOT_FOUND).setMessage("接口 ["
+ request.getRequestURI() +"] 不存在"
); }else
if
(einstanceof
ServletException) { result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); }else
{ result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 ["
+ request.getRequestURI() +"] 内部错误,请联系管理员"
);String
message;if
(handlerinstanceof
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);return
new
ModelAndView(); } }); }/<code>常用基础方法抽象封装
<code>
public
interface
Service
<T
> {void
save
(T model)
;void
save
(List models)
;void
deleteById
(Integer id)
;void
deleteByIds
(String ids)
;void
update
(T model)
;T
findById
(Integer id)
;T
findBy
(String fieldName, Object value)
throws
TooManyResultsException;List
findByIds
(String ids)
;List
findByCondition
(Condition condition)
;List
findAll
()
; }/<code>提供代码生成器来生成基础代码
<code>
public
abstract
class
CodeGenerator
{ ...public
static
void
main
(String[] args
) { genCode("输入表名"
public
static
void
genCode
(String... tableNames
) {for
(String tableName : tableNames) { genModelAndMapper(tableName); genService(tableName); genController(tableName); } } ... }/<code>CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName)方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。
由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。
提供简单的接口签名认证
<code>
public
void
addInterceptors
(InterceptorRegistry registry)
{if
(!"dev"
.equals(env)) { registry.addInterceptor(new
HandlerInterceptorAdapter() {public
boolean
preHandle
(HttpServletRequest request, HttpServletResponse response, Object handler)
throws
Exception {boolean
pass = validateSign(request);if
(pass) {return
true
; }else
{ logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}"
, request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap())); Result result =new
Result(); result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败"
); responseResult(response, result);return
false
; } } }); } }/<code><code>
private
boolean
validateSign(HttpServletRequest request) {String
requestSign = request.getParameter("sign"
);if
(StringUtils.isEmpty(requestSign)) {return
false
; } List<String
> keys =new
ArrayList<String
>(request.getParameterMap().keySet()); keys.remove("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);return
StringUtils.equals(sign, requestSign); }/<code>集成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
后言
感谢大家的支持,没想到一个简单的项目总结分享在短短两天获得这么多人的关注,还上了GitHub Trending榜单,有点受宠若惊哈,话说现在国内技术社区氛围真是越来越好了,希望大家有时间的话都能参与到开源分享的行列来,分享知识,快乐编码,共勉。