spring架构开发技巧

xService微服务快速开发框架

2020-05-29  本文已影响0人  javacoo

XService:为组件化,快速构建微服务而生

什么是XService?

XService接口服务快速开发框架,基于SpringBoot实现,封装了接口开发过程中的基础功能及控制流程,并约定了统一的接口报文格式,制定了完善的开发规范以及测试规范,让程序员只需关注具体业务实现,提高了开发接口服务的效率。

XService基础功能基于xkernel 提供的SPI机制,结合SpringBoot提供的 ConditionalOnBean,ConditionalOnProperty等注解实现,实用,简单,扩展灵活。

安装 & 入门

如果你是使用Maven来构建项目,你需要添加XService的pom.xml文件内,如下所示:

       <dependency>
            <groupId>com.javacoo.xservice</groupId>
            <artifactId>xservice_base</artifactId>
            <version>1.0.0</version>
        </dependency>

添加完组件我们就可以进行配置使用了。

使用指南

XService的约定
参数 类型 是否必选 描述
appKey String 应用key
nonce String 32位UUID随机字串
sign String 签名
timestamp Long 请求时间戳,防止重放攻击
transactionSn String 交易流水号
parameter Object 请求的业务对象
参数 类型 描述
code String 返回码,具体含有参见下文
message String 返回消息,如错误信息
timestamp String 响应时间
transactionSn String 交易流水号
sign String 签名
data Object 返回的业务对象

平台级返回码如下:业务可制定具体的业务返回代码

code message 说明
200 请求成功
207 频繁操作 频繁操作
400 请求参数出错 终端传递的参数值错误
403 没有权限 没有权限
404 服务不存在 服务不存在
408 请求超时 请求超时
409 业务逻辑出错 服务端执行服务方法时
执行业务逻辑校验出错,或者响应数据为空。
500 系统繁忙,请稍后再试 数据不满足提交条件或
服务端执行服务方法时出现异常,需由服务人员解决

签名算法:HEX(SHA256(secretKey+参数字符串+随机数+时间戳+secretKey))

说明:

1:参数字符串=将报文体中 业务对象 转换为 json字符串。

2:将应用密钥(secretKey)分别添加到 参数字符串+随机数+时间戳 的头部和尾部:secretKey+参数字符串+随机数+时间戳+secretKey.

3:对该字符串进行 SHA256 运算,得到一个byte数组。

4:将该byte数组转换为十六进制的字符串,该字符串即是签名。

/**
   * 业务参数控制器基类
 * <p>说明:</p>
   * <li>定义有业务参数的接口处理基本流程</li>
   * @author DuanYong
   * @param <P> 参数
   * @since 2017年6月28日下午2:48:27
   */
  @Slf4j
  public abstract class AbstractParamController<P extends BaseParameter> extends BaseController {
    /**
     * 接口处理
     * <p>说明:</p>
     * <li>1:请求参数解析</li>
     * <li>2:检查请求参数</li>
     * <li>3:业务处理</li>
     * <li>4:设置响应数据</li>
     * @author DuanYong
     * @since 2017年6月28日下午3:19:43
       * @param response 响应对象
     */
    @RequestMapping
    public final void handle(HttpServletResponse response) {
        final Long startTime = System.currentTimeMillis();
          //参数解析->检查请求参数->业务处理->设置响应数据
        parse().map(r->validateFunction.apply(r)).map(r->Optional.ofNullable(execute(r))).map(o->setSuccessResponse(response,o.orElse(Optional.empty())));
          log.info("接口->{},处理完成,耗时->{}秒,流水号:{}", SwapAreaUtils.getSwapAreaData().getReqMethod(),(System.currentTimeMillis() - startTime)/1000.0,SwapAreaUtils.getSwapAreaData().getTransactionSn());
    }
    /**
     * 执行
     * <p>说明:</p>
     * <li>hystrix</li>
     * @author DuanYong
       * @since 2017年11月13日下午3:41:04
     * @param p 业务参数
       * @return: java.lang.Object 业务返回对象
     */
    private final Object execute(P p){
        return executeFunction.apply(p);
    }
    /**
     * 解析请求参数
     * <p>说明:</p>
     * <li>将请求参数中的业务参数对象转换为服务使用的对象</li>
     * @author DuanYong
     * @since 2017年6月28日下午3:17:32
       * @return: java.util.Optional<P> 业务参数对象
     */
    protected final Optional<P> parse(){
        BaseRequest baseRequest = SwapAreaUtils.getSwapAreaData().getBaseRequest();
        baseRequest.getParameter().orElseThrow(()->new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_PARAM_PARSE_IS_EMPTY)));
        try{
            return baseRequest.getParameter().map(o->o.toString()).map(s->initBaseParameter(s,baseRequest));
        }catch(Exception ex){
            ex.printStackTrace();
              log.error("将请求参数中的业务参数对象转换为服务使用的对象失败,流水号:{},请求参数:{},异常信息:", WebUtil.getSwapAreaData().getTransactionSn(),baseRequest.getParameter(),ex);
            throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_PARAM_PARSE_ERROR));
        }
    }
    /**
     * 初始化初始请求参数
     * <p>说明:</p>
     * <li>解析并初始化请求参数对象</li>
     * @author DuanYong
     * @param paramString 参数原始json字符串
       * @param baseRequest 请求参数对象
     * @return P 业务参数对象
     * @since 2017年11月14日上午11:07:19
     */
    private P initBaseParameter(String paramString, BaseRequest baseRequest){
        P p = FastJsonUtil.toBean(paramString,getParamClass());
        p.setTransactionSn(baseRequest.getTransactionSn());
        p.setQueryStringMap(baseRequest.getQueryStringMap());
        return p;
    }
    /**
     * 校验请求中的业务参数
     * <p>说明:</p>
     * <li>由子类实现,如果参数检查不通过,请抛出参数异常:IllegalParameterException</li>
     * @author DuanYong
     * @param p 业务参数对象
     * @throws IllegalParameterException
     * @since 2017年6月28日下午2:28:10
     */
    protected abstract void validate(P p) throws IllegalParameterException;
    /**
     * 具体业务处理
     * <p>说明:</p>
     * <li>由子类实现</li>
     * @author DuanYong
     * @param p 业务参数对象
     * @return 业务返回数据
     * @since 2017年5月5日下午3:24:09
     */
    protected abstract Object process(P p);
    /**
     * 获取参数类型
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @return 参数类型对象
     * @since 2017年7月24日上午10:33:30
     */
    protected abstract Class<P> getParamClass();
    /**
     * 服务降级,默认返回REQUEST_TIMEOUT字符串,框架统一处理抛出TimeoutException异常
     * <p>说明:</p>
     * <li>注意:在fallback方法中不允许有远程方法调用,方法尽量要轻,调用其他外部接口也要进行hystrix降级。否则执行fallback方法会抛出异常</li>
     * @author DuanYong
     * @param p 参数
     * @return REQUEST_TIMEOUT
     * @since 2018年8月21日上午11:20:37
     */
    protected Object fallback(P p){
        return Constants.REQUEST_TIMEOUT;
    }
  
    /**
     * 校验并返回业务参数
     */
    private Function<P,P> validateFunction = (P p)->{
        validate(p);
        return p;
    };
    /**
     * 执行业务处理
     */
    private Function<P,Object> executeFunction = (P p)-> process(p);
    /**
     * 执行降级业务处理
     */
    private Function<P,Object> fallbackFunction = (P p)-> fallback(p);
  }

无参数的接口服务基类:AbstractNonParamController

/**
 * 无业务参数控制器基类
 * <p>说明:</p>
 * <li>定义无业务参数接口处理基本流程</li>
 * <li>统一异常处理</li>
 * @author DuanYong
 * @since 2017年7月11日上午8:49:58
 */
@Slf4j
public abstract class AbstractNonParamController extends BaseController {
    /**
     * 具体业务处理
     * <p>说明:</p>
     * <li>由子类实现</li>
     * @author DuanYong
     * @return 业务返回数据
     * @since 2017年7月11日上午8:51:23
     */
    protected abstract Object process();
  /**
   * 接口处理
   * <p>说明:</p>
   * <li>业务处理</li>
   * <li>设置响应数据</li>
   * @since 2017年7月11日上午9:13:28
   */
  @RequestMapping
  private final void handle(HttpServletResponse httpServletResponse) {
      Long startTime = System.currentTimeMillis();
      //业务处理->设置响应数据
      Optional.ofNullable(execute()).map(o->setSuccessResponse(httpServletResponse,o));
      log.info("接口->{},处理完成,耗时->{}秒,流水号:{}", SwapAreaUtils.getSwapAreaData().getReqMethod(),(System.currentTimeMillis() - startTime)/1000.0,SwapAreaUtils.getSwapAreaData().getTransactionSn());
  }
  /**
   * 执行
   * <p>说明:</p>
   * @author DuanYong
   * @return: java.lang.Object 业务返回数据
   * @since 2017年11月13日下午3:41:04
   */
  private final Object execute(){
      return executeFunction.get();
  }
  /**
   * 执行业务处理
   */
  private Supplier<Object> executeFunction = ()-> process();
  
}

url参数接口服务基类:AbstractUrlParamController

/**
 * 业务参数控制器基类
 * <p>说明:</p>
 * <li>定义有业务参数的接口处理基本流程</li>
 * @author DuanYong
 * @since 2017年6月28日下午2:48:27
 */
@Slf4j
public abstract class AbstractUrlParamController extends BaseController {
  /**
   * 接口处理
   * <p>说明:</p>
   * <li>1:请求参数解析</li>
   * <li>2:检查请求参数</li>
   * <li>3:业务处理</li>
   * <li>4:设置响应数据</li>
   * @author DuanYong
   * @since 2017年6月28日下午3:19:43
   */
  @RequestMapping
  public final void handle(HttpServletResponse response) {
      Long startTime = System.currentTimeMillis();
        //参数解析->检查请求参数->业务处理->设置响应数据
        parse().map(r->validateFunction.apply(r)).map(r->Optional.ofNullable(execute(r))).map(o->setSuccessResponse(response,o.orElse(Optional.empty())));
      log.info("接口->{},处理完成,耗时->{}秒,流水号:{}", SwapAreaUtils.getSwapAreaData().getReqMethod(),(System.currentTimeMillis() - startTime)/1000.0,SwapAreaUtils.getSwapAreaData().getTransactionSn());
  }
  /**
   * 执行
   * <p>说明:</p>
   * @author DuanYong
   * @param p 请求参数
   * @return Object 业务返回数据
   * @since 2017年11月13日下午3:41:04
   */
  private final Object execute(Map<String,String> p){
      return executeFunction.apply(p);
  }
  /**
   * 解析请求参数
   * <p>说明:</p>
   * <li>将URL请求参数中的业务参数对象转换为服务使用的MAP对象</li>
   * @author DuanYong
   * @since 2017年6月28日下午3:17:32
     * @return: java.util.Optional<Map<String,String>> 业务参数对象
   */
  protected final Optional<Map<String,String>> parse(){
      BaseRequest baseRequest = SwapAreaUtils.getSwapAreaData().getBaseRequest();
      if(baseRequest.getQueryStringMap().isEmpty()){
          log.error("解析URL请求参数失败,请求参数为空,流水号:{}", WebUtil.getSwapAreaData().getTransactionSn());
          throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_PARAM_PARSE_IS_EMPTY));
      }
      return Optional.ofNullable(baseRequest.getQueryStringMap());

  }
  /**
   * 校验请求中的业务参数
   * <p>说明:</p>
   * <li>由子类实现,如果参数检查不通过,请抛出参数异常:IllegalParameterException</li>
   * @author DuanYong
   * @param p 业务参数对象
   * @throws IllegalParameterException
   * @since 2017年6月28日下午2:28:10
   */
  protected abstract void validate(Map<String,String> p) throws IllegalParameterException;
  /**
   * 具体业务处理
   * <p>说明:</p>
   * <li>由子类实现</li>
   * @author DuanYong
   * @param p 业务参数对象
   * @return 业务返回数据
   * @since 2017年5月5日下午3:24:09
   */
  protected abstract Object process(Map<String,String> p);
  /**
   * 校验并返回业务参数
   */
  private Function<Map<String,String>,Map<String,String>> validateFunction = (Map<String,String> p)->{
      validate(p);
      return p;
  };
  /**
   * 执行业务处理
   */
  private Function<Map<String,String>,Object> executeFunction = (Map<String,String> p)-> process(p);
}

服务开发过程中尽量少使用多线程,如果使用了多线程,框架提供的交换区对象(ThreadLocal实现)将无法正常使用。

打印日志请使用LogUtil中的方法,因为框架对日志输出进行了增强,统一添加了流水号(基于交换区对象)。

LogUtil只能在当前主线程下使用。

使用示例

请求协议业务部分如下:parameter 对象

参数 类型 是否必选 描述
id String 业务主键

响应协议业务部分如下:data 对象

参数 类型 描述
id Integer 主键
data String 数据

获取案例数据接口,带参数Controller:ExampleController

/**
 * 获取案例数据接口,带参数
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月17日上午9:02:56
 */
@Slf4j
@RestController
@RequestMapping(value = "/example/v1/getExampleInfo")
public class ExampleController extends AbstractParamController<BaseReq> {
  /** 数据服务 */
  @Autowired
  private ExampleService exampleService;
  @Override
  protected void validate(BaseReq p) throws IllegalParameterException {
      AbstractAssert.notNull(p, ErrorCodeConstants.SERVICE_REQ_PARAM);
      AbstractAssert.isNotBlank(p.getId(), ErrorCodeConstants.SERVICE_GET_EXAMPLE_INFO_ID);
  }

  @Override
  public Object process(BaseReq p) {
      log.info("执行业务方法");
        return exampleService.getExampleInfo(p.getId()).get();
  }

  @Override
  protected Class<BaseReq> getParamClass() {
      return BaseReq.class;
  }
}

获取案例数据接口,无参数Controller:ExampleNonParamController

/**
 * 获取案例数据接口,无参数
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月17日上午9:02:56
 */
@RestController
@RequestMapping(value = "/example/v1/getNonParamExampleInfo")
public class ExampleNonParamController extends AbstractNonParamController {
  /** 数据服务 */
  @Autowired
  private ExampleService exampleService;
  @Override
  public Object process() {
        return exampleService.getExampleInfo("1");
  }
}

获取案例数据接口,url参数Controller:ExampleUrlParamController

/**
 * 获取案例数据接口,url参数
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月17日上午9:02:56
 */
@Slf4j
@RestController
@RequestMapping(value = "/example/v1/getUrlParamExampleInfo")
public class ExampleUrlParamController extends AbstractUrlParamController {
  /** 数据服务 */
  @Autowired
  private ExampleService exampleService;


    @Override
    protected void validate(Map<String, String> p) throws IllegalParameterException {
        log.info("validate->{}",p);

    }

    @Override
    protected Object process(Map<String, String> p) {
        return exampleService.getExampleInfo(p.get("id"));
    }
}

请求业务对象:BaseReq

/**
 * 查询对象基类
 * <p>说明:</p>
 * <li>定义相关公共查询字段</li>
 * @author DuanYong
 * @since 2017年7月17日上午8:55:10
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseReq extends BaseParameter {
  /**
   * ID
   */
  private String id;
}

响应业务对象:ExampleDto

/**
 * 参数
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月14日下午1:04:59
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExampleDto {
    /**
     * id
     */
    private String id;

    /**
     * 数据
     */
    private String data;
}

编写:ExampleDao及ExampleDaoMapper.xml

/**
 * Example服务DAO
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月14日下午1:37:04
 */
public interface ExampleDao {
    /**
   * 根据版块ID ,查询版块内容
   * <p>说明:</p>
   * <li></li>
   * @author DuanYong
   * @param id
   * @return ExampleDto
   * @since 2017年7月14日下午1:40:26
   */
    ExampleDto getExampleInfo(@Param("id")String id);
}

ExampleDaoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javacoo.xservice.example.dao.ExampleDao">
    <!-- 案例 -->
    <select id="getExampleInfo" resultType="com.javacoo.xservice.example.bean.dto.ExampleDto">
        SELECT id,data
        FROM example
        WHERE id=#{id}
    </select>
</mapper>

定义服务接口:ExampleService

/**
 * 案例数据服务接口
 * <p>说明:</p>
 * <li>获取详细数据</li>
 * @author DuanYong
 * @since 2017年7月14日上午10:54:21
 */
public interface ExampleService {
  /**
   * 获取版块及版块下内容信息
   * <p>说明:</p>
   * <li></li>
   * @author DuanYong
   * @param id 参数
   * @return
   * @since 2017年7月14日上午11:23:21
   */
  Optional<ExampleDto> getExampleInfo(String id);
}

实现服务:ExampleServiceImpl

/**
 * 案例数据服务接口实现
 * <p>说明:</p>
 * <li></li>
 * @author DuanYong
 * @since 2017年7月14日下午1:30:18
 */
@Service
@Slf4j
public class ExampleServiceImpl implements ExampleService {
  @Autowired
    private ExampleDao exampleDao;

  @Override
  public Optional<ExampleDto> getExampleInfo(String id) {
      AbstractAssert.isNotBlank(id, ErrorCodeConstants.SERVICE_GET_EXAMPLE_INFO_ID);
        return Optional.ofNullable(exampleDao.getExampleInfo(id));
  }
}

插件开发指南

XService的插件机制:

基于xkernel 提供的SPI机制,结合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty实现,

XService的扩展点:

系统提供默认实现:

ext.png

xkernel spi 开发步骤及实现机制 见:https://gitee.com/javacoo/xkernel

XService插件开发示例:授权服务扩展
/**
 * 授权服务实现
 * <li></li>
 *
 * @author: duanyong@jccfc.com
 * @since: 2021/3/3 13:40
 */
@Slf4j
public class AuthServiceImpl implements AuthService {

    /**
     * 授权
     * <li></li>
     *
     * @param o : 参数
     * @author duanyong@jccfc.com
     * @date 2021/3/2 18:11
     * @return: void true-> 成功
     */
    @Override
    public boolean auth(Object o) {
        log.info("授权:{}", o);
        return true;
    }
}
example=com.javacoo.xservice.example.service.impl.AuthServiceImpl
#========授权配置============
app.config.auth.impl=example

软件架构

总体逻辑架构

快速开发框架基于SpringBoot2.X实现。具体分为:终端展现层、网关层、应用层、数据层。

  1. 终端展现层:终端分为电视端,微信端,PC浏览器。
  2. 网关层:基于Kong,实现了服务注册,流量控制,负载均衡,签名验签等。
  3. 应用层:转发展现层、远程调用等对业务层的逻辑请求,由控制层完成请求的接入、参数校验、流转调度。将所有请求接入后统一转交给业务集成层完成具体的服务调用。
  4. 业务层:所有的业务逻辑代码都集中在这一层,包括本地业务,和远程服务。
  5. 数据层:提供访问数据库,缓存组件,统一了数据访问接口。

框架设计

概述
框架Controller体系结构

类结构模型:带参数

带参数

类结构模型:不带参数

不带参数
拦截器体系结构

类结构模型

类结构模型

1.preHandle预处理流程包括:
a)初始化数据交换区:基于ThreadLocal实现,封装了此次请求的相关信息SwapAreaData,供整个请求过程中使用。
b)解析请求参数:将原始请求参数转换为框架内部BaseRequest对象
c)执行预处理流程:生成全局流水号,依次执行注册的预处理器HandlerStack,如参数解码,参数校验等。
2.postHandle提交处理流程包括:
a)依次执行注册的预处理器HandlerStack,如编码,签名等。
b)设置响应数据:响应数据转换为目标格式(如JSON格式)
3.afterCompletion完成处理后续流程包括:
a)异步发布交易完成事件。
b)释放当前线程数据交换区数据

异常体系结构

类结构模型

输入图片说明
过滤器体系结构

类结构模型

输入图片说明
授权组件结构

类结构模型

AuthService.png
事件体系结构

类结构模型

输入图片说明
处理器体系结构

类结构模型

输入图片说明
熔断处理组件结构

类结构模型

输入图片说明
数据交换区组件结构

类结构模型

输入图片说明
统一异常处理组件结构

类结构模型

输入图片说明
表达式处理组件结构

类结构模型

输入图片说明
分布式锁处理组件结构

类结构模型

输入图片说明
服务日志处理组件结构

类结构模型

输入图片说明
项目信息
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo/xService
QQ群:164863067
作者/微信:javacoo
邮箱:xihuady@126.com
下载地址

https://gitee.com/javacoo/xService

上一篇 下一篇

猜你喜欢

热点阅读