spring 2019-03-27
台有必要重新搭建一个内部的开发框架,由于没有历史积累,直接使用SpringBoot作为基础框架,在此之上再做一个封装。
分享出来给大家参考,此框架适应于中小企业Java实现的WebAPI项目(前后端分离)。大家可以直接使用,但是最好还是理解并修改成适用自己团队的框架。
如上图,我们的框架包含很多库,其中d1.framework.webapi和d1.framework.cache是所有webapi项目都必须依赖的,其它的库是看需求选择依赖的。下面列出所有库的基本功能:
d1.framework.webapi: 业务项目依赖的基本库,包含springboot相关很多功能的封装,比如Application、Entity、Service、Controller等基类,还有swagger、跨域,权限统一认证,日志相关等等,后面单独说明。
d1.framework.cache: 缓存相关的库,因为d1.framework.webapi依赖这个库,所以所有业务项目也都依赖它,一个接口外加ehcache和redis的实现,实际业务项目中选择一种就可以。
d1.framework.storage: 不是必须依赖的库,一个接口外加本地文件存储和七牛云存储的实现。
d1.framework.util:不是必须依赖的库,实现一系列工具类,是在业务项目开发过程中逐渐积累的一些和业务无关的静态方法。
d1.framework.push:不是必须依赖的库,实现极光推送,这个是我们团队比较常用的app推送第三方库的封装。
d1.framework.sms:不是必须依赖的库,一个接口外加二种第三方的短信封装,主要用于注册等短信验证。
d1.framework.weixin:不是必须依赖的库,微信小程序、公众号、app等开发平台相关封装。
d1.framework.ocr: 不是必须依赖的库,封装了阿里的二个图形识别服务,也是实际业务项目用到了后封装的。
d1.framework.mqttclient:不是必须依赖的库,是对mqtt协议的实现,也是对一个开源库的封装。
springboot已经非常方便了,但是还是有必要把一些重复的不变的功能封装成框架,开发框架和开发业务应该分开。
框架里有一些我们团队特有的约定,并不一定适合所有人的习惯。
所有业务项目使用gradle工具构建,主要是觉得gradle比maven更简洁。
框架所有项目都上传到我们内部的Nexus
搭建的maven库,搭建的方法可以参考我以前发的贴。 业务项目通过gradle来配置依赖:
repositories {
mavenCentral()
maven{ url 'http://你的nexus库地址/repository/d1-java/'}
}
除d1.framework.webapi库强制依赖springboot以外,其它库都没有依赖springboot,这样其它库也可以很方便用于非springboot的业务项目
大部分库都是先定义接口,再对这个接口进行实现,通常有好几种实现。
很多第三方库已经很简单了,但是还是有必要做一次封装,主要还是第三方库功能很多,但是我们通常用到的很少,而且有很多缺省用法,封装后使用更简单,也能让第三方库版本变化后对现有业务项目也没影响。
每个库项目都由一个springboot业务项目作为测试库的入口以及一个库真正实现的module,这个module修改完后build成jar包并推送到nexus服务上。
所有业务项目都是在IDEA下开发,没有使用Eclipse。
框架是在业务项目不断的开发中逐渐积累和完善的,功能会越来越多,bug会越来越少。
源码都上传到github,下载后要跑起来还需修改:
搭建自己的nexus服务,或者改造业务项目生成jar包,然后使用本地依赖方式。
所有第三方对应的密钥信息都已经修改成一些随便的数字,确保安全性,
源码里除了框架库代码,还有一个d1project项目是用于使用框架的一个webapi模板项目。如果新建一个业务项目可以通过拷贝这个项目作为基础。
d1.framework.webapi 库是基础封装,每个 webapi 项目都必须使用,里面包含功能很多。
1. BaseApplication
所有业务项目的主入口 Application 类都需继承此类
@SpringBootApplication
public class DemoApplication extends BaseApplication{
}
这个类主要是增加几个缺省注解,比如激活 Swagger,确保对包进行扫描是从 d1 开始。
所以约定我们所有的业务项目的 package 都是 d1 开头,比如d1.project.xxxx@EnableSwagger2
@ComponentScan("d1")
@EntityScan("d1")
@EnableJpaRepositories("d1")
public class BaseApplication {
}
2. 封装 webapi 接口的返回 Result
所有 controller 返回的值都是一个 Result 对象,基本结构是
{
"code": 1,
"msg": "返回的消息,通常是字符串",
"data": "返回的数据,通常又是一个json对象"
}
使用方式:
return ResultUtil.result(10001,"自定义的消息",数据对象);
return ResultUtil.fail("自定义的消息",数据对象);
return ResultUtil.success("自定义的消息",数据对象);
3. ControllerAdvice
利用注解 @ControllerAdvice 实现统一拦截处理所有 controller 没有 catch 的错误
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public Result all(Exception e) {
return ResultUtil.result(ResultCode.UN_CATCH_ERROR.code, e.getMessage(), e);
}
4. 封装Swagger自动生成API文档
只需要在 application.properties 配置文件里添加以下几个配置,业务项目的 API 文档就可以自动生成并以服务的方式来访问,访问的url是http://域名或地址/swagger-ui.html#/
#application.properties
d1.framework.webapi.swagger.enable=true #生产环境下通常改成false
d1.framework.webapi.swagger.title=项目的标题
d1.framework.webapi.swagger.desc=项目的描述
d1.framework.webapi.swagger.version=项目API的版本
d1.framework.webapi.swagger.host=www.xxxx.com:8089
在 contorller 里使用注解标记 API 的方法参考文档4. 跨域设置
只需要在 application.properties 配置文件里添加以下配置,业务项目的前端页面可以实现跨域访问,当然生产环境的时候需要严格控制可跨域的域名。
#application.properties
#生成环境需要把*换成真实的域名,多个域名可以用逗号隔开
d1.framework.webapi.cors=*
5. BaseEntity
这是所有 Entity 的基类,主要是强行设置一个字段叫 Id,这个 Id 缺省是32位 uuid,在业务系统上建议所有 entity 都继承它。
6. User相关封装
DoUserBaseEntity 定义了用户表的常用字段,业务系统直接继承做一些扩展就可以了。
DoUserServiceImplBase 实现用户相关的 service,主要是创建 token,登录验证 token,缺省 token 都是用 d1.framework.cache 来缓存。
7. Auth相关
这里也是基于团队内部的一个约定,所有 webapi 接口如果需要验证权限,都需要在 http 请求的 header 里设置 Authorization 属性,属性的值有2种情况:
Authorization = token xxxxxxx 表示是通过用户登录后返回的 token 来验证
Authorization = sign xxxxxxx 表示服务间通过 HMAC 签名来校验
定义AuthFilter(继承Filter)来验证用户的请求Header里的Authoriztion对应的值如果是token的话,从d1.framework.cache里对应的缓存里查询token是否存在并是否没有过期。
另外通过定义一个自定义的注解Auth来设定web api接口是属于某种特定的用户。
@Auth("webadmin")
@RestController
@RequestMapping("/webadmin/user")
@Api(value = "/webadmin/user", description = "管理用户管理")
public class WebAdminUserController extends DoBaseController<WebAdminUser> {
......
}
@Auth("webadmin")标识这个controller里所有方法都必须是webadmin用户登录后才可以访问,这个注解也可以单独给特定方法使用8. DestroyEhcacheBean
JVM 退出时先 shutdown ehcache,确保内存里的 cache 内容正确写入本地文件
public class DestroyEhcacheBean implements DisposableBean, ExitCodeGenerator {
......
@Override
public void destroy() throws Exception {
if (cache != null)
cache.shutDown();
}
}
9. DoServiceImpBase
包含 service 常用方法,其它 service 都继承这个方法,里面主要包括基本的增删改查,这是一个 abstract 方法,最主要是需要子类继承的时候返回一个实际的 dao 类。
这个基类还实现了根据 token 查询对应的用户对象。
public abstract class DoServiceImpBase<T> {
......
protected abstract JpaRepository<T, String> getDao();
......
}
10. DoBaseController类
controller 基类,封装了最基本的增删改查,子类只需要继承,常见的接口都已定义。
11. 日志
在项目的resource下有一个 logback.xml,没有特殊的需求的话,所有业务系统都可以用这个默认的日志配置。
另外定义了一个 LoggerController 实现通过 webapi 接口查看日志和下载日志文件,这样远程就可以查看日志。
12. HMACSignService
添加HMAC校验签名的基类service, 实现了服务间HMAC接口验证方式,规则参考微信的服务校验
13. SignInRetryLimitService
封装用户登录重试次数校验,超过一定次数将被锁住不能再试了。
需要在application.properties里添加2个配置:
#登录密码错误重试的次数,没有这个值或值为0表示不限制
d1.framework.webapi.sign-in.retry-count=5
#登录密码错误重试到一定次数后,锁住用户一段时间,单位是分钟
d1.framework.webapi.sign-in.lock-period=60
使用这个服务通过3个函数:
if (user == null) throw new Exception("用户:" + username + "不存在");
if (retryService.verifyIsLocked(username)) throw new Exception("用户重试错误密码多次,导致用户被锁");
if (!user.getPassword().equals(password)) {
retryService.signInWithWrongPwd(username);
throw new Exception("密码不对");
}
retryService.signInSuccess(username);
以上是 d1.framework.webapi 的基本功能,很多功能都是在开发实际项目的过程中逐渐添加上去的,包含的功能是杂七杂八的需求。
其它库都是针对特定的功能,这里不一一详述了。