mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
5.0
This commit is contained in:
@@ -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
|
||||
|
@@ -1,5 +1,3 @@
|
||||
# 服务示例
|
||||
|
||||
- sop-story:微服务示例
|
||||
- sop-auth:应用授权示例
|
||||
- sop-springmvc:springmvc工程实例
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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";
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
@@ -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";
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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]);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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=";
|
||||
|
||||
|
Reference in New Issue
Block a user