背景

装修项目属于邻商小铺整体项目的一个模块,是gateway项目的一个下游子项目;
从gateway种获取session和用户授权,并针对装修进行业务逻辑封装实现,服务于装修前端;

技术概览

java项目基于JDK1.8+
基于开发框架:spring boot 2.3.0+
上下游服务通讯:dubbo,通讯方式为:rpc
数据持久层选用:mybatise+mybatis-plus ,并使用mybatis-plus提供的代码生成
底层公共包:com.lensung.soft.common.commoncode-1.0.0
数据库使用:mysql5.6.0+ 数据规范
第三方SDK:

  • json处理使用:com.alibaba.fastjson
  • 日期处理:请使用jdk8的LocalDate和LocalDateTime
  • 使用lombok来让数据层变的精简

项目层次结构

源码目录:src/main/java
资源目录:src/main/resources
测试目录:src/test
groupId: com.lensung
artifactId: decorate

下设三大部分分别为:
environment : 开发环境,公共配置和依赖,全局代码以及和gateway上游服务的对接;
modular: 本项目的核心业务模块代码;
wxclient: 第三方依赖和接口的对接,这里主要是对接底层的微信接口,所以直接使用了wxclient;而没有使用sdk3dr;

下面基于项目包层结构,逐一展开其中:Entity 为实体类,Function为功能模块 Modal 为模块

src/main/java/com.lensung.decoration.environment 下的包层结构
层包 解释 示例
~.config 配置类和配置数据 Modal+Config
~.enums 全局枚举 Model+Enum
~.excepiton 全局异常错误以及全局异常错误处理 Model+Exception
~.interceptor 拦截层类,类的命名为 Function+Interceptor
~.utils 公共工具库,常用类库 Function+Util

src/main/java/com.lensung.decoration.modular 下的包层结构
层包 解释 示例
~.enums 存放业务枚举 Entity+Enum
~.mapper 存放mybatis的mapper类 Entity+Mapper
~.model 实体类包
~.service 服务层接口,类的命名为 Entity+Service
~.service.impl 存放服务层实现类 Entity+Service+Impl
~.web 控制层类,类的命名为 EntityController

其中model层是业务模型的核心,对model层进行了进一步的细分

src/main/java/com.lensung.decoration.modular.model 层的细分
层包 解释 示例
~.model.dto 数据传输层类 Entity+DTO
~.model.entity 实体类 Entity 和数据库表一一对应
~.model.req 请求参数包 Entity+Req
~.model.vo 视图显示层类 Entity+VO

src/main/java/com.lensung.decoration.wxclient 下的包层结构
层包 解释 示例
~.[modular] 微信的对接接口层 Model+Api
~.[modular].request 微信的请求参数 Model+Request
~.[modular].response 微信的响应结果 Model+Response

层级和数据交互

装修模块架构描述文档version2 - 图1

数据层和数据模型使用

  • Req:web层入参模型对象。
  • DO(Data Object) : 与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。即model
  • DTO(Data Transfer Object) : 数据传输对象,超过 2 个参数的查询封装,禁止使用map进行传递。
  • VO :显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

层于层之间的数据交互以及调用关系 装修模块架构描述文档version2 - 图2

公共代码和全局处理

公共包

外部接口或第三方平台client : 包括其它部门 RPC 开放接口,基础平台,公司其它的 HTTP 接口。
commom包下面包含了项目中共同约定的公共包,项目应该遵守约定,分别为:
exception:全局异常处理包下的异常类
result:结果返回类
util:常用工具包

下游服务对接上游服务获取session和授权

使用Interceptor对所有的前端请求进行拦截处理其中的cookie数据t,并解析出WShopJwtDto数据,并存放到Session中,进一步使用jwtDto数据获取用户的授权信息,进而进行下一步web请求处理;

装修模块架构描述文档version2 - 图3

异常的统一处理和内部处理逻辑

@ControllerAdvice 注解为统一异常处理的核心是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上该类中的设计思路:

  • 使用@ExceptionHandler注解捕获指定或自定义的异常;
  • 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
  • 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
  • 自定一个异常类,捕获针对项目或业务的异常;
  • 异常的对象信息补充到统一结果枚举中;

列如:

  1. @ControllerAdvice
  2. public class MyExceptionHandler {
  3. }

基于上面的提供的技术和设计方式,可以是项目中的异常进行统一的处理,所以要求项目中不要使用trycacth处理业务异常处理,而是直接进行 throw Exception,或者封装成已经定义的逻辑或者已知异常再抛出,将异常抛给框架进行统一处理;

How to use
比如我们可以将已知的错误封装成SystemException并进行抛给下面的一茶馆处理器进行返回

  1. @ExceptionHandler(value = SystemException.class)
  2. @ResponseBody
  3. public ResultData systemExceptionHandler(HttpServletRequest request, SystemException e) {
  4. ResultData result = new ResultData();
  5. //系统自定义异常
  6. log.error(String.format("requestUrl ={%s},params ={%s}",
  7. request.getRequestURI(), getParams(request)));
  8. log.error(e.getMessage(), e);
  9. return result.error(e.getCode(), e.getErrMsg());
  10. }

对于没有捕获的异常或者系统级别的异常,框架使用了Exception进行了全局捕获,不用担心存在异常不会被捕获的情况;

  1. @ExceptionHandler(value = Exception.class)
  2. @ResponseBody
  3. public ResultData exceptionHandler(HttpServletRequest request, Exception e) {
  4. ResultData result = new ResultData();
  5. //其他异常
  6. log.error(String.format("requestUrl ={%s},params ={%s},jwtDto={%s}",
  7. request.getRequestURI(), getParams(request), UserContext.getJwtDto()));
  8. log.error(e.getMessage(), e);
  9. return result.error(e);
  10. }

在模块开发过程中,可以在controller层或者service层,抛出自定义异常,而不需要trycatch或者进行错误逻辑处理,这样大大简化了开发的难道和提高了效率,也简化了业务逻辑的实现

结果数据的封装和返回

使用统一的结果类ResultData进行封装结果并进行返回;这样前端就可以获得结构一致的返回格式;目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式有利于前后端的交互与UI的展示;
使用统一和标准的状态码进行返回,状态码定义在ResultEnum并约定如下:

  1. // 状态码
  2. SUCCESS("请求成功", 0), // 正常
  3. ERROR("服务器内部错误", 500), // 服务器错误
  4. RELOGIN("重新登录", 401), // session过期
  5. FORBIDDEN("没有权限", 403),//表示用户没有权限
  6. PARAM_ERROR("参数错误", 5001),// 参数错误
  7. NON_EXISTENT("目标不存在", 5002),// 数据库没有
  8. WXAPI_ERROR("调用微信api发生错误", 5003), //调用微信api发生错误
  9. BUSINESS_ERROR("业务错误", 5004),//业务错误,
  10. APPID_GET_ERROR("获取appid失败", 5005);

How To Use

ResultData仅仅限于在web层使用,如controller和interceptor,用于结果返回给前端的统一格式;
使用范例:

  1. //controller层只需要返回正确的结果,异常和错误的处理通过抛出异常让全局异常处理模块进行处理
  2. @GetMapping('api/path')
  3. public ResultData api() {
  4. ResultData resultData = new ResultData();
  5. resultData.addData("list", collect);
  6. return resultData;
  7. }

对于错误的业务逻辑,直接抛出异常,或者不做任何处理也可;

对接全局服务gateway

decorate作为下游服务,不能够独立存在需要对接加入到整体服务中提供局部服务,即装修相关的功能模块服务;需要从gateway中获取session信息和授权信息;

服务之间的交互和通讯: 装修模块架构描述文档version2 - 图4 和gateway对接的关键,在一下的方法和类:JwtCookieInterceptor(核心拦截器)UserContext(用户上下文)和ContextNotSetException(对接时可能产生的错误)
首先在webconfig中将JwtCookieInterceptor拦截器配置到请求处理中,让拦截器起作用并多所有的请求进行拦截处理

  1. @Configuration
  2. @ServletComponentScan
  3. public class WebConfig implements WebMvcConfigurer {
  4. private JwtCookieInterceptor interceptor;
  5. public WebConfig(JwtCookieInterceptor interceptor) {
  6. this.interceptor = interceptor;
  7. }
  8. @Override
  9. public void addInterceptors(InterceptorRegistry registry) {
  10. registry.addInterceptor(interceptor)
  11. //添加需要验证登录用户操作权限的请求
  12. .addPathPatterns("/**")
  13. //排除不需要验证登录用户操作权限的请求
  14. .excludePathPatterns("/login","/template/findByAppId","/image/upload")
  15. .excludePathPatterns("/wechat/**")
  16. .excludePathPatterns("/css/**")
  17. .excludePathPatterns("/js/**")
  18. .excludePathPatterns("/images/**");
  19. }
  20. }

通过上面的配置就可以对接到gateway中,解析出gateway传来来的cookie,并使用相同的session,并通过解析出来的数据获得gateway持有的授权信息;到此就完成了gateway的对接;

模块的开发

定义数据结构

拿到需求和产品设计后,根据交互分析出需要的表以及表结构,显性字段和隐性字段,单表模块还是多表模块,以及变之间的结构,和实现逻辑;并定义数据存储结构;数据库和数据结构的设计应遵循统一的设计规范;

生成模板代码

使用 MyBatis-Plus的AutoGenerator快速生成代码,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。


编写和开发模块

基于需求和功能要求实现具体的接口功能,遵循开发规范;