背景
装修项目属于邻商小铺整体项目的一个模块,是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 |
层级和数据交互
层级交互关系
数据层和数据模型使用
- Req:web层入参模型对象。
- DO(Data Object) : 与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。即model
- DTO(Data Transfer Object) : 数据传输对象,超过 2 个参数的查询封装,禁止使用map进行传递。
- VO :显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
层于层之间的数据交互以及调用关系
公共代码和全局处理
公共包
外部接口或第三方平台client : 包括其它部门 RPC 开放接口,基础平台,公司其它的 HTTP 接口。
commom包下面包含了项目中共同约定的公共包,项目应该遵守约定,分别为:
exception:全局异常处理包下的异常类
result:结果返回类
util:常用工具包
下游服务对接上游服务获取session和授权
使用Interceptor对所有的前端请求进行拦截处理其中的cookie数据t,并解析出WShopJwtDto数据,并存放到Session中,进一步使用jwtDto数据获取用户的授权信息,进而进行下一步web请求处理;
异常的统一处理和内部处理逻辑
@ControllerAdvice 注解为统一异常处理的核心是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上该类中的设计思路:
- 使用@ExceptionHandler注解捕获指定或自定义的异常;
- 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
- 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
- 自定一个异常类,捕获针对项目或业务的异常;
- 异常的对象信息补充到统一结果枚举中;
列如:
@ControllerAdvice
public class MyExceptionHandler {
}
基于上面的提供的技术和设计方式,可以是项目中的异常进行统一的处理,所以要求项目中不要使用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信息和授权信息;
服务之间的交互和通讯:
和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 等各个模块的代码,极大的提升了开发效率。
编写和开发模块
基于需求和功能要求实现具体的接口功能,遵循开发规范;