init
This commit is contained in:
commit
69d2d09f96
|
@ -0,0 +1,20 @@
|
|||
*.class
|
||||
*.iml
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
#workspace Files
|
||||
|
||||
node_modules/
|
||||
dist/
|
||||
.factorypath
|
||||
target/
|
||||
.settings/
|
||||
.factorypath
|
||||
.classpath
|
||||
.project
|
|
@ -0,0 +1,147 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>rop</name>
|
||||
<description>restful open platform</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.8.RELEASE</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop-core</artifactId>
|
||||
<version>${axe-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop-server</artifactId>
|
||||
<version>${axe-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop-client</artifactId>
|
||||
<version>${axe-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<properties>
|
||||
<axe-version>1.0</axe-version>
|
||||
</properties>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<name>kerbores</name>
|
||||
<email>kerbores@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection>scm:https://github.com/ZHCS-CLUB/AXE.git</connection>
|
||||
<developerConnection>scm:https://github.com/ZHCS-CLUB/AXE.git</developerConnection>
|
||||
<url>https://github.com/ZHCS-CLUB/AXE.git</url>
|
||||
</scm>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>${javadoc.opts}</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.6.3</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>ossrh</serverId>
|
||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>nutz</id>
|
||||
<url>http://jfrog.nutz.cn/artifactory/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<url>http://jfrog.nutz.cn/artifactory/soho-project/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.nutz</groupId>
|
||||
<artifactId>nutz</artifactId>
|
||||
<version>[1.r.68,)</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<modules>
|
||||
<module>rop-core</module>
|
||||
<module>rop-server</module>
|
||||
<module>rop-client</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -0,0 +1,31 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<artifactId>rop-client</artifactId>
|
||||
<name>rop-client</name>
|
||||
<description>rop invoke client integration</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,25 @@
|
|||
package cn.com.chinarecrm.rop.client;
|
||||
|
||||
import cn.com.chinarecrm.rop.core.signer.Signer;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public interface ClientSigner extends Signer {
|
||||
/**
|
||||
* 客户端签名
|
||||
*
|
||||
* @param appSecret
|
||||
* 应用密钥
|
||||
* @param timestamp
|
||||
* 时间戳
|
||||
* @param gateway
|
||||
* 方法/路由
|
||||
* @param nonce
|
||||
* 随机串
|
||||
* @param request
|
||||
* 请求
|
||||
* @return 签名字符串
|
||||
*/
|
||||
public String sign(String appSecret, String timestamp, String gateway, String nonce, ROPRequest request);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package cn.com.chinarecrm.rop.client;
|
||||
|
||||
import org.nutz.http.Header;
|
||||
import org.nutz.http.Request;
|
||||
import org.nutz.http.Request.METHOD;
|
||||
import org.nutz.http.Response;
|
||||
import org.nutz.http.Sender;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.Times;
|
||||
import org.nutz.lang.random.R;
|
||||
import org.nutz.log.Log;
|
||||
import org.nutz.log.Logs;
|
||||
|
||||
import cn.com.chinarecrm.rop.ROPConfig;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROPClient {
|
||||
public static ROPClient create(String appKey, String appSecret, String endpoint, String digestName) {
|
||||
ROPClient client = new ROPClient();
|
||||
client.setAppKey(appKey);
|
||||
client.setAppSecret(appSecret);
|
||||
client.setEndpoint(endpoint);
|
||||
client.setDigestName(digestName);
|
||||
return client;
|
||||
}
|
||||
|
||||
private String appKey;
|
||||
private String appSecret;
|
||||
private String digestName;
|
||||
|
||||
private String endpoint;// 调用点
|
||||
|
||||
Log log = Logs.get();
|
||||
|
||||
private ROPClient() {}
|
||||
|
||||
public String getAppKey() {
|
||||
return appKey;
|
||||
}
|
||||
|
||||
public String getAppSecret() {
|
||||
return appSecret;
|
||||
}
|
||||
|
||||
public String getDigestName() {
|
||||
return digestName;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public Response send(ROPRequest request) {
|
||||
return Sender.create(toRequest(request)).send();
|
||||
}
|
||||
|
||||
public void setAppKey(String appKey) {
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public void setAppSecret(String appSecret) {
|
||||
this.appSecret = appSecret;
|
||||
}
|
||||
|
||||
public void setDigestName(String digestName) {
|
||||
this.digestName = digestName;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理header
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private Header signHeader(ROPRequest request) {
|
||||
String nonce = R.UU16();
|
||||
String ts = Times.now().getTime() + "";
|
||||
Header header = null;
|
||||
if (request.getMethod() == METHOD.GET) {
|
||||
String query = request.getURLEncodedParams();
|
||||
String method = Strings.isBlank(query) ? request.getGateway() : request.getGateway() + "?" + query;
|
||||
header = request.getHeader()
|
||||
.set(ROPConfig.APP_KEY_KEY, appKey)
|
||||
.set(ROPConfig.METHOD_KEY, method)
|
||||
.set(ROPConfig.NONCE_KEY, nonce)
|
||||
.set(ROPConfig.TS_KEY, ts)
|
||||
.set(ROPConfig.SIGN_KEY, new ROPClientDigestSigner(digestName).sign(appSecret, ts, method, nonce, request));
|
||||
} else {
|
||||
header = request.getHeader()
|
||||
.set(ROPConfig.APP_KEY_KEY, appKey)
|
||||
.set(ROPConfig.METHOD_KEY, request.getGateway())
|
||||
.set(ROPConfig.NONCE_KEY, nonce)
|
||||
.set(ROPConfig.TS_KEY, ts)
|
||||
.set(ROPConfig.SIGN_KEY,
|
||||
new ROPClientDigestSigner(digestName).sign(appSecret, ts, request.getGateway(), nonce, request));
|
||||
}
|
||||
return request.getData() == null || request.getData().length == 0 ? header.asFormContentType() : header.asJsonContentType();
|
||||
}
|
||||
|
||||
public Request toRequest(ROPRequest request) {
|
||||
Request req = Request.create(endpoint, request.getMethod());
|
||||
req.setParams(request.getParams());
|
||||
req.setData(request.getData());
|
||||
req.setHeader(signHeader(request));
|
||||
Header header = req.getHeader();
|
||||
log.debugf("send headers %s", header);
|
||||
return req;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cn.com.chinarecrm.rop.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Streams;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
import cn.com.chinarecrm.rop.core.signer.DigestSigner;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROPClientDigestSigner extends DigestSigner implements ClientSigner {
|
||||
|
||||
public ROPClientDigestSigner(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected String getDataMate(ROPRequest request) {
|
||||
if (request.isGet()) {
|
||||
String query = request.getURLEncodedParams();
|
||||
String method = Strings.isBlank(query) ? request.getGateway() : request.getGateway() + "?" + query;
|
||||
query = method.indexOf('?') >= 0 ? method.substring(method.indexOf('?') + 1) : "";
|
||||
return Lang.md5(query);
|
||||
}
|
||||
StringBuilder info;
|
||||
try {
|
||||
info = Streams.read(new InputStreamReader(request.getInputStream()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw Lang.wrapThrow(e);
|
||||
}
|
||||
return Lang.md5(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sign(String appSecret, String timestamp, String gateway, String nonce, ROPRequest request) {
|
||||
return sign(appSecret, timestamp, gateway, nonce, getDataMate(request));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
package cn.com.chinarecrm.rop.client;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.nutz.http.Cookie;
|
||||
import org.nutz.http.Header;
|
||||
import org.nutz.http.Http;
|
||||
import org.nutz.http.Request.METHOD;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.ContinueLoop;
|
||||
import org.nutz.lang.Each;
|
||||
import org.nutz.lang.Encoding;
|
||||
import org.nutz.lang.ExitLoop;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.LoopException;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import cn.com.chinarecrm.rop.core.signer.SignerHelper;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROPRequest {
|
||||
public static ROPRequest create(String gateway, METHOD method) {
|
||||
return create(gateway, method, new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
public static ROPRequest create(String gateway, METHOD method, Map<String, Object> params) {
|
||||
return ROPRequest.create(gateway, method, params, Header.create());
|
||||
}
|
||||
|
||||
public static ROPRequest create(String gateway, METHOD method, Map<String, Object> params, Header header) {
|
||||
return new ROPRequest().setMethod(method).setParams(params).setGateway(gateway).setHeader(header);
|
||||
}
|
||||
|
||||
public static ROPRequest create(String gateway, METHOD method, String paramsAsJson) {
|
||||
return create(gateway, method, (Map<String, Object>) Json.fromJson(paramsAsJson));
|
||||
}
|
||||
|
||||
public static ROPRequest create(String gateway, METHOD method, String paramsAsJson, Header header) {
|
||||
return create(gateway, method, (Map<String, Object>) Json.fromJson(paramsAsJson), header);
|
||||
}
|
||||
|
||||
public static ROPRequest get(String gateway) {
|
||||
return create(gateway, METHOD.GET, new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
public static ROPRequest get(String gateway, Header header) {
|
||||
return ROPRequest.create(gateway, METHOD.GET, new HashMap<String, Object>(), header);
|
||||
}
|
||||
|
||||
public static ROPRequest post(String gateway) {
|
||||
return create(gateway, METHOD.POST, new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
public static ROPRequest post(String gateway, Header header) {
|
||||
return ROPRequest.create(gateway, METHOD.POST, new HashMap<String, Object>(), header);
|
||||
}
|
||||
|
||||
private byte[] data;
|
||||
|
||||
private String enc = Encoding.UTF8;
|
||||
private String gateway;
|
||||
private Header header;
|
||||
private InputStream inputStream;
|
||||
private METHOD method;
|
||||
private Map<String, Object> params;
|
||||
|
||||
private ROPRequest() {}
|
||||
|
||||
protected void fileUpload(final StringBuilder sb) {
|
||||
List<String> keys = new ArrayList<>(params.keySet());
|
||||
Collections.sort(keys);
|
||||
for (final String key : keys) {
|
||||
Object val = params.get(key);
|
||||
if (val == null)
|
||||
val = "";
|
||||
Lang.each(val, new Each<Object>() {
|
||||
@Override
|
||||
public void invoke(int index, Object ele, int length)
|
||||
throws ExitLoop, ContinueLoop, LoopException {
|
||||
if (ele instanceof File) {
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(Lang.md5((File) ele), enc))
|
||||
.append('&');
|
||||
} else {
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(ele, enc))
|
||||
.append('&');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Cookie getCookie() {
|
||||
String s = header.get("Cookie");
|
||||
if (null == s)
|
||||
return new Cookie();
|
||||
return new Cookie(s);
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getEnc() {
|
||||
return enc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the gateway
|
||||
*/
|
||||
public String getGateway() {
|
||||
return gateway;
|
||||
}
|
||||
|
||||
public Header getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
if (inputStream != null) {
|
||||
return inputStream;
|
||||
} else {
|
||||
if (header.get("Content-Type") == null)
|
||||
header.asFormContentType(enc);
|
||||
if (null == data) {
|
||||
try {
|
||||
return new ByteArrayInputStream(getURLEncodedParams().getBytes(enc));
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw Lang.wrapThrow(e);
|
||||
}
|
||||
}
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
}
|
||||
|
||||
public METHOD getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public String getURLEncodedParams() {
|
||||
// 此处不影响发送的数据,只影响签名,需要按照rop的规则进行签名串的获取即可
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (isFileUpload()) {// 文件上传的签名流
|
||||
fileUpload(sb);
|
||||
} else if (params != null) {
|
||||
return SignerHelper.mapAsUrlParams(params, enc);
|
||||
}
|
||||
if (sb.length() > 0)
|
||||
sb.setLength(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public ROPRequest header(String key, String value) {
|
||||
getHeader().set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isDelete() {
|
||||
return METHOD.DELETE == method;
|
||||
}
|
||||
|
||||
public boolean isFileUpload() {
|
||||
final NutMap t = NutMap.NEW().addv("target", false);
|
||||
if ((isPost() || isPut()) && getParams() != null) {
|
||||
for (Object val : getParams().values()) {
|
||||
Lang.each(val, new Each<Object>() {
|
||||
|
||||
@Override
|
||||
public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException {
|
||||
if (ele instanceof File) {
|
||||
t.put("target", true);
|
||||
throw new ExitLoop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return t.getBoolean("target");
|
||||
}
|
||||
|
||||
public boolean isGet() {
|
||||
return METHOD.GET == method;
|
||||
}
|
||||
|
||||
public boolean isPost() {
|
||||
return METHOD.POST == method;
|
||||
}
|
||||
|
||||
public boolean isPut() {
|
||||
return METHOD.PUT == method;
|
||||
}
|
||||
|
||||
public ROPRequest setCookie(Cookie cookie) {
|
||||
header.set("Cookie", cookie.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setData(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setData(String data) {
|
||||
this.data = data.getBytes(StandardCharsets.UTF_8);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setEnc(String reqEnc) {
|
||||
if (reqEnc != null)
|
||||
this.enc = reqEnc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setGateway(String gateway) {
|
||||
this.gateway = gateway;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setHeader(Header header) {
|
||||
if (header == null)
|
||||
header = Header.create();
|
||||
this.header = header;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setMethod(METHOD method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPRequest setParams(Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cn.com.chinarecrm.rop.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import cn.com.chinarecrm.rop.client.ROPClient;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ROPClientConfigurationProperties.class)
|
||||
public class ROPClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ROPClient ropClient(ROPClientConfigurationProperties configProperties) {
|
||||
return ROPClient.create(configProperties.getAppKey(),
|
||||
configProperties.getAppSecret(),
|
||||
configProperties.getEndpoint(),
|
||||
configProperties.getDigestName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package cn.com.chinarecrm.rop.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "rop.client")
|
||||
public class ROPClientConfigurationProperties {
|
||||
/**
|
||||
* appKey
|
||||
*/
|
||||
String appKey;
|
||||
/**
|
||||
* appSecret
|
||||
*/
|
||||
String appSecret;
|
||||
/**
|
||||
* 签名算法
|
||||
*/
|
||||
String digestName;
|
||||
/**
|
||||
* 对方接口地址
|
||||
*/
|
||||
String endpoint;
|
||||
|
||||
public String getAppKey() {
|
||||
return appKey;
|
||||
}
|
||||
|
||||
public String getAppSecret() {
|
||||
return appSecret;
|
||||
}
|
||||
|
||||
public String getDigestName() {
|
||||
return digestName;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setAppKey(String appKey) {
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public void setAppSecret(String appSecret) {
|
||||
this.appSecret = appSecret;
|
||||
}
|
||||
|
||||
public void setDigestName(String digestName) {
|
||||
this.digestName = digestName;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.com.chinarecrm.rop.config.ROPClientAutoConfiguration
|
|
@ -0,0 +1 @@
|
|||
provides: chinare-rop-client
|
|
@ -0,0 +1,20 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<artifactId>rop-core</artifactId>
|
||||
<name>rop-core</name>
|
||||
<description>rop core for sign and check</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,38 @@
|
|||
package cn.com.chinarecrm.rop;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROP {
|
||||
public static final String DESCRIPTION = "chinare rest open platform";
|
||||
public static final int MAJOR_VERSION = 1;
|
||||
public static final int MINOR_VERSION = 0;
|
||||
public static final String NAME = "rop";
|
||||
public static final String RELEASE_LEVEL = "b";
|
||||
public static final boolean SNAPSHOT = false;
|
||||
|
||||
public static int majorVersion() {
|
||||
return MAJOR_VERSION;
|
||||
}
|
||||
|
||||
public static int minorVersion() {
|
||||
return MINOR_VERSION;
|
||||
}
|
||||
|
||||
public static String releaseLevel() {
|
||||
return RELEASE_LEVEL;
|
||||
}
|
||||
|
||||
public static String v() {
|
||||
return String.format("%d.%s.%d%s",
|
||||
majorVersion(),
|
||||
releaseLevel(),
|
||||
minorVersion(),
|
||||
SNAPSHOT ? ".SNAPSHOT" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private ROP() {}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package cn.com.chinarecrm.rop;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROPConfig {
|
||||
public static final String APP_KEY_KEY = "rop-sign-appkey";
|
||||
public static final String APP_SECRET_KEY = "rop-sign-appsecret";
|
||||
public static final String METHOD_KEY = "rop-service-method";
|
||||
public static final String NONCE_KEY = "rop-nonce";
|
||||
public static final String PARAS_KEY = "rop-paras";
|
||||
public static final String SIGNER_KEY = "rop-signer-name";
|
||||
public static final String SIGN_KEY = "rop-sign";
|
||||
public static final String TS_KEY = "rop-ts";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private ROPConfig() {}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package cn.com.chinarecrm.rop.core;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public enum OperationState {
|
||||
/**
|
||||
* 默认
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* 异常
|
||||
*/
|
||||
EXCEPTION,
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
FAIL,
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* 未登录
|
||||
*/
|
||||
UNLOGINED
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package cn.com.chinarecrm.rop.core;
|
||||
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.json.JsonFormat;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class ROPData<T> {
|
||||
|
||||
/**
|
||||
* 创建一个异常结果
|
||||
*
|
||||
* @return 一个异常结果实例,不携带异常信息
|
||||
*/
|
||||
public static ROPData exception() {
|
||||
return ROPData.me().setOperationState(OperationState.EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个异常结果
|
||||
*
|
||||
* @param e
|
||||
* 异常
|
||||
* @return 一个异常结果实例,包含参数异常的信息
|
||||
*/
|
||||
public static ROPData exception(Exception e) {
|
||||
return ROPData.exception(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个异常结果
|
||||
*
|
||||
* @param msg
|
||||
* 异常信息
|
||||
* @return 一个异常结果实例,不携带异常信息
|
||||
*/
|
||||
public static ROPData exception(String msg) {
|
||||
return ROPData.exception().setMsg(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个带失败信息的ROPData
|
||||
*
|
||||
* @param reason
|
||||
* 失败原因
|
||||
* @return ROPData实例
|
||||
*/
|
||||
public static ROPData fail(String reason) {
|
||||
return ROPData.me().setOperationState(OperationState.FAIL).setMsg(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个ROPData实例
|
||||
*
|
||||
* @return 一个不携带任何信息的ROPData实例
|
||||
*/
|
||||
public static ROPData me() {
|
||||
return new ROPData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个成功结果
|
||||
*
|
||||
* @return ROPData实例状态为成功无数据携带
|
||||
*/
|
||||
public static ROPData success() {
|
||||
return ROPData.me().setOperationState(OperationState.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录
|
||||
*
|
||||
* @return 未登录
|
||||
*/
|
||||
public static ROPData unlogin() {
|
||||
return ROPData.me().setOperationState(OperationState.UNLOGINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作结果数据 假设一个操作要返回很多的数据 一个用户名 一个产品 一个相关产品列表 一个产品的评论信息列表 我们以key
|
||||
* value形式进行保存,页面获取data对象读取其对于的value即可
|
||||
*/
|
||||
private T data;
|
||||
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 带状态的操作 比如登录有成功和失败
|
||||
*/
|
||||
private OperationState operationState = OperationState.DEFAULT;
|
||||
|
||||
public ROPData() {
|
||||
super();
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public OperationState getOperationState() {
|
||||
return operationState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return getOperationState() == OperationState.SUCCESS;
|
||||
}
|
||||
|
||||
public ROPData<T> setData(T data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPData setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPData setOperationState(OperationState operationState) {
|
||||
this.operationState = operationState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ROPData<T> success(T t) {
|
||||
return success().setData(t);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toJson(this, JsonFormat.forLook());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.nutz.http.Http;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Streams;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.log.Log;
|
||||
import org.nutz.log.Logs;
|
||||
import org.nutz.mvc.upload.UploadingContext;
|
||||
|
||||
import cn.com.chinarecrm.rop.ROPConfig;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public abstract class AbstractSinger implements Signer {
|
||||
UploadingContext context = new UploadingContext(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
Log log = Logs.get();
|
||||
|
||||
@Override
|
||||
public boolean check(HttpServletRequest request, AppsecretFetcher fetcher) {
|
||||
if (Strings.isBlank(request.getHeader(ROPConfig.APP_KEY_KEY))
|
||||
|| Strings.isBlank(request.getHeader(ROPConfig.TS_KEY))
|
||||
|| Strings.isBlank(request.getHeader(ROPConfig.METHOD_KEY))
|
||||
|| Strings.isBlank(request.getHeader(ROPConfig.NONCE_KEY))
|
||||
|| Strings.isBlank(request.getHeader(ROPConfig.SIGN_KEY))) {
|
||||
return false;
|
||||
}
|
||||
String sign = request.getHeader(ROPConfig.SIGN_KEY);
|
||||
log.debugf("Expected sign is %s", sign);
|
||||
return Strings.equalsIgnoreCase(sign(request, fetcher), sign);
|
||||
}
|
||||
|
||||
public String contentType(HttpServletRequest request) {
|
||||
return request.getHeader("Content-Type");
|
||||
}
|
||||
|
||||
protected String getDataMate(HttpServletRequest request) {
|
||||
if (Strings.equalsIgnoreCase(request.getMethod(), "GET")) {// GET请求需要处理一下
|
||||
return Lang.md5(request.getQueryString());
|
||||
}
|
||||
// 文件上传
|
||||
if (isFileUpload(request)) {
|
||||
try {
|
||||
return Lang.md5(new ByteArrayInputStream(
|
||||
getURLEncodedParams(request).getBytes(request.getCharacterEncoding())));
|
||||
}
|
||||
catch (IOException | ServletException e) {
|
||||
log.debug("不支持的编码!");
|
||||
throw Lang.wrapThrow(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
StringBuilder info = Streams.read(new InputStreamReader(request.getInputStream()));
|
||||
if (info.length() == 0) {
|
||||
return Lang.md5(
|
||||
SignerHelper.paramMapAsUrlString(request.getParameterMap(), request.getCharacterEncoding()));
|
||||
}
|
||||
return Lang.md5(info);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw Lang.wrapThrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getURLEncodedParams(final HttpServletRequest request) throws IOException, ServletException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
List<Part> parts = Lang.collection2list(request.getParts());
|
||||
Collections.sort(parts, (part1, part2) -> part1.getName().compareTo(part2.getName()));
|
||||
parts.stream().forEach(part -> {
|
||||
String key = part.getName();
|
||||
if (Strings.isBlank(part.getContentType())) {
|
||||
// 参数
|
||||
sb.append(Http.encode(key, request.getCharacterEncoding()))
|
||||
.append('=')
|
||||
.append(Http.encode(request.getParameter(key), request.getCharacterEncoding()))
|
||||
.append('&');
|
||||
} else {
|
||||
// 文件
|
||||
try {
|
||||
sb.append(Http.encode(key, request.getCharacterEncoding()))
|
||||
.append('=')
|
||||
.append(Http.encode(Lang.md5(part.getInputStream()), request.getCharacterEncoding()))
|
||||
.append('&');
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw Lang.wrapThrow(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (sb.length() > 0)
|
||||
sb.setLength(sb.length() - 1);
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
private boolean isCommonFileUpload(HttpServletRequest request) {
|
||||
return contentType(request) != null
|
||||
&& contentType(request).startsWith("multipart/form-data");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean isFileUpload(HttpServletRequest request) {
|
||||
return isCommonFileUpload(request) || isHtml5FileUpload(request);
|
||||
}
|
||||
|
||||
private boolean isHtml5FileUpload(HttpServletRequest request) {
|
||||
return contentType(request) != null
|
||||
&& contentType(request).startsWith("application/octet-stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据appSecret获取器签名
|
||||
*
|
||||
* @param fetcher
|
||||
* appSecret获取器
|
||||
* @param appKey
|
||||
* 应用key
|
||||
* @param timestamp
|
||||
* 时间戳
|
||||
* @param gateway
|
||||
* 方法/路由
|
||||
* @param nonce
|
||||
* 随机串
|
||||
* @param dataMate
|
||||
* 数据元数据
|
||||
* @return 签名字符串
|
||||
*/
|
||||
public String sign(AppsecretFetcher fetcher,
|
||||
String appKey,
|
||||
String timestamp,
|
||||
String gateway,
|
||||
String nonce,
|
||||
String dataMate) {
|
||||
return sign(fetcher.fetch(appKey), timestamp, gateway, nonce, dataMate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器端签名
|
||||
*
|
||||
* @param request
|
||||
* 请求
|
||||
* @param fetcher
|
||||
* appSecret获取器
|
||||
* @return 签名字符串
|
||||
*/
|
||||
public String sign(HttpServletRequest request, AppsecretFetcher fetcher) {
|
||||
return sign(fetcher,
|
||||
request.getHeader(ROPConfig.APP_KEY_KEY),
|
||||
request.getHeader(ROPConfig.TS_KEY),
|
||||
request.getHeader(ROPConfig.METHOD_KEY),
|
||||
request.getHeader(ROPConfig.NONCE_KEY),
|
||||
getDataMate(request));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public interface AppsecretFetcher {
|
||||
|
||||
public String fetch(String key);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
import org.nutz.lang.Lang;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class DefaultMD5Fetcher implements AppsecretFetcher {
|
||||
|
||||
@Override
|
||||
public String fetch(String key) {
|
||||
return Lang.md5(key);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class DigestSigner extends AbstractSinger {
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public DigestSigner() {
|
||||
this.name = "MD5";
|
||||
}
|
||||
|
||||
public DigestSigner(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sign(String appSecret, String timestamp, String gateway, String nonce, String dataMate) {
|
||||
String[] temp = Lang.array(appSecret, timestamp, gateway, nonce, dataMate);
|
||||
Arrays.sort(temp);
|
||||
log.debugf("sign with %s args %s", name(), temp);
|
||||
return Lang.digest(name(), Strings.join("", temp));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public interface Signer {
|
||||
/**
|
||||
* 签名检查
|
||||
*
|
||||
* @param request
|
||||
* 请求
|
||||
* @param fetcher
|
||||
* 密钥获取器
|
||||
* @return 是否检查通过
|
||||
*/
|
||||
public boolean check(HttpServletRequest request, AppsecretFetcher fetcher);
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*
|
||||
* @return 签名器名称
|
||||
*/
|
||||
public String name();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param appSecret
|
||||
* 密钥
|
||||
* @param timestamp
|
||||
* 时间戳
|
||||
* @param gateway
|
||||
* 网关/方法名称
|
||||
* @param nonce
|
||||
* 随机串
|
||||
* @param dataMate
|
||||
* 数据元数据
|
||||
* @return 签名字符串
|
||||
*/
|
||||
public String sign(String appSecret, String timestamp, String gateway, String nonce, String dataMate);
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package cn.com.chinarecrm.rop.core.signer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.nutz.http.Http;
|
||||
import org.nutz.lang.ContinueLoop;
|
||||
import org.nutz.lang.Each;
|
||||
import org.nutz.lang.ExitLoop;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.LoopException;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
/**
|
||||
* @author 王贵源(wangguiyuan@chinarecrm.com.cn)
|
||||
*/
|
||||
public class SignerHelper {
|
||||
|
||||
public static String mapAsUrlParams(Map<String, Object> map, String enc) {
|
||||
if (map == null || map.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
String[] keys = Lang.collection2array(map.keySet());
|
||||
Arrays.sort(keys);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Lang.each(keys, new Each<String>() {
|
||||
|
||||
@Override
|
||||
public void invoke(int index, String key, int length) throws ExitLoop, ContinueLoop, LoopException {
|
||||
Object val = map.get(key);
|
||||
if (val instanceof Collection || val.getClass().isArray()) {
|
||||
// 数组情况的处理
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(toStringInfo(val), enc))
|
||||
.append('&');
|
||||
} else {
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(val, enc))
|
||||
.append('&');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
if (sb.length() > 0)
|
||||
sb.setLength(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String paramMapAsUrlString(Map<String, String[]> map, String enc) {
|
||||
if (map == null || map.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
String[] keys = Lang.collection2array(map.keySet());
|
||||
Arrays.sort(keys);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Lang.each(keys, new Each<String>() {
|
||||
|
||||
@Override
|
||||
public void invoke(int index, String key, int length) throws ExitLoop, ContinueLoop, LoopException {
|
||||
String[] val = map.get(key);
|
||||
if (val.length == 1) {
|
||||
// 单值的情况
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(val[0], enc))
|
||||
.append('&');
|
||||
} else {
|
||||
sb.append(Http.encode(key, enc))
|
||||
.append('=')
|
||||
.append(Http.encode(toStringInfo(val), enc))
|
||||
.append('&');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
if (sb.length() > 0)
|
||||
sb.setLength(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected static String toStringInfo(Object val) {
|
||||
List<Object> info = Lang.list();
|
||||
Lang.each(val, new Each<Object>() {
|
||||
|
||||
@Override
|
||||
public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException {
|
||||
info.add(ele);
|
||||
}
|
||||
|
||||
});
|
||||
return Strings.join(",", info);
|
||||
}
|
||||
|
||||
private SignerHelper() {}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<artifactId>rop-server</artifactId>
|
||||
<name>rop-server</name>
|
||||
<description>rop server integration</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.com.chinarecrm</groupId>
|
||||
<artifactId>rop-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|