背景

装修项目属于邻商小铺整体项目的一个模块,是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
  • 日期处理:joda-time
  • 使用lombok来让数据层变的精简

项目层次结构

源码目录:src/main/java
资源目录:src/main/resources
测试目录:src/test

项目包层结构,基于src/main/java,groupId:com.lensung.decorate
其中:Entity 为实体类,Function为功能模块 Modal 为模块

层包 解释 示例
~common 尽量使用commoncode包种的共享代码,本项目独有的代码放在此包
~.config 配置类和配置数据 Modal+Config
~.model 实体类包
~model.req 请求参数包 Entity+Req
~.model.entity 实体类 Entity 和数据库表一一对应
~model.dto 数据传输层类 Entity+DTO
~.model.vo 视图显示层类 Entity+VO
~.web.controller 控制层类,类的命名为 Entity+Controller
~web.interceptor 拦截层类,类的命名为 Function+Interceptor
~service 服务层接口,类的命名为 Entity+Service
~service.impl 存放服务层实现类 Entity+Service+Impl
~mapper 存放mybatis的mapper类 Entity+Mapper
~wxapi 微信的对接接口层 Model+Api
~wxapi.request 微信的请求参数 Model+Request
~wxapi.response 微信的响应结果 Model+Response

层级和数据交互

层级交互关系

装修架构搭建Step by Step - 图1

数据层和数据模型使用

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

层于层之间的数据交互以及调用关系 装修架构搭建Step by Step - 图2

公共代码和全局处理

公共包

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

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

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

装修架构搭建Step by Step - 图3

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

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

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

列如:

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

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

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

@ExceptionHandler(value = SystemException.class)
@ResponseBody
public ResultData systemExceptionHandler(HttpServletRequest request, SystemException e) {
    ResultData result = new ResultData();
    //系统自定义异常
    log.error(String.format("requestUrl ={%s},params ={%s}",
            request.getRequestURI(), getParams(request)));
    log.error(e.getMessage(), e);

    return result.error(e.getCode(), e.getErrMsg());
}

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

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

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

结果数据的封装和返回

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

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

How To Use

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

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

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

对接全局服务gateway

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

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

@Configuration
@ServletComponentScan
public class WebConfig implements WebMvcConfigurer {

    private JwtCookieInterceptor interceptor;

    public WebConfig(JwtCookieInterceptor interceptor) {
        this.interceptor = interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor)
            //添加需要验证登录用户操作权限的请求
            .addPathPatterns("/**")
            //排除不需要验证登录用户操作权限的请求
            .excludePathPatterns("/login","/template/findByAppId","/image/upload")
            .excludePathPatterns("/wechat/**")
            .excludePathPatterns("/css/**")
            .excludePathPatterns("/js/**")
            .excludePathPatterns("/images/**");
    }
}

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

模块的开发

定义数据结构

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

生成模板代码

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


编写和开发模块

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