This commit is contained in:
六如
2024-09-11 00:03:51 +08:00
parent d4823ae473
commit cccafde158
18 changed files with 231 additions and 16 deletions

View File

@@ -86,7 +86,7 @@ open.error_40001_isv.missing-timestamp=\u7F3A\u5C11\u65F6\u95F4\u6233\u53C2\u657
open.error_40001_isv.missing-version=\u7F3A\u5C11\u7248\u672C\u53C2\u6570
open.error_40001_isv.decryption-error-missing-encrypt-type=\u89E3\u5BC6\u51FA\u9519, \u672A\u6307\u5B9A\u52A0\u5BC6\u7B97\u6CD5
open.error_40002=\u975E\u6CD5\u7684\u53C2\u6570
open.error_40002=\u53c2\u6570\u9519\u8bef
open.error_40002_isv.invalid-parameter=\u53C2\u6570\u65E0\u6548
open.error_40002_isv.upload-fail=\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25
open.error_40002_isv.invalid-file-extension=\u6587\u4EF6\u6269\u5C55\u540D\u65E0\u6548

View File

@@ -1,5 +1,3 @@
# 服务示例
- sop-story微服务示例
- sop-auth应用授权示例
- sop-springmvcspringmvc工程实例

View File

@@ -12,6 +12,9 @@ public interface StoryService {
@Open("story.save")
Integer save(StorySaveDTO storySaveDTO);
@Open("story.update")
Integer update(Integer id, StorySaveDTO storySaveDTO);
@Open("story.get")
StoryResponse getById(Integer id);

View File

@@ -1,9 +1,9 @@
package com.gitee.sop.storyweb.open.impl;
import com.gitee.sop.support.annotation.Open;
import com.gitee.sop.storyweb.open.StoryService;
import com.gitee.sop.storyweb.open.req.StorySaveDTO;
import com.gitee.sop.storyweb.open.resp.StoryResponse;
import com.gitee.sop.support.context.OpenContext;
import org.apache.dubbo.config.annotation.DubboService;
import javax.validation.constraints.NotNull;
@@ -20,6 +20,18 @@ public class StoreyServiceImpl implements StoryService {
@Override
public Integer save(StorySaveDTO storySaveDTO) {
System.out.println("save storySaveDTO:" + storySaveDTO);
System.out.println("appId:" + OpenContext.getAppId());
System.out.println("apiName:" + OpenContext.getApiName());
System.out.println("version:" + OpenContext.getVersion());
System.out.println("token:" + OpenContext.getAppAuthToken());
System.out.println("ip:" + OpenContext.getClientIp());
System.out.println("traceId:" + OpenContext.getTraceId());
return 1;
}
@Override
public Integer update(Integer id, StorySaveDTO storySaveDTO) {
System.out.println("update, id:" + id + ", storySaveDTO=" + storySaveDTO);
return 1;
}

View File

@@ -42,7 +42,7 @@ public class ApiRequest implements Serializable {
/**
* 接口名称
*
* @mock alipay.trade.fastpay.refund.query
* @mock shop.goods.get
*/
@NotBlank(message = "method不能为空")
@Length(max = 128)
@@ -62,9 +62,8 @@ public class ApiRequest implements Serializable {
*
* @mock utf-8
*/
@NotBlank(message = "charset不能为空")
@Length(max = 10)
private String charset;
private String charset = "utf-8";
/**
* 商户生成签名字符串所使用的签名算法类型目前支持RSA2和RSA推荐使用RSA2

View File

@@ -4,6 +4,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Locale;
/**
@@ -13,9 +14,24 @@ import java.util.Locale;
@Getter
@Setter
public class ApiRequestContext {
/**
* 请求参数
*/
private ApiRequest apiRequest;
/**
* 本地语言
*/
private Locale locale;
/**
* 客户端ip
*/
private String ip;
/**
* traceId
*/
private String traceId;
/**
* 上传文件
*/
private List<FileInfo> files;
}

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.index.common;
/**
* @author 六如
*/
public class AttachmentNames {
/** 分配给开发者的应用ID */
public static final String APP_ID_NAME = "client.app_id";
/** 接口名称 */
public static final String API_NAME = "client.method";
/** 调用的接口版本 */
public static final String VERSION_NAME = "client.version";
/** 开放平台主动通知商户服务器里指定的页面http/https路径 */
public static final String NOTIFY_URL_NAME = "client.notify_url";
/** OAuth 2.0授权token */
public static final String APP_AUTH_TOKEN_NAME = "client.app_auth_token";
public static final String CLIENT_IP = "client.ip";
public static final String TRACE_ID = "client.trace_id";
}

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.index.common;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class FileInfo {
/**
* 文件名,包含后缀,如:aa.jpg
*/
private String filename;
/**
* 文件内容
*/
private byte[] content;
}

View File

@@ -20,7 +20,7 @@ package com.gitee.sop.index.common;
*/
public class ParamNames {
/** 分配给开发者的应用ID */
public static String APP_KEY_NAME = "app_id";
public static String APP_ID_NAME = "app_id";
/** 接口名称 */
public static String API_NAME = "method";
/** 仅支持JSON */
@@ -42,10 +42,13 @@ public class ParamNames {
/** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */
public static String BIZ_CONTENT_NAME = "biz_content";
// -----
/** 时间戳格式 */
public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static String HEADER_VERSION_NAME = "sop-version";
public static String CLIENT_IP = "client_ip";
}

View File

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
/**
* 开放平台入口
@@ -35,12 +36,13 @@ public class IndexController {
* @return 返回响应内容
*/
@PostMapping("api")
public ApiResponse index(@RequestBody ApiRequest apiRequest, HttpServletRequest request) {
public ApiResponse index(ApiRequest apiRequest, HttpServletRequest request) {
String ip = RequestUtil.getIP(request);
ApiRequestContext apiRequestContext = ApiRequestContext.builder()
.apiRequest(apiRequest)
.locale(request.getLocale())
.ip(ip)
.traceId(UUID.randomUUID().toString().replace("-", ""))
.build();
return routeService.route(apiRequestContext);
}

View File

@@ -6,12 +6,16 @@ import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ApiResponse;
import com.gitee.sop.index.common.AttachmentNames;
import com.gitee.sop.index.common.ParamInfoDTO;
import com.gitee.sop.index.common.ParamNames;
import com.gitee.sop.index.exception.ExceptionExecutor;
import com.gitee.sop.index.service.validate.Validator;
import com.gitee.sop.index.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.ClassUtils;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcContextAttachment;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
@@ -57,6 +61,7 @@ public class RouteServiceImpl implements RouteService {
}
protected Object doRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfo) {
setAttachment(apiRequestContext);
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String paramInfo = apiInfo.getParamInfo();
List<ParamInfoDTO> paramInfoList = JSON.parseArray(paramInfo, ParamInfoDTO.class);
@@ -72,6 +77,18 @@ public class RouteServiceImpl implements RouteService {
return result;
}
protected void setAttachment(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
RpcContextAttachment clientAttachment = RpcContext.getClientAttachment();
clientAttachment.setAttachment(AttachmentNames.APP_ID_NAME, apiRequest.getApp_id());
clientAttachment.setAttachment(AttachmentNames.API_NAME, apiRequest.getMethod());
clientAttachment.setAttachment(AttachmentNames.VERSION_NAME, apiRequest.getVersion());
clientAttachment.setAttachment(AttachmentNames.APP_AUTH_TOKEN_NAME, apiRequest.getApp_auth_token());
clientAttachment.setAttachment(AttachmentNames.NOTIFY_URL_NAME, apiRequest.getNotify_url());
clientAttachment.setAttachment(AttachmentNames.CLIENT_IP, apiRequestContext.getIp());
clientAttachment.setAttachment(AttachmentNames.TRACE_ID, apiRequestContext.getTraceId());
}
protected String[] buildParamType(List<ParamInfoDTO> paramInfoList) {
if (ObjectUtils.isEmpty(paramInfoList)) {
return new String[0];
@@ -90,16 +107,17 @@ public class RouteServiceImpl implements RouteService {
List<Object> params = new ArrayList<>();
for (ParamInfoDTO paramInfoDTO : paramInfoList) {
if (ClassUtil.isPrimitive(paramInfoDTO.getType())) {
String paramName = paramInfoDTO.getName();
try {
Object value = jsonObject.getObject(paramInfoDTO.getName(), ClassUtils.forName(paramInfoDTO.getType()));
Object value = jsonObject.getObject(paramName, ClassUtils.forName(paramInfoDTO.getType()));
params.add(value);
jsonObject.remove(paramName);
} catch (ClassNotFoundException e) {
log.error("找不到参数class, paramInfoDTO={}, apiRequest={}", paramInfoDTO, apiRequest, e);
throw new RuntimeException("找不到class:" + paramInfoDTO.getType(), e);
}
} else {
Object value = jsonObject.getObject(paramInfoDTO.getName(), Object.class);
params.add(value);
params.add(jsonObject);
}
}
return params.toArray(new Object[0]);

View File

@@ -126,6 +126,24 @@ public class ApiValidator implements Validator {
if (ObjectUtils.isEmpty(apiRequest.getApp_id())) {
throw new ApiException(ErrorEnum.ISV_MISSING_APP_ID, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getMethod())) {
throw new ApiException(ErrorEnum.ISV_MISSING_METHOD, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getVersion())) {
throw new ApiException(ErrorEnum.ISV_MISSING_VERSION, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getSign_type())) {
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getCharset())) {
throw new ApiException(ErrorEnum.ISV_INVALID_CHARSET, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getSign())) {
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, locale);
}
if (ObjectUtils.isEmpty(apiRequest.getTimestamp())) {
throw new ApiException(ErrorEnum.ISV_MISSING_TIMESTAMP, locale);
}
}
/**

View File

@@ -52,7 +52,7 @@ public class AlipaySigner implements Signer {
private static Map<String, String> buildParams(ApiRequest apiRequest) {
Map<String, String> params = new SkipNullHashMap(20);
params.put(ParamNames.APP_KEY_NAME, apiRequest.getApp_id());
params.put(ParamNames.APP_ID_NAME, apiRequest.getApp_id());
params.put(ParamNames.API_NAME, apiRequest.getMethod());
params.put(ParamNames.FORMAT_NAME, apiRequest.getFormat());
params.put(ParamNames.CHARSET_NAME, apiRequest.getCharset());

View File

@@ -4,6 +4,11 @@ package com.gitee.sop.registry;
* @author 六如
*/
public class RegistryMain {
/*
内置简单的注册中心,基于zookeeper,用来开发演示使用.
生产环境不可使用!
*/
public static void main(String[] args) {
int zkPort = 2181;
System.out.println("启动内置zookeeper注册中心(仅在开发环境下使用),port=" + zkPort);

View File

@@ -20,6 +20,11 @@
<artifactId>sop-index-api</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -1,10 +1,68 @@
package com.gitee.sop.support.context;
import org.apache.dubbo.rpc.RpcContext;
/**
* @author 六如
*/
public class OpenContext {
/** 分配给开发者的应用ID */
private static final String APP_ID_NAME = "client.app_id";
/** 接口名称 */
private static final String API_NAME = "client.method";
/** 调用的接口版本 */
private static final String VERSION_NAME = "client.version";
/** 开放平台主动通知商户服务器里指定的页面http/https路径 */
private static final String NOTIFY_URL_NAME = "client.notify_url";
/** OAuth 2.0授权token */
private static final String APP_AUTH_TOKEN_NAME = "client.app_auth_token";
private static final String CLIENT_IP = "client.ip";
private static final String TRACE_ID = "client.trace_id";
/**
* 获取appId
*/
public static String getAppId() {
return RpcContext.getServerAttachment().getAttachment(APP_ID_NAME);
}
/**
* 获取apiName
*/
public static String getApiName() {
return RpcContext.getServerAttachment().getAttachment(API_NAME);
}
/**
* 获取version
*/
public static String getVersion() {
return RpcContext.getServerAttachment().getAttachment(VERSION_NAME);
}
/**
* 获取token,没有返回null
*/
public static String getAppAuthToken() {
return RpcContext.getServerAttachment().getAttachment(APP_AUTH_TOKEN_NAME);
}
/**
* 获取客户端ip
*/
public static String getClientIp() {
return RpcContext.getServerAttachment().getAttachment(CLIENT_IP);
}
/**
* 获取回调地址
*/
public static String getNotifyUrl() {
return RpcContext.getServerAttachment().getAttachment(NOTIFY_URL_NAME);
}
public static String getTraceId() {
return RpcContext.getServerAttachment().getAttachment(TRACE_ID);
}
}

View File

@@ -138,5 +138,41 @@ public class AlipayClientPostTest extends TestBase {
System.out.println(responseData);
}
@Test
public void testUpdate() throws Exception {
// 公共请求参数
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("method", "story.update");
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
params.put("version", "1.0");
// 业务参数
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("id", "1");
bizContent.put("storyName", "花仙子");
bizContent.put("addTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
params.put("biz_content", JSON.toJSONString(bizContent));
String content = AlipaySignature.getSignContent(params);
String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8");
params.put("sign", sign);
System.out.println("----------- 请求信息 -----------");
System.out.println("请求参数:" + buildParamQuery(params));
System.out.println("商户秘钥:" + privateKey);
System.out.println("待签名内容:" + content);
System.out.println("签名(sign)" + sign);
System.out.println("URL参数" + buildUrlQuery(params));
System.out.println("----------- 返回结果 -----------");
String responseData = postJson(url, params);// 发送请求
System.out.println(responseData);
}
}

View File

@@ -23,7 +23,7 @@ import java.util.Optional;
*/
public class AllInOneTest extends TestBase {
String url = "http://localhost:8081";
String url = "http://localhost:8081/api";
String appId = "2019032617262200001";
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";