作者:简单的土豆
www.jianshu.com/p/99fcead32d35
最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。
在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。
在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。
在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试。
项目地址&使用文档:
https://github.com/lihengming/spring-boot-api-project-seed
。
如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
最佳实践的项目结构、配置文件、精简的POM
注:
使用代码生成器生成代码后会创建model、dao、service、web等包。
/**
* 统一API响应结果封装
*/
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
//省略getter、setter方法
}
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误
public int code;
ResultCode(int code) {
this.code = code;
}
}
/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
public static Result genSuccessResult() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}
public static Result genSuccessResult(Object data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}
public static Result genFailResult(String message) {
return new 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());
} else if (e instanceof NoHandlerFoundException) {
result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
} else if (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);
return new ModelAndView();
}
});
}
常用基础方法抽象封装
public interface Service<T> {
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();//获取所有
}
提供代码生成器来生成基础代码
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);
}
}
...
}
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) {
return true;
} else