This commit is contained in:
六如
2024-12-02 23:03:25 +08:00
parent c0cfdbafd9
commit 942826f4b0
107 changed files with 247 additions and 3432 deletions

View File

@@ -88,7 +88,7 @@ public class ApiConfig {
private String zoneId = "Asia/Shanghai";
/**
* 返回结果字段小写形式
* 字段下划线小写形式
*/
private Boolean fieldLowercase = false;
private Boolean fieldSnakeCase = false;
}

View File

@@ -1,13 +1,12 @@
package com.gitee.sop.gateway.config;
import com.gitee.sop.gateway.service.ParamExecutor;
import com.gitee.sop.gateway.service.impl.ParamExecutorImpl;
import com.gitee.sop.gateway.service.RouteService;
import com.gitee.sop.gateway.service.impl.RouteServiceImpl;
import com.gitee.sop.gateway.service.Serde;
import com.gitee.sop.gateway.service.impl.ParamExecutorImpl;
import com.gitee.sop.gateway.service.impl.RouteServiceImpl;
import com.gitee.sop.gateway.service.impl.SerdeGsonImpl;
import com.gitee.sop.gateway.service.impl.SerdeImpl;
import com.gitee.sop.gateway.service.interceptor.internal.ResultRouteInterceptor;
import com.gitee.sop.gateway.service.manager.ApiManager;
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
import com.gitee.sop.gateway.service.manager.IsvManager;
@@ -99,13 +98,6 @@ public class GatewayConfig {
return new SerdeGsonImpl();
}
// DEFAULT ROUTE INTERCEPTOR
@Bean
@ConditionalOnMissingBean
public ResultRouteInterceptor resultRouteInterceptor() {
return new ResultRouteInterceptor();
}
@Bean
@ConditionalOnMissingBean
public ParamExecutor paramExecutor() {

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.gateway.service.interceptor;
package com.gitee.sop.gateway.interceptor;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.request.ApiRequestContext;
@@ -12,6 +12,9 @@ public interface RouteInterceptor {
/**
* 在路由转发前执行签名校验通过后会立即执行此方法
* <pre>
* 在这个方法中抛出异常会中断接口执行直接返回错误信息
* </pre>
*
* @param context context
* @param apiInfoDTO 接口信息
@@ -24,7 +27,7 @@ public interface RouteInterceptor {
*
* @param context context
* @param apiInfoDTO 接口信息
* @param result 返回结果,通常是HashMap
* @param result 业务返回结果,通常是HashMap
* @return 返回格式化后的结果, 可对原结果进行修改
*/
default Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) {

View File

@@ -0,0 +1,10 @@
package com.gitee.sop.gateway.interceptor;
/**
* @author 六如
*/
public class RouteInterceptorOrders {
public static final int RESULT_INTERCEPTOR = -1000;
}

View File

@@ -1,11 +1,12 @@
package com.gitee.sop.gateway.service.interceptor.internal;
package com.gitee.sop.gateway.interceptor.internal;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.request.ApiRequestContext;
import com.gitee.sop.gateway.service.interceptor.RouteInterceptor;
import com.gitee.sop.gateway.service.interceptor.RouteInterceptorOrders;
import com.gitee.sop.gateway.interceptor.RouteInterceptor;
import com.gitee.sop.gateway.interceptor.RouteInterceptorOrders;
import com.gitee.sop.support.dto.CommonFileData;
import com.gitee.sop.support.dto.FileData;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@@ -17,6 +18,7 @@ import java.util.Objects;
*
* @author 六如
*/
@Component
public class ResultRouteInterceptor implements RouteInterceptor {
private static final String CLASS = "class";

View File

@@ -3,7 +3,6 @@ package com.gitee.sop.gateway.response;
import com.gitee.sop.gateway.message.ErrorEnum;
import com.gitee.sop.gateway.message.IError;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Locale;
@@ -53,19 +52,41 @@ import java.util.Locale;
*
* @author 六如
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ApiResponse extends BaseResponse {
public class ApiResponse implements Response {
public static final String SUCCESS_CODE = "0";
public static final String SUCCESS_MSG = "success";
/**
* 网关异常码范围0~100 成功返回"0"
*/
private String code = SUCCESS_CODE;
/**
* 网关异常信息
*/
private String msg = "";
/**
* 返回对象
*/
private Object data;
/**
* 业务异常码
*/
private String subCode = "";
private String sub_code = "";
/**
* 业务异常信息
*/
private String subMsg = "";
private String sub_msg = "";
/**
* 解决方案
*/
private String solution;
public static ApiResponse success(Object data) {
ApiResponse apiResponse = new ApiResponse();
@@ -86,8 +107,8 @@ public class ApiResponse extends BaseResponse {
ApiResponse apiResponse = new ApiResponse();
apiResponse.setCode(error.getCode());
apiResponse.setMsg(error.getMsg());
apiResponse.setSubCode(subCode);
apiResponse.setSubMsg(subMsg);
apiResponse.setSub_code(subCode);
apiResponse.setSub_msg(subMsg);
apiResponse.setSolution(solution);
return apiResponse;
}
@@ -99,8 +120,8 @@ public class ApiResponse extends BaseResponse {
public static ApiResponse error(IError error) {
ApiResponse apiResponse = new ApiResponse();
apiResponse.setSubCode(error.getSubCode());
apiResponse.setSubMsg(error.getSubMsg());
apiResponse.setSub_code(error.getSubCode());
apiResponse.setSub_msg(error.getSubMsg());
apiResponse.setCode(error.getCode());
apiResponse.setMsg(error.getMsg());
apiResponse.setSolution(error.getSolution());
@@ -109,18 +130,7 @@ public class ApiResponse extends BaseResponse {
private static ApiResponse error(IError error, String subMsg) {
ApiResponse response = error(error);
response.setSubMsg(subMsg);
response.setSub_msg(subMsg);
return response;
}
public Response toLower() {
ApiResponseLower apiResponseLower = new ApiResponseLower();
apiResponseLower.setSub_code(this.subCode);
apiResponseLower.setSub_msg(this.subMsg);
apiResponseLower.setCode(this.subCode);
apiResponseLower.setMsg(this.subMsg);
apiResponseLower.setData(this.getData());
apiResponseLower.setSolution(this.getSolution());
return apiResponseLower;
}
}

View File

@@ -1,108 +0,0 @@
package com.gitee.sop.gateway.response;
import com.gitee.sop.gateway.exception.ApiException;
import com.gitee.sop.gateway.message.ErrorEnum;
import com.gitee.sop.gateway.message.IError;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Locale;
/**
* 默认的结果封装类.
* <pre>
*
* xml返回结果:
* <response>
* <code>50</code>
* <msg>Remote service error</msg>
* <sub_code>isv.invalid-parameter</sub_code>
* <sub_msg>非法参数</sub_msg>
* </response>
* 成功情况:
* <response>
* <code>0</code>
* <msg>成功消息</msg>
* <data>
* ...返回内容
* </data>
* </response>
*
* json返回格式
* {
* "code":"50",
* "msg":"Remote service error",
* "sub_code":"isv.invalid-parameter",
* "sub_msg":"非法参数"
* }
* 成功情况:
* {
* "code":"0",
* "msg":"成功消息内容。。。",
* "data":{
* ...返回内容
* }
* }
* </pre>
* <p>
* 字段说明:
* code:网关异常码 <br>
* msg:网关异常信息 <br>
* sub_code:业务异常码 <br>
* sub_msg:业务异常信息 <br>
*
* @author 六如
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ApiResponseLower extends BaseResponse {
/**
* 业务异常码
*/
private String sub_code = "";
/**
* 业务异常信息
*/
private String sub_msg = "";
public static ApiResponseLower success(Object data) {
ApiResponseLower apiResponse = new ApiResponseLower();
apiResponse.setCode(SUCCESS_CODE);
apiResponse.setMsg(SUCCESS_MSG);
apiResponse.setData(data);
return apiResponse;
}
public static ApiResponseLower error(ApiException e) {
IError error = e.getError();
return error(error);
}
public static ApiResponseLower error(ErrorEnum errorEnum, Locale locale, String subMsg) {
IError error = errorEnum.getError(locale);
return error(error, subMsg);
}
public static ApiResponseLower error(ErrorEnum errorEnum, Locale locale) {
IError error = errorEnum.getError(locale);
return error(error);
}
public static ApiResponseLower error(IError error) {
return error(error, error.getSubMsg());
}
public static ApiResponseLower error(IError error, String subMsg) {
ApiResponseLower apiResponse = new ApiResponseLower();
apiResponse.setSub_code(error.getSubCode());
apiResponse.setSub_msg(error.getSubMsg());
apiResponse.setCode(error.getCode());
apiResponse.setMsg(subMsg);
return apiResponse;
}
}

View File

@@ -1,33 +0,0 @@
package com.gitee.sop.gateway.response;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class BaseResponse implements Response {
public static final String SUCCESS_CODE = "0";
public static final String SUCCESS_MSG = "success";
/**
* 网关异常码范围0~100 成功返回"0"
*/
private String code = SUCCESS_CODE;
/**
* 网关异常信息
*/
private String msg = "";
/**
* 返回对象
*/
private Object data;
/**
* 解决方案
*/
private String solution;
}

View File

@@ -5,7 +5,7 @@ package com.gitee.sop.gateway.response;
*
* @author 六如
*/
public class NoCommonResponse extends BaseResponse {
public class NoCommonResponse extends ApiResponse {
public static NoCommonResponse success(Object data) {
NoCommonResponse apiResponse = new NoCommonResponse();

View File

@@ -1,14 +1,25 @@
package com.gitee.sop.gateway.service;
import com.alibaba.fastjson2.JSONObject;
import java.util.Map;
/**
* 序列化
* 序列化/反序列化
*
* @author 六如
*/
public interface Serde {
String toJSONString(Object object);
String toJson(Object object);
String toXml(Object object);
Map<String, Object> parseJson(String json);
default JSONObject parseObject(String json) {
Map<String, Object> jsonObj = parseJson(json);
return jsonObj instanceof JSONObject ? (JSONObject) jsonObj : new JSONObject(jsonObj);
}
}

View File

@@ -8,6 +8,7 @@ import com.gitee.sop.gateway.request.ApiRequest;
import com.gitee.sop.gateway.request.ApiRequestContext;
import com.gitee.sop.gateway.request.RequestFormatEnum;
import com.gitee.sop.gateway.request.UploadContext;
import com.gitee.sop.gateway.response.NoCommonResponse;
import com.gitee.sop.gateway.response.Response;
import com.gitee.sop.gateway.service.ParamExecutor;
import com.gitee.sop.gateway.service.Serde;
@@ -120,7 +121,7 @@ public class ParamExecutorImpl implements ParamExecutor<HttpServletRequest, Http
} else {
Object responseData = apiResponse;
// 不需要公共参数
if (!apiResponse.needWrap()) {
if (apiResponse instanceof NoCommonResponse || !apiResponse.needWrap()) {
responseData = data;
}
this.writerText(apiRequestContext, responseData, response);
@@ -138,7 +139,7 @@ public class ParamExecutorImpl implements ParamExecutor<HttpServletRequest, Http
response.getWriter().write(xml);
} else {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
String json = serde.toJSONString(apiResponse);
String json = serde.toJson(apiResponse);
response.getWriter().write(json);
}
}

View File

@@ -5,7 +5,6 @@ import com.gitee.sop.gateway.common.enums.YesOrNoEnum;
import com.gitee.sop.gateway.config.ApiConfig;
import com.gitee.sop.gateway.request.ApiRequestContext;
import com.gitee.sop.gateway.response.ApiResponse;
import com.gitee.sop.gateway.response.ApiResponseLower;
import com.gitee.sop.gateway.response.NoCommonResponse;
import com.gitee.sop.gateway.response.Response;
import com.gitee.sop.gateway.service.ResultWrapper;
@@ -37,11 +36,7 @@ public class ResultWrapperImpl implements ResultWrapper {
if (needNotWrap) {
return NoCommonResponse.success(result);
}
if (Objects.equals(apiConfig.getFieldLowercase(), true)) {
return ApiResponseLower.success(result);
} else {
return ApiResponse.success(result);
}
return ApiResponse.success(result);
}
private Response executeApiResponse(ApiResponse apiResponse, boolean needNotWrap) {
@@ -49,11 +44,7 @@ public class ResultWrapperImpl implements ResultWrapper {
if (needNotWrap) {
return NoCommonResponse.success(apiResponse.getData());
}
if (Objects.equals(apiConfig.getFieldLowercase(), true)) {
return apiResponse.toLower();
} else {
return apiResponse;
}
return apiResponse;
}
}

View File

@@ -6,6 +6,7 @@ import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.ParamInfoDTO;
import com.gitee.sop.gateway.exception.ApiException;
import com.gitee.sop.gateway.exception.ExceptionExecutor;
import com.gitee.sop.gateway.interceptor.RouteInterceptor;
import com.gitee.sop.gateway.message.ErrorEnum;
import com.gitee.sop.gateway.request.ApiRequest;
import com.gitee.sop.gateway.request.ApiRequestContext;
@@ -15,27 +16,28 @@ import com.gitee.sop.gateway.response.Response;
import com.gitee.sop.gateway.service.GenericServiceInvoker;
import com.gitee.sop.gateway.service.ResultWrapper;
import com.gitee.sop.gateway.service.RouteService;
import com.gitee.sop.gateway.service.interceptor.RouteInterceptor;
import com.gitee.sop.gateway.service.Serde;
import com.gitee.sop.gateway.service.validate.Validator;
import com.gitee.sop.gateway.util.ClassUtil;
import com.gitee.sop.support.dto.CommonFileData;
import com.gitee.sop.support.context.DefaultOpenContext;
import com.gitee.sop.support.dto.FileData;
import com.gitee.sop.support.context.OpenContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.PostConstruct;
import com.gitee.sop.support.dto.CommonFileData;
import com.gitee.sop.support.dto.FileData;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.ClassUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
@@ -61,6 +63,9 @@ public class RouteServiceImpl implements RouteService {
@Autowired
private ResultWrapper resultWrapper;
@Autowired
private Serde serde;
@Override
public Response route(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
@@ -68,10 +73,13 @@ public class RouteServiceImpl implements RouteService {
try {
// 接口校验
ApiInfoDTO apiInfoDTO = validator.validate(apiRequestContext);
// 执行拦截器前置动作
this.doPreRoute(apiRequestContext, apiInfoDTO);
// 微服务结果
Object result = doRoute(apiRequestContext, apiInfoDTO);
// 执行拦截器后置动作
result = this.doAfterRoute(apiRequestContext, apiInfoDTO, result);
// 结果处理
return resultWrapper.wrap(apiRequestContext, apiInfoDTO, result);
} catch (Exception e) {
log.error("接口请求报错, , ip={}, apiRequest={}", apiRequestContext.getIp(), apiRequest, e);
@@ -120,7 +128,7 @@ public class RouteServiceImpl implements RouteService {
}
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String bizContent = apiRequest.getBizContent();
JSONObject jsonObject = JSON.parseObject(bizContent);
JSONObject jsonObject = serde.parseObject(bizContent);
List<Object> params = new ArrayList<>();
for (ParamInfoDTO paramInfoDTO : paramInfoList) {
String type = paramInfoDTO.getType();
@@ -141,10 +149,13 @@ public class RouteServiceImpl implements RouteService {
} else {
if (ClassUtil.isPrimitive(type)) {
String paramName = paramInfoDTO.getName();
Object value = null;
try {
Object value = jsonObject.getObject(paramName, ClassUtils.forName(type));
if (jsonObject != null) {
value = jsonObject.getObject(paramName, ClassUtils.forName(type));
jsonObject.remove(paramName);
}
params.add(value);
jsonObject.remove(paramName);
} catch (ClassNotFoundException e) {
log.error("找不到参数class, paramInfoDTO={}, apiRequest={}", paramInfoDTO, apiRequest, e);
throw new RuntimeException("找不到class:" + type, e);

View File

@@ -3,7 +3,12 @@ package com.gitee.sop.gateway.service.impl;
import com.alibaba.nacos.shaded.com.google.gson.Gson;
import com.alibaba.nacos.shaded.com.google.gson.GsonBuilder;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 序列化/反序列化 gson实现
*
* @author 六如
*/
public class SerdeGsonImpl extends SerdeImpl {
@@ -11,12 +16,22 @@ public class SerdeGsonImpl extends SerdeImpl {
Gson gson;
@Override
public String toJSONString(Object object) {
public String toJson(Object object) {
return gson.toJson(object);
}
@Override
protected void doInit() {
gson = new GsonBuilder().setDateFormat(dateFormat).create();
public Map<String, Object> parseJson(String json) {
return gson.fromJson(json, LinkedHashMap.class);
}
@Override
protected void doInit() {
gson = new GsonBuilder()
.setDateFormat(dateFormat)
.create();
}
}

View File

@@ -1,27 +1,35 @@
package com.gitee.sop.gateway.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.gitee.sop.gateway.config.ApiConfig;
import com.gitee.sop.gateway.service.Serde;
import com.gitee.sop.gateway.util.XmlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.Map;
/**
* @author 六如
*/
public class SerdeImpl implements Serde {
static JSONWriter.Context context;
static JSONWriter.Context WRITE_CONTEXT;
@Autowired
protected ApiConfig apiConfig;
@Value("${gateway.serialize.date-format}")
protected String dateFormat;
@Override
public String toJSONString(Object object) {
return JSON.toJSONString(object, context);
public String toJson(Object object) {
return JSON.toJSONString(object);
}
@Override
@@ -33,10 +41,15 @@ public class SerdeImpl implements Serde {
}
}
@Override
public Map<String, Object> parseJson(String json) {
return JSON.parseObject(json);
}
@PostConstruct
public void init() {
context = new JSONWriter.Context();
context.setDateFormat(dateFormat);
WRITE_CONTEXT = new JSONWriter.Context();
WRITE_CONTEXT.setDateFormat(dateFormat);
this.doInit();
}

View File

@@ -1,11 +0,0 @@
package com.gitee.sop.gateway.service.interceptor;
/**
* @author 六如
*/
public class RouteInterceptorOrders {
public static final int RESULT_INTERCEPTOR = -1000;
public static final int RESULT_WRAPPER_INTERCEPTOR = RESULT_INTERCEPTOR + 1;
}

View File

@@ -1,38 +0,0 @@
package com.gitee.sop.gateway.service.interceptor.internal;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.config.ApiConfig;
import com.gitee.sop.gateway.request.ApiRequestContext;
import com.gitee.sop.gateway.response.ApiResponse;
import com.gitee.sop.gateway.response.ApiResponseLower;
import com.gitee.sop.gateway.service.interceptor.RouteInterceptor;
import com.gitee.sop.gateway.service.interceptor.RouteInterceptorOrders;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Objects;
/**
* 对结果进行包裹
*
* @author 六如
*/
public class ResultWrapperInterceptor implements RouteInterceptor {
@Autowired
private ApiConfig apiConfig;
@Override
public Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) {
if (Objects.equals(apiConfig.getFieldLowercase(), true)) {
return ApiResponseLower.success(result);
} else {
return ApiResponse.success(result);
}
}
@Override
public int getOrder() {
return RouteInterceptorOrders.RESULT_WRAPPER_INTERCEPTOR;
}
}

View File

@@ -0,0 +1,46 @@
package com.gitee.sop.gateway.util;
/**
* @author 六如
*/
public class FieldUtil {
private static final String REGEX = "([a-z])([A-Z])";
private static final String REGEX_VAL = "$1_$2";
private static final char UNDERLINE = '_';
/**
* 驼峰转下划线
*
* @return 返回下划线
*/
public static String camelCaseToSnakeCase(String name) {
return name.replaceAll(REGEX, REGEX_VAL).toLowerCase();
}
/**
* 下划线转驼峰
*
* @param param 内容
* @return 返回转换后的字符串
*/
public static String snakeCaseToCamelCase(String param) {
if (param == null || param.trim().isEmpty()) {
return "";
}
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (c == UNDERLINE) {
if (++i < len) {
sb.append(Character.toUpperCase(param.charAt(i)));
}
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@@ -34,4 +34,5 @@ public class JsonUtil {
return JSON.parseArray(value, clazz);
}
}

View File

@@ -1,5 +1,5 @@
spring.profiles.active=dev
spring.application.name=sop-index
spring.application.name=sop-gateway
server.port=8081
####### gateway config #######
@@ -40,8 +40,6 @@ api.timeout-seconds=300
api.timestamp-pattern=yyyy-MM-dd HH:mm:ss
# default zone
api.zone-id=Asia/Shanghai
# if true, response field name all lowercase, such as : sub_code, sub_msg
api.field-lowercase=false
####### dubbo config #######
dubbo.protocol.name=dubbo