XService接口服务快速开发框架,基于SpringBoot实现,封装了接口开发过程中的基础功能及控制流程,并约定了统一的接口报文格式,制定了完善的开发规范以及测试规范,让程序员只需关注具体业务实现,提高了开发接口服务的效率。
XService基础功能基于xkernel 提供的SPI机制,结合SpringBoot提供的 ConditionalOnBean,ConditionalOnProperty等注解实现,实用,简单,扩展灵活。
如果你是使用Maven
来构建项目,你需要添加XService的pom.xml
文件内,如下所示:
<dependency>
<groupId>com.javacoo.xservicegroupId>
<artifactId>xservice_baseartifactId>
<version>1.0.0version>
dependency>
添加完组件我们就可以进行配置使用了。
参数 | 类型 | 是否必选 | 描述 |
---|---|---|---|
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数组转换为十六进制的字符串,该字符串即是签名。
加密/解密
加密算法:Base64(DES(value,secretKey))
解密算法:DES(Base64(value),secretKey)
定义接口服务
XService约定一个接口服务为一个Controller类,且此类必须继承框架提供的三个基类之一。
带参数的接口服务基类:AbstractParamController
/**
* 业务参数控制器基类
* 说明:
* 定义有业务参数的接口处理基本流程
* @author DuanYong
* @param 参数
* @since 2017年6月28日下午2:48:27
*/
@Slf4j
public abstract class AbstractParamController<P extends BaseParameter> extends BaseController {
/**
* 接口处理
* 说明:
* 1:请求参数解析
* 2:检查请求参数
* 3:业务处理
* 4:设置响应数据
* @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());
}
/**
* 执行
* 说明:
* hystrix
* @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);
}
/**
* 解析请求参数
* 说明:
* 将请求参数中的业务参数对象转换为服务使用的对象
* @author DuanYong
* @since 2017年6月28日下午3:17:32
* @return: java.util.Optional 业务参数对象
*/
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));
}
}
/**
* 初始化初始请求参数
* 说明:
* 解析并初始化请求参数对象
* @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;
}
/**
* 校验请求中的业务参数
* 说明:
* 由子类实现,如果参数检查不通过,请抛出参数异常:IllegalParameterException
* @author DuanYong
* @param p 业务参数对象
* @throws IllegalParameterException
* @since 2017年6月28日下午2:28:10
*/
protected abstract void validate(P p) throws IllegalParameterException;
/**
* 具体业务处理
* 说明:
* 由子类实现
* @author DuanYong
* @param p 业务参数对象
* @return 业务返回数据
* @since 2017年5月5日下午3:24:09
*/
protected abstract Object process(P p);
/**
* 获取参数类型
* 说明:
*
* @author DuanYong
* @return 参数类型对象
* @since 2017年7月24日上午10:33:30
*/
protected abstract Class<P> getParamClass();
/**
* 服务降级,默认返回REQUEST_TIMEOUT字符串,框架统一处理抛出TimeoutException异常
* 说明:
* 注意:在fallback方法中不允许有远程方法调用,方法尽量要轻,调用其他外部接口也要进行hystrix降级。否则执行fallback方法会抛出异常
* @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
/**
* 无业务参数控制器基类
* 说明:
* 定义无业务参数接口处理基本流程
* 统一异常处理
* @author DuanYong
* @since 2017年7月11日上午8:49:58
*/
@Slf4j
public abstract class AbstractNonParamController extends BaseController {
/**
* 具体业务处理
* 说明:
* 由子类实现
* @author DuanYong
* @return 业务返回数据
* @since 2017年7月11日上午8:51:23
*/
protected abstract Object process();
/**
* 接口处理
* 说明:
* 业务处理
* 设置响应数据
* @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());
}
/**
* 执行
* 说明:
* @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
/**
* 业务参数控制器基类
* 说明:
* 定义有业务参数的接口处理基本流程
* @author DuanYong
* @since 2017年6月28日下午2:48:27
*/
@Slf4j
public abstract class AbstractUrlParamController extends BaseController {
/**
* 接口处理
* 说明:
* 1:请求参数解析
* 2:检查请求参数
* 3:业务处理
* 4:设置响应数据
* @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());
}
/**
* 执行
* 说明:
* @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);
}
/**
* 解析请求参数
* 说明:
* 将URL请求参数中的业务参数对象转换为服务使用的MAP对象
* @author DuanYong
* @since 2017年6月28日下午3:17:32
* @return: java.util.Optional
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());
}
/**
* 校验请求中的业务参数
* 说明:
* 由子类实现,如果参数检查不通过,请抛出参数异常:IllegalParameterException
* @author DuanYong
* @param p 业务参数对象
* @throws IllegalParameterException
* @since 2017年6月28日下午2:28:10
*/
protected abstract void validate(Map<String,String> p) throws IllegalParameterException;
/**
* 具体业务处理
* 说明:
* 由子类实现
* @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
/**
* 获取案例数据接口,带参数
* 说明:
*
* @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
/**
* 获取案例数据接口,无参数
* 说明:
*
* @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参数
* 说明:
*
* @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
/**
* 查询对象基类
* 说明:
* - 定义相关公共查询字段
* @author DuanYong
* @since 2017年7月17日上午8:55:10
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseReq extends BaseParameter {
/**
* ID
*/
private String id;
}
响应业务对象:ExampleDto
/**
* 参数
* 说明:
*
* @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
* 说明:
*
* @author DuanYong
* @since 2017年7月14日下午1:37:04
*/
public interface ExampleDao {
/**
* 根据版块ID ,查询版块内容
* 说明:
*
* @author DuanYong
* @param id
* @return ExampleDto
* @since 2017年7月14日下午1:40:26
*/
ExampleDto getExampleInfo(@Param("id")String id);
}
ExampleDaoMapper.xml
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
/**
* 案例数据服务接口
* 说明:
* - 获取详细数据
* @author DuanYong
* @since 2017年7月14日上午10:54:21
*/
public interface ExampleService {
/**
* 获取版块及版块下内容信息
* 说明:
*
* @author DuanYong
* @param id 参数
* @return
* @since 2017年7月14日上午11:23:21
*/
Optional<ExampleDto> getExampleInfo(String id);
}
实现服务:ExampleServiceImpl
/**
* 案例数据服务接口实现
* 说明:
*
* @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));
}
}
编写测试
测试基类
/**
* 测试基类
*
* @author [email protected]
* @date 2020/10/16 15:58
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BaseTest {
// 安全密钥
protected static final String SECRET_KEY = "5c06aedadb259698dc59f64fc02f4488d32fb2fd298d156873e23ed37311a2b600000018";
// 渠道
protected static final String APP_KEY = "CHENNEL_1";
protected static String getNonce() {
return WebUtil.genTransSn();
}
/**
* 校验结果
*
* @author [email protected]
* @date 2021/3/3 14:59
* @param result: 结果
* @return: void
*/
protected void verify(String result){
if(StringUtils.isBlank(result)){
return;
}
BaseResponse baseResponse = FastJsonUtil.toBean(result,BaseResponse.class);
if(StringUtils.isBlank(baseResponse.getSign())){
return;
}
baseResponse.getData().ifPresent(o->{
String s = FastJsonUtil.toJSONString(o);
log.info("请求返回业务json:{}",s);
log.info("请求返回签名:{}",baseResponse.getSign());
if (SignUtil.cloudVerifySign(baseResponse.getSign(), s,baseResponse.getTransactionSn(),baseResponse.getTimestamp().toString(), SECRET_KEY)) {
log.info("返回数据合法");
} else {
log.info("返回数据被篡改");
}
});
}
}
测试类:ExampleControllerTest
/**
* 接口测试
*
*
* @author: [email protected]
* @since: 2021/3/3 13:49
*/
@Slf4j
public class ExampleControllerTest extends BaseTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void getExampleInfoTest() throws Exception{
MvcResult mvcResult = mockMvc.perform(post("/example/v1/getExampleInfo")
.contentType(MediaType.APPLICATION_JSON)
.content(FastJsonUtil.toJSONString(getExampleInfoReq())))
.andExpect(status().isOk())// 模拟发送post请求
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))// 预期返回值的媒体类型text/plain;charset=UTF-8
.andReturn();// 返回执行请求的结果
String result = mvcResult.getResponse().getContentAsString();
log.info("请求的结果:{}",result);
verify(result);
}
private Object getExampleInfoReq() {
BaseRequest baseRequest = new BaseRequest();
baseRequest.setAppKey(APP_KEY);
baseRequest.setTimestamp(Calendar.getInstance().getTimeInMillis());
baseRequest.setNonce(getNonce());
baseRequest.setTransactionSn(getNonce());
BaseReq baseReq = new BaseReq();
baseReq.setId(SecurityUtil.encryptDes("1",SECRET_KEY));
String json = FastJsonUtil.toJSONString(baseReq);
String sign = SignUtil.clientSign(json,baseRequest.getNonce(),baseRequest.getTimestamp().toString(),SECRET_KEY);
baseRequest.setSign(sign);
baseRequest.setParameter(baseReq);
return baseRequest;
}
}
测试日志
[ main] c.j.x.b.interceptor.HandlerInterceptor : 接口->getExampleInfo,原始POST请求参数:{"appKey":"CHENNEL_1","nonce":"20210309094818488EEE50B9E9061026","parameter":{"id":"lbGSlXJ0ZB7="},"parameterMap":{"id":"lbGSlXJ0ZB7="},"sign":"0defb4240c72d52e5b46803ce51a728f8650ccafa9b80f9dbd4e7cd716c0fa1d","timestamp":1615254498483,"transactionSn":"20210309094818490626096EFA956655"},原始URL请求参数:null,流水号:202103090948185408BA9FAB01255050
[ main] c.j.x.b.s.handler.MethodLockHandler : 方法进入分布式事务锁,加锁key:getExampleInfo-lbGSlXJ0ZB7=,自动失效时间:10秒
[ main] c.j.x.b.cache.redis.lock.RedssionLock : >>>>> tryLock lockKey:javacoo:service:lock:getExampleInfo-lbGSlXJ0ZB7=,TimeUnit:SECONDS,waitTime:0,timeout:10
[ main] c.j.x.b.s.handler.MethodLockHandler : 加锁成功,KEY:getExampleInfo-lbGSlXJ0ZB7=,自动失效时间:10秒
[ main] c.j.x.b.s.handler.ParamValidatorHandler : 接口上送的签名值:0defb4240c72d52e5b46803ce51a728f8650ccafa9b80f9dbd4e7cd716c0fa1d
[ main] c.j.x.b.support.handler.EnDeCodeHandler : 接口:getExampleInfo,参数对象:{id=lbGSlXJ0ZB7=},加解密字段:['id']
[ main] c.j.x.b.interceptor.HandlerInterceptor : 接口->getExampleInfo,预处理完成,耗时->0.489秒,流水号:20210309094818490626096EFA956655
[ main] c.j.x.e.controller.ExampleController : 执行业务方法
[ main] org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
[ main] org.mybatis.spring.SqlSessionUtils : SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b87ff6] was not registered for synchronization because synchronization is not active
[ main] com.zaxxer.hikari.HikariDataSource : MyHikariCP - Starting...
[ main] com.zaxxer.hikari.HikariDataSource : MyHikariCP - Start completed.
[ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@30621512 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a226ea] will not be managed by Spring
[ main] org.mybatis.spring.SqlSessionUtils : Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b87ff6]
[ main] c.j.x.base.AbstractParamController : 接口->getExampleInfo,处理完成,耗时->1.366秒,流水号:20210309094818490626096EFA956655
[ main] c.j.x.b.support.handler.EnDeCodeHandler : 接口:getExampleInfo,参数对象:{data=data, id=1},加解密字段:['id'],['data']
[ main] c.j.x.b.cache.redis.lock.RedssionLock : >>>>> unlock lockKey:javacoo:service:lock:getExampleInfo-lbGSlXJ0ZB7=
[ main] c.j.x.b.s.handler.MethodUnLockHandler : 方法解锁,MethodName:getExampleInfo,key:getExampleInfo-lbGSlXJ0ZB7=,流水号:20210309094818490626096EFA956655
[ main] c.j.x.b.interceptor.HandlerInterceptor : 接口->getExampleInfo,后置处理完成,耗时->0.014秒,流水号:20210309094818490626096EFA956655
[pool-2-thread-4] c.j.x.b.s.e.h.TransCompleteEventHandler : 交易完成事件处理:com.javacoo.xservice.base.support.event.TransCompleteEvent[source=com.javacoo.xservice.base.interceptor.HandlerInterceptor@1266391]
[ main] c.j.x.e.c.ExampleControllerTest : 请求的结果:{"code":"200","data":{"data":"BOmRuJy1ta7=","id":"lbGSlXJ0ZB7="},"message":"请求成功","sign":"e8b4b758265a8a00d7628565d8fa17d7baeec7ebc4b3f01882b6ac56816e7d88","timestamp":1615254500416,"transactionSn":"20210309094818490626096EFA956655"}
[ main] com.javacoo.xservice.example.BaseTest : 请求返回业务json:{"data":"BOmRuJy1ta7=","id":"lbGSlXJ0ZB7="}
[ main] com.javacoo.xservice.example.BaseTest : 请求返回签名:e8b4b758265a8a00d7628565d8fa17d7baeec7ebc4b3f01882b6ac56816e7d88
[ main] com.javacoo.xservice.example.BaseTest : 返回数据合法
配置及说明
profile = dev_envrimont
spring.http.encoding.force=true
#监控
#management.security.enabled=false
#management.port=54007
spring.datasource.url=jdbc:mysql://mysql01.io:3306/dev?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true&roundRobinLoadBalance=true
spring.datasource.username=root
#SecurityUtil解密,以DES@+密文
spring.datasource.password=DES@JXAR60ozSXSBMvQLcOMhmKyhh0Ua1HnC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
## Hikari 连接池配置 ------ 详细配置请访问:https://github.com/brettwooldridge/HikariCP
## 最小空闲连接数量
spring.datasource.hikari.minimum-idle=10
## 空闲连接存活最大时间,默认600000(10分钟)
spring.datasource.hikari.idle-timeout=180000
## 连接池最大连接数,默认是10
spring.datasource.hikari.maximum-pool-size=50
## 此属性控制从池返回的连接的默认自动提交行为,默认值:true
spring.datasource.hikari.auto-commit=true
## 连接池母子
spring.datasource.hikari.pool-name=MyHikariCP
## 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
spring.datasource.hikari.max-lifetime=1800000
## 数据库连接超时时间,默认30秒,即30000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
#==================应用相关配置=============
#是否开启请求限制:true->开启,false->关闭
app.config.core.reqLimitEnabled=true
#请求限制:每秒允许最大并发限制
app.config.core.reqLimitMax=300
#=======单机模式==========
spring.redis.database=0
# Redis服务器地址(单机模式)
spring.redis.host=redis01.io
# Redis服务器连接端口
spring.redis.port=16579
# Redis服务器连接密码(默认为空)
spring.redis.password=ZvsXBp2uyoqpcH5M
# 连接超时时间(毫秒)
spring.redis.timeout=20000
#=======连接池==========
# 连接池最大连接数(使用负值表示没有限制),如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
spring.redis.jedis.pool.max-active=200
# 连接池中的最大空闲连接,控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8
spring.redis.jedis.pool.max-idle=50
#========安全配置============
#签名算法:
#- 1:请求参数串=请求报文体中 parameter 对象转换为 json字符串(统一用fastjson)
#- 2:将应用密钥分别添加到 请求参数串+随机数+时间戳 的头部和尾部:secret+请求参数字符串+随机数+时间戳+secret
#- 3:对该字符串进行 SHA256 运算,得到一个byte数组
#- 4:将该byte数组转换为十六进制的字符串,该字符串即是这些请求参数对应的签名
#- 5:HEX(SHA256(secret+请求参数字符串+随机数+时间戳+secret))
#渠道_CHENNEL_1:secretKey->安全密钥,appKey->渠道编码,sign->是否需要签名,true->需要签名
app.config.core.securityMap[CHENNEL_1].secretKey=5c06aedadb259698dc59f64fc02f4488d32fb2fd298d156873e23ed37311a2b600000018
app.config.core.securityMap[CHENNEL_1].appKey=CHENNEL_1
app.config.core.securityMap[CHENNEL_1].sign=true
#========接口业务数据加解密配置============
#加密算法:Base64(DES(value,secretKey))
#解密
#格式=> app.config.decode.decodeParamMap[(版本号_)接口名称]=['解密参数1'],['解密参数2']...
app.config.decode.decodeParamMap[getExampleInfo]=['id']
#加密
#格式=> encode.encodeParamMap[(版本号_)接口名称]=['编码参数名1'],['编码参数名2']...
app.config.encode.encodeParamMap[getExampleInfo]=['id'],['data']
#========接口加锁配置============
#说明:参数为空时为方法级加锁,否则是参数级加锁
#格式=> app.config.lock.lockParamMap[(版本号_)接口名称].secondTimeout=60
基于xkernel 提供的SPI机制,结合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty实现,
系统提供默认实现:
xkernel spi 开发步骤及实现机制 见:https://gitee.com/javacoo/xkernel
/**
* 授权服务实现
*
*
* @author: [email protected]
* @since: 2021/3/3 13:40
*/
@Slf4j
public class AuthServiceImpl implements AuthService {
/**
* 授权
*
*
* @param o : 参数
* @author [email protected]
* @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.preHandle预处理流程包括:
a)初始化数据交换区:基于ThreadLocal实现,封装了此次请求的相关信息SwapAreaData,供整个请求过程中使用。
b)解析请求参数:将原始请求参数转换为框架内部BaseRequest对象
c)执行预处理流程:生成全局流水号,依次执行注册的预处理器HandlerStack,如参数解码,参数校验等。
2.postHandle提交处理流程包括:
a)依次执行注册的预处理器HandlerStack,如编码,签名等。
b)设置响应数据:响应数据转换为目标格式(如JSON格式)
3.afterCompletion完成处理后续流程包括:
a)异步发布交易完成事件。
b)释放当前线程数据交换区数据
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
类结构模型
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo/xService
QQ群:164863067
作者/微信:javacoo
邮箱:[email protected]
https://gitee.com/javacoo/xService