# 账户中心框架介绍
### 改造目的:
* 降低代码耦合度
* 增加代码复用性,提高开发效率
* 重新规划定义模块及职责,以更快速地响应需求变更
* 优化调整部分业务实现,提高系统的稳定性与扩展性
## 技术架构调整
### 技术选型
* 公共
* 编译插件:Lombok 1.16.4
* 后端
* 核心框架:Spring Framework 4.0.3
* 视图框架:Spring MVC 4.0.3
* 服务端验证:Hibernate Validator 5.1.1
* 持久层框架:MyBatis 3.2.7
* 数据库连接池:Alibaba Druid 1.0.29
* 缓存框架:Ehcache 2.10.4、Redis
* 日志管理:Logback 1.1.3
* 测试框架:Spring Test 4.0.3、Junit 4.12、Dbunit 2.5.3
* 工具类:Apache Commons、Jackson 2.8.8、Dozer 5.5.1、Jedis 2.9
* RPC框架:Dubbo 2.5.3
* 前端
* JS框架:jQuery 1.12.1、zepto.js
* 模板框架:Velocity 1.7
* 平台
* 服务器中间件:JDK1.7、Servlet3.1 下开发,支持 Tomcat8+
* 数据库支持:MySql5.*
* 开发环境:JDK1.7、IDEA、Maven3.1、Git
### 框架结构介绍
* component
* lang
* parent
* module-base
* parent-module
* common
* common-service
* uic
* uic-portal
* uic-sdk
* uic-business
* uic-service
* uic-dao
* gic
* uic-portal
* uic-sdk
* uic-business
* uic-service
* uic-dao
* auth
* auth-portal
* auth-business
* auth-service
* auth-dao
### 包名规范
* lang : cn.com.servyou.uc.lang
* dao-base : cn.com.servyou.uc.dao.base
* service-base : cn.com.servyou.uc.service.base
* portal-base : cn.com.servyou.uc.portal.base
* common-dao : cn.com.servyou.uc.dao.common
* common-service : cn.com.servyou.uc.service.common
* uic-dao : cn.com.servyou.uc.dao.uic
* uic-service : cn.com.servyou.uc.service.uic
* uic-business : cn.com.servyou.uc.business.uic
* uic-sdk : cn.com.servyou.uc.portal.uic.dubbo.sdk
* uic-portal : cn.com.servyou.uc.portal.uic
* gic-dao : cn.com.servyou.uc.dao.gic
* gic-service : cn.com.servyou.uc.service.gic
* gic-business : cn.com.servyou.uc.business.gic
* gic-sdk : cn.com.servyou.uc.portal.dubbo.sdk
* gic-portal : cn.com.servyou.uc.portal.gic
* auth-dao : cn.com.servyou.uc.dao.auth
* auth-service : cn.com.servyou.uc.service.auth
* auth-business : cn.com.servyou.uc.business.auth
* auth-portal : cn.com.servyou.uc.portal.auth
### Spring使用规范
* 在 Portal 层以下如需要使用 Spring 则把定义放入本模块 resource 目录中的 * .business.xml 或者 * .service.xml 文件中
* 在 Portal 层需要使用 Spring 则把定义放入本模块 config 包中的 WebConfig 类中。
### Dubbo开发规范
* 在 module-sdk 模块的 facade.business 包中定义 Dubbo 接口及配套的 Stub 类(用于完成客户端数据校验,减少服务端访问压力)
* 在 module-sdk 模块的 vo.business 包中定义具体接口的返回对象,该对象必须继承 cn.com.servyou.uc.portal.dubbo.sdk.base.BaseResponse 类
* 在 module-sdk 模块的 vo.business 包中定义具体接口有可能返回的错误码接口,该接口必须继承cn.com.servyou.uc.portal.dubbo.sdk.base.ResultMessage
* 在 module-sdk 模块的 base.enumeration 包中定义接口有可能用到的枚举类型,完成客户端数据校验的时候有可能会使用到
* 服务端接口配置规范,每个具体接口暴露给客户端调用前需要根据业务需要,定义以下几个配置
* version : 接口的版本号,
* stub : 接口对应的 stub 类路径
* retries : 接口重试次数(除非业务需要,默认建议设置为0)
* timeout : 接口访问超时时间
* loadbalance :重试算法,当需要重试时,该参数定义才有意义
* actives : 每服务消费者每服务每方法最大并发调用数
* executes : 服务提供者每服务每方法最大可并行执行请求数
* delay : 延迟注册服务时间(毫秒) ,设为-1时,表示延迟到Spring容器初始化完成时暴露服务,推荐设置为-1
* 异常处理 见异常使用规范章节
### Restful开发规范
### 公共组件介绍
* cn.com.servyou.uc.lang.enumeration : 定义整个 UC 系统会在POJO中用到的枚举类型,集中管理系统枚举
* cn.com.servyou.uc.lang.exception : 定义整个 UC 系统中各个子系统间传递的异常类型
* cn.com.servyou.uc.lang.format : 定义整个 UC 系统中会用到的格式化工具类,其中包含 JSON 的格式化类
* cn.com.servyou.uc.lang.reflect : 定义整个 UC 系统中会用到的基于反射的工具类,其中包含对 Dozer 进行封装的工具类
* cn.com.servyou.uc.lang.stereotype : 定义整个 UC 系统中会用到的自定义注解
* cn.com.servyou.uc.lang.test : 存放了 UC 系统最顶级的测试基类
* cn.com.servyou.uc.lang.utils : 定义 UC 系统各个模块都有可能会用到的工具类,区别于各个顶级基础模块中定义的层级工具类,一个 Scope 是整个 Project 另外的 Scope 是具体的逻辑 Module 层,公用程度不一。
* cn.com.servyou.uc.lang.validator : 定义 Hibernate Validator 校验的具体实现类,此包将由指定的 Service Owner 维护
### 业务开发规范
**逻辑层划分如下:**
* portal 业务入口层
* portal-sdk 对外提供的 RPC 访问接口
* business 业务服务层
* service 原子服务层
* dao 数据持久化层
**有以下几条约定的跨层访问规则:**
* portal-sdk 没有 parent module
* portal-sdk 对内只会被对应的 portal 层依赖
* business 层有统一的 parent 为 parent-service
* business 只会被对应的 portal 层依赖
* business 可以夸工程调用其他工程的 service 层 (存在待讨论的议题)
* service 层有统一的 parent 为 parent-service
* service 可以被对应的 business 层依赖,也可以被对应的 portal 层依赖
* dao 层有统一的 parent 为 parent-dao
* dao 只会被对应的 service 层依赖
* 以上涉及的依赖访问全部是基于接口依赖
**以下有几条程序开发约定**
* portal 层定义的 Controller 需要统一继承 BaseController, 详见 Demo
* portal 层与业务相关的 VO 定义放在 http.facade.* 包中,具体规则如下:
* 每个业务接口包含 Request、Response、Message 的定义
* Request 中需要加上规则校验,通过 Hibernate Validator 注解实现
* Response 统一继承 cn.com.servyou.uc.portal.http.base.BaseResponse 详见 Demo
* business 层需要放入 Spring 容器的类需要加上 @BusinessService 注解,详见 Demo
* service 层需要放入 Spring 容器的类需要加上 @Service 注解,详见 Demo
* dao 层需要被 MyBatis 扫描到的接口类需要加上 @MyBatisDao 注解,详见 Demo
* 所有 Redis 缓存都定义在 cn.com.servyou.baseservice.dao.base.cache.Cache 中
* Redis 缓存的工具类是 cn.com.servyou.baseservice.dao.base.cache.CacheUtil
### 异常使用规范
异常处理每个系统都会有自己的一套异常产生和异常处理的机制,UC 也不例外,有以下几条约定:
* 除非有特殊的需要,一般建议只抛出非检查型异常,以减少工程中的样板式代码,可以用 Exceptions 类中的 unchecked 方法把检查型异常转为非检查型异常,详见 Demo
* dao 层使用 Spring 提供的非检查型 DataAccessException 异常,框架会把 MyBatis 异常转换为 Spring 异常
* service 层允许抛出的异常只能为 ServiceException , 详见 Demo
* business 层允许抛出的异常只能为 BusinessException , 详见 Demo
* portal 层允许抛出的异常只能为 ConstraintViolationException , 详见 Demo
* 框架会在 portal 层捕获 ServiceException , BusinessException , ConstraintViolationException 详见 Demo
### 单元测试规范
以下几个关键环节必须被单元测试覆盖:
* dao 层暴露给 service 层的每个方法,详见 Demo
* service 层暴露给 business 或 portal 层的每个方法,详见 Demo
* business 层暴露给 portal 层的每个方法,详见 Demo
* portal 层暴露给外部调用的每个接口 ( 包括 HTTP 和 Dubbo 接口 ),详见 Demo
各层单元测试的编写规范
* dao 层单元测试类需要继承 BaseDaoTests 类,测试类以 Test 结尾
* service、business 层单元测试类需要继承 BaseServiceTest 类,测试类以 ServiceTest、BusinessTest 结尾
* portal 层 HTTP 服务的单元测试类需要继承 BaseHttpTest 类,测试类以 ControllerTest 结尾
* portal 层 Dubbo 服务的单元测试类以 FacadeTest 结尾
### 小企业日志框架引入
未完待续
### 小企业密钥机制引入
未完待续
## 功能点方案变更
本次改造对于功能方面主要包含以下几方面的优化调整:
* 持久化方案调整
* 打包生产切面代码时把页面模板替换成静态页面,发布到生产资源服务器中
### 持久化方案调整
#### 现状
为了提高数据读写的速度,目前 UC 中很多对象是放在 Redis 中,没有持久化到数据库中,随着业务的发展,服务的使用者越来越多,内存将会不断地升高。目前生产环境是购买阿里云的 Redis 云服务,目前的方案会造成业务运行一段时间后就要面临一次生产服务扩容的问题。先看一下有哪些对象以什么样的形式存放到了 Redis 中。
* String
* LIST
* HASH
* 企业 token 缓存 , key 格式为:“ct:UUID” , hashkey 格式为:{"companySessionInfo", "companyToken", "appId"}
* accessToken 缓存 , key 格式为: “UUID” , hashkey 格式为:{"sessionInfo", "refreshToken", "isHigh", "ssoToken"}
* refreshToken 缓存 , key 格式为:“UUID” , hashkey 格式为:{"sessionInfo", "accessToken", "ssoToken"}
* ssoToken 缓存 , key 格式为:“UUID”? , hashkey 格式为:{"accessTokenSet", "refreshTokenSet", "sessionInfo", "isHigh", "weixinToken"}
* code 缓存 , key 格式为:“UUID” , hashkey 格式为:{"ssoToken", "appId"}
* session 缓存 , key 格式为:“UUID” , hashkey 格式为:{"lastAccessedTime", "creationTime", "maxInactiveInterval", "sessionAttr:#Session.attributes.id}"
* token 缓存 ( Dubbo 暴露的服务 ) , key 格式为:未知 , hashkey 格式为:{ 未知 } //TODO 是否存在调整必要?
* SET
* 账户缓存 , key 为 "account_token_set_#Session.id" , values 为 accessToken
* ZSET
其他已经持久化到数据库中的对象
* String
* LIST
* HASH
* rank_dto_domain? 缓存 , key 格式为:“rank_dto_domain” , hashkey 格式为:{#DictData.code}
* industry_category_domain? 缓存 , key 格式为:“industry_category_domain” , hashkey 格式为:{#DictData.code}
* state_tax_bureau_code_domain? 缓存 , key 格式为:“state_tax_bureau_code_domain” , hashkey 格式为:{#TaxBureauCode.taxBureauCode} //TODO 此 POJO 设计是否有问题?
* local_tax_bureau_code_domain? 缓存 , key 格式为:“state_tax_bureau_code_domain” , hashkey 格式为:{#TaxBureauCode.taxBureauCode} //TODO 此 POJO 设计是否有问题,而且存放的内容和 state_tax_bureau_code_domain 一模一样?
* company_nsrsbh_id_domain? 缓存 , key 格式为:“company_nsrsbh_id_domain” , hashkey 格式为:{nsrsbh}
* company_dssh_id_domain? 缓存 , key 格式为:“company_dssh_id_domain” , hashkey 格式为:{nsrsbh}
* company_shxydm_domain? 缓存 , key 格式为:“company_shxydm_domain” , hashkey 格式为:{nsrsbh}
* company_dto_domain? 缓存 , key 格式为:“company_dto_domain” , hashkey 格式为:{#CompanyDto.id}
* nsrsbh_to_group_id_domain? 缓存 , key 格式为:“nsrsbh_to_group_id_domain” , hashkey 格式为:{nsrsbh}
* 标签缓存 , key 格式为:“t:#Account.id” , hashkey 格式为:{#AccountTag.tagCode}
* SET
* ZSET
#### 改造目标
把未曾持久化到数据库的数据全部持久化到数据库中,减少系统对 Redis 的依赖。优化现有的 Redis 中对象的存储结构、有效期及读写方式。目的是保持高性能的前提下减少 Redis 的内存占用量
#### 改造步骤
为了实现上述改造目标,从以下几方面着手进行改造
* 把 token、session、account 与 accessToken 的关联关系持久化到数据库中
* 替换原先的唯一键生成规则,从原先的 16 位减少为以后的 8 位
* 调整缓存中对象的存储结构(具体规则待讨论) //TODO
* 调整缓存中对象对象的生命周期(具体规则待讨论)//TODO
### 鉴权中心实现静态资源分离
#### 现状
单点登陆项目中的 View 层是用 Velocity Template 实现,当服务端发生一些未知异常时会导致用户界面显示 500+ 错误,用户体验不好
#### 改造目标
即使服务端发生故障也不会影响页面显示,只会在点击某个页面按钮时给出我们预先设计好的相对友好的提示,引导用户进行下一步操作或等待服务恢复
#### 改造步骤
* 编写 Template 2 Html 的转换工具类及转换服务
* 在用生产切面打包 HTTP 项目时自动执行转换操作
### 工作量评估
依赖技术框架的确定,未完待续
----
#### 待讨论的议题
* 内部讨论
* 文档中的 TODO 点
* 开放讨论
* business 可以夸工程调用其他工程的 service 层,将采用哪种方式调用?
* 内部RPC服务 & 外部RPC服务如何隔离?
* 对外提供 REST API 的标准?
* 小企业的日志框架如何引入
* 小企业的安全密钥机制如何引入