diff --git a/README.md b/README.md index 2b2f2e2c..3acda6c4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处 - 接入方管理+秘钥管理 - 接口权限分配 - 文件上传/下载 -- SDK +- 提供基础SDK(含:Java,C#,Python,Go) - 接口限流 - 文档整合 - 应用授权 diff --git a/changelog.md b/changelog.md index c6822c03..f1fb204e 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,8 @@ ## 3.2.0 - 使用alibaba cloud +- 新增Python,Go版本SDK +- 返回结果新增全局request_id Hoxton.SR3(Spring Cloud Version), 2.2.1.RELEASE(Spring Cloud Alibaba Version), 2.2.5.RELEASE(Spring Boot Version) diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java index 13d24846..c7c5483e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java @@ -6,6 +6,7 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants; import org.apache.commons.lang3.StringUtils; import java.util.Map; +import java.util.UUID; /** * 客户端传来的参数放在这里. @@ -23,6 +24,7 @@ public class ApiParam extends JSONObject implements Param { super(map); } + private String requestId = UUID.randomUUID().toString().replace("-", ""); private boolean ignoreSign; private boolean ignoreValidate; @@ -289,4 +291,8 @@ public class ApiParam extends JSONObject implements Param { public void setRestful(boolean restful) { this.restful = restful; } + + public String fetchRequestId() { + return requestId; + } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index 931d7f84..6d998415 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -180,6 +180,8 @@ public abstract class BaseExecutorAdapter implements ResultExecutor params = new ApiParam(); params.setName("error"); } + // 全局请求id,方便追踪定位 + finalData.put("request_id", params.fetchRequestId()); ApiConfig apiConfig = ApiConfig.getInstance(); // 点换成下划线 DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder(); diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/DemoOrderController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/DemoOrderController.java new file mode 100644 index 00000000..4dab0d29 --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/DemoOrderController.java @@ -0,0 +1,74 @@ +package com.gitee.sop.storyweb.controller; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.servercommon.annotation.ApiMapping; +import com.gitee.sop.servercommon.util.UploadUtil; +import lombok.Data; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +/** + * @author tanghc + */ +@RestController +public class DemoOrderController { + + @Value("${server.port}") + private String port; + + @ApiMapping("member.info.get") + public Object member(MemberInfoGetParam param, HttpServletRequest request) { + if ("tom".equals(param.name)) { + throw new IllegalArgumentException("name参数错误"); + } + Collection uploadFiles = UploadUtil.getUploadFiles(request); + for (MultipartFile uploadFile : uploadFiles) { + try { + System.out.println("文件名称:" + uploadFile.getOriginalFilename() + + " 表单名称:" + uploadFile.getName() + + " 文件内容:" + + IOUtils.toString(uploadFile.getInputStream(), StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } + } + System.out.println(param); + return JSON.parseObject("{\n" + + " \"id\": 123,\n" + + " \"name\": \"jim\",\n" + + " \"member_info\": {\n" + + " \"is_vip\": 1,\n" + + " \"vip_endtime\": \"2020-11-11 11:11:11\"\n" + + " }\n" + + "}", MemberInfoGetResult.class); + } + + + @Data + public static class MemberInfoGetParam { + private String name; + private Integer age; + private String address; + } + + @Data + public static class MemberInfoGetResult { + private Integer id; + private String name; + private MemberInfoGetResultMemberInfo member_info; + } + + @Data + public static class MemberInfoGetResultMemberInfo { + private Byte is_vip; + private String vip_endtime; + } + +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/TokenController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/TokenController.java index f0da8211..ab28ff9b 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/TokenController.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/TokenController.java @@ -6,6 +6,7 @@ import com.gitee.sop.servercommon.bean.ServiceContext; import com.gitee.sop.storyweb.controller.param.StoryParam; import com.gitee.sop.storyweb.controller.result.StoryResult; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController; @Api(tags = "故事接口") public class TokenController { + @ApiOperation(value="传递token", notes = "传递token") @ApiMapping(value = "story.token.get", needToken = true/* 设置true,网关会校验token是否存在 */) public StoryResult token(StoryParam story) { OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); diff --git a/sop-sdk/sdk-go/common/HttpTool.go b/sop-sdk/sdk-go/common/HttpTool.go new file mode 100644 index 00000000..13055a9b --- /dev/null +++ b/sop-sdk/sdk-go/common/HttpTool.go @@ -0,0 +1,126 @@ +package common + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" +) + +type UploadFile struct { + // 表单名称 + Name string + Filepath string +} + +// 请求客户端 +var httpClient = &http.Client{} + +func Get(reqUrl string, allParams map[string]string, headers map[string]string) string { + urlParams := url.Values{} + Url, _ := url.Parse(reqUrl) + for key, val := range allParams { + urlParams.Set(key, val) + } + + //如果参数中有中文参数,这个方法会进行URLEncode + Url.RawQuery = urlParams.Encode() + // 得到完整的url,http://xx?query + urlPath := Url.String() + + httpRequest,_ := http.NewRequest("GET", urlPath, nil) + // 添加请求头 + if headers != nil { + for k, v := range headers { + httpRequest.Header.Add(k,v) + } + } + // 发送请求 + resp, err := httpClient.Do(httpRequest) + if err != nil { + panic(err) + } + defer resp.Body.Close() + response, _ := ioutil.ReadAll(resp.Body) + return string(response) +} + +func PostForm(reqUrl string, allParams map[string]string, headers map[string]string) string { + return post(reqUrl, allParams, "application/x-www-form-urlencoded", nil, headers) +} + +func PostJson(reqUrl string, allParams map[string]string, headers map[string]string) string { + return post(reqUrl, allParams, "application/json", nil, headers) +} + +func PostFile(reqUrl string, allParams map[string]string, files []UploadFile, headers map[string]string) string { + return post(reqUrl, allParams, "multipart/form-data", files, headers) +} + + +func post(reqUrl string, allParams map[string]string, contentType string, files []UploadFile, headers map[string]string) string { + requestBody, realContentType := getReader(allParams, contentType, files) + httpRequest,_ := http.NewRequest("POST", reqUrl, requestBody) + // 添加请求头 + httpRequest.Header.Add("Content-Type", realContentType) + if headers != nil { + for k, v := range headers { + httpRequest.Header.Add(k,v) + } + } + // 发送请求 + resp, err := httpClient.Do(httpRequest) + if err != nil { + panic(err) + } + defer resp.Body.Close() + response, _ := ioutil.ReadAll(resp.Body) + return string(response) +} + +func getReader(allParams map[string]string, contentType string, files []UploadFile) (io.Reader, string) { + if strings.Index(contentType, "json") > -1 { + bytesData, _ := json.Marshal(allParams) + return bytes.NewReader(bytesData), contentType + } else if files != nil { + body := &bytes.Buffer{} + // 文件写入 body + writer := multipart.NewWriter(body) + for _, uploadFile := range files { + file, err := os.Open(uploadFile.Filepath) + if err != nil { + panic(err) + } + part, err := writer.CreateFormFile(uploadFile.Name, filepath.Base(uploadFile.Filepath)) + if err != nil { + panic(err) + } + _, err = io.Copy(part, file) + file.Close() + } + // 其他参数列表写入 body + for k, v := range allParams { + if err := writer.WriteField(k, v); err != nil { + panic(err) + } + } + if err := writer.Close(); err != nil { + panic(err) + } + // 上传文件需要自己专用的contentType + return body, writer.FormDataContentType() + } else { + urlValues := url.Values{} + for key, val := range allParams { + urlValues.Set(key, val) + } + reqBody:= urlValues.Encode() + return strings.NewReader(reqBody), contentType + } +} diff --git a/sop-sdk/sdk-go/common/IRequest.go b/sop-sdk/sdk-go/common/IRequest.go new file mode 100644 index 00000000..ef0f10e7 --- /dev/null +++ b/sop-sdk/sdk-go/common/IRequest.go @@ -0,0 +1,24 @@ +package common + +type RequestType string + +const ( + GET RequestType = "GET" + POST_JSON RequestType = "POST_JSON" + POST_FORM RequestType = "POST_FORM" + POST_UPLOAD RequestType = "POST_UPLOAD" +) + +type Model struct { + // 业务参数 + BizModel interface{} + // 上传文件 + Files []UploadFile +} + +type IRequest interface { + GetMethod() string + GetVersion() string + GetRequestType() RequestType + GetModel() Model +} diff --git a/sop-sdk/sdk-go/common/OpenClient.go b/sop-sdk/sdk-go/common/OpenClient.go new file mode 100644 index 00000000..23289c52 --- /dev/null +++ b/sop-sdk/sdk-go/common/OpenClient.go @@ -0,0 +1,108 @@ +package common + +import ( + "encoding/json" + "errors" + "reflect" + "strings" + "time" +) + +var headers = map[string]string{ + "Accept-Encoding": "identity", +} + +type IClient interface { + Execute() string +} + +type OpenClient struct { + AppId string + PrivateKey string + Url string +} + +func (client OpenClient) ExecuteToken(iRequest IRequest, token string) []byte { + model := iRequest.GetModel() + bizModel := model.BizModel + types := reflect.TypeOf(bizModel) + values := reflect.ValueOf(bizModel) + params := make(map[string]interface{}) + //遍历结构体的所有字段 + for i := 0; i < values.NumField(); i++ { + // 获取到struct标签,需要通过reflect.Type来获取tag标签的值 + fieldName := types.Field(i).Tag.Get("json") + // 如果该字段有tag标签就显示,否则就不显示 + if fieldName != "" { + params[fieldName] = values.Field(i).Interface() + } + } + + requestType := iRequest.GetRequestType() + var response string + allParams := client.buildParams(iRequest, params, token) + if model.Files != nil && len(model.Files) > 0 { + response = PostFile(client.Url, allParams, model.Files, headers) + } else { + switch requestType { + case GET: + response = Get(client.Url, allParams, headers) + case POST_FORM: + response = PostForm(client.Url, allParams, headers) + case POST_JSON: + response = PostJson(client.Url, allParams, headers) + case POST_UPLOAD: + response = PostFile(client.Url, allParams, model.Files, headers) + default: + panic(errors.New("GetRequestType()返回错误")) + } + } + return parseResponseResult(iRequest, response) +} + +func parseResponseResult(iRequest IRequest, response string) []byte { + var responseRoot = map[string]interface{}{} + var err = json.Unmarshal([]byte(response), &responseRoot) + if err != nil { + panic(err) + } + requestId := responseRoot["request_id"].(string) + var responseDataMap = responseRoot["error_response"] + if responseDataMap == nil { + dataName := strings.ReplaceAll(iRequest.GetMethod(), ".", "_") + "_response" + responseDataMap = responseRoot[dataName] + } + responseDataMap.(map[string]interface{})["request_id"] = requestId + // json数据 + dataJsonBytes, _ := json.Marshal(responseDataMap) + return dataJsonBytes +} + +func (client OpenClient) buildParams(iRequest IRequest, params map[string]interface{}, token string) map[string]string { + allParams := map[string]string{ + "app_id": client.AppId, + "method": iRequest.GetMethod(), + "charset": "UTF-8", + "sign_type": "RSA2", + "timestamp": time.Now().Format("2006-01-02 15:04:05"), + "version": iRequest.GetVersion(), + } + + if token != "" { + allParams["access_token"] = token + } + + // 添加业务参数 + for k, v := range params { + allParams[k] = ToString(v) + } + + // 构建sign + sign := CreateSign(allParams, client.PrivateKey, "RSA2") + allParams["sign"] = sign + return allParams +} + +func (client OpenClient) Execute(iRequest IRequest) []byte { + return client.ExecuteToken(iRequest, "") +} diff --git a/sop-sdk/sdk-go/common/SignUtil.go b/sop-sdk/sdk-go/common/SignUtil.go new file mode 100644 index 00000000..b3669bea --- /dev/null +++ b/sop-sdk/sdk-go/common/SignUtil.go @@ -0,0 +1,99 @@ +package common + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "sort" + "strings" +) + +const ( + PEM_BEGIN = "-----BEGIN RSA PRIVATE KEY-----\n" + PEM_END = "\n-----END RSA PRIVATE KEY-----" +) + +func CreateSign(allParams map[string]string, privateKey string, signType string) string { + signContent := GetSignContent(allParams) + return Sign(signContent, privateKey, signType) +} + +func Sign(signContent string, privateKey string, signType string) string { + if signType == "RSA" { + return RsaSign(signContent, privateKey, crypto.SHA1) + } else if signType == "RSA2" { + return RsaSign(signContent, privateKey, crypto.SHA256) + } else { + panic(errors.New("signType错误")) + } +} + +func RsaSign(signContent string, privateKey string, hash crypto.Hash) string { + shaNew := hash.New() + shaNew.Write([]byte(signContent)) + hashed := shaNew.Sum(nil) + priKey, err := ParsePrivateKey(privateKey) + if err != nil { + panic(err) + } + + signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed) + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(signature) +} + +func ParsePrivateKey(privateKey string)(*rsa.PrivateKey, error) { + privateKey = FormatPrivateKey(privateKey) + // 2、解码私钥字节,生成加密对象 + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return nil, errors.New("私钥信息错误!") + } + // 3、解析DER编码的私钥,生成私钥对象 + priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return priKey, nil +} + +func FormatPrivateKey(privateKey string) string { + if !strings.HasPrefix(privateKey, PEM_BEGIN) { + privateKey = PEM_BEGIN + privateKey + } + if !strings.HasSuffix(privateKey, PEM_END) { + privateKey = privateKey + PEM_END + } + return privateKey +} + + +/** +1.筛选并排序 +获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照参数名ASCII码递增排序(字母升序排序), +如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。 + +2.拼接 +将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。 + */ +func GetSignContent(allParams map[string]string) string { + keys := make([]string, 0, len(allParams)) + var result []string + for k := range allParams { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + val := allParams[key] + if len(val) > 0 { + result = append(result, key + "=" + val) + } + } + return strings.Join(result, "&") +} \ No newline at end of file diff --git a/sop-sdk/sdk-go/common/StringUtil.go b/sop-sdk/sdk-go/common/StringUtil.go new file mode 100644 index 00000000..e4a49c56 --- /dev/null +++ b/sop-sdk/sdk-go/common/StringUtil.go @@ -0,0 +1,56 @@ +package common + +import ( + "encoding/json" + "strconv" +) + +func ToString(value interface{}) string { + var key string + switch value.(type) { + case float64: + ft := value.(float64) + key = strconv.FormatFloat(ft, 'f', -1, 64) + case float32: + ft := value.(float32) + key = strconv.FormatFloat(float64(ft), 'f', -1, 64) + case int: + it := value.(int) + key = strconv.Itoa(it) + case uint: + it := value.(uint) + key = strconv.Itoa(int(it)) + case int8: + it := value.(int8) + key = strconv.Itoa(int(it)) + case uint8: + it := value.(uint8) + key = strconv.Itoa(int(it)) + case int16: + it := value.(int16) + key = strconv.Itoa(int(it)) + case uint16: + it := value.(uint16) + key = strconv.Itoa(int(it)) + case int32: + it := value.(int32) + key = strconv.Itoa(int(it)) + case uint32: + it := value.(uint32) + key = strconv.Itoa(int(it)) + case int64: + it := value.(int64) + key = strconv.FormatInt(it, 10) + case uint64: + it := value.(uint64) + key = strconv.FormatUint(it, 10) + case string: + key = value.(string) + case []byte: + key = string(value.([]byte)) + default: + newValue, _ := json.Marshal(value) + key = string(newValue) + } + return key +} \ No newline at end of file diff --git a/sop-sdk/sdk-go/model/MemberInfoGetModel.go b/sop-sdk/sdk-go/model/MemberInfoGetModel.go new file mode 100644 index 00000000..06ddbd4d --- /dev/null +++ b/sop-sdk/sdk-go/model/MemberInfoGetModel.go @@ -0,0 +1,7 @@ +package model + +type MemberInfoGetModel struct { + Name string `json:"name"` + Age uint32 `json:"age"` + Address string `json:"address"` +} diff --git a/sop-sdk/sdk-go/readme.md b/sop-sdk/sdk-go/readme.md new file mode 100644 index 00000000..0d914e11 --- /dev/null +++ b/sop-sdk/sdk-go/readme.md @@ -0,0 +1,43 @@ +# sdk-go + +```go + +// 应用ID +const appId string = "xx" +// 应用私钥 +const privateKey string = "xx" +// 请求地址 +const url string = "http://localhost:7071/prod/gw68uy85" + +// 请求客户端 +var openClient = common.OpenClient{AppId: appId, PrivateKey: privateKey, Url: url} + +func main() { + // 创建请求 + memberInfoGetRequest := request.MemberInfoGetRequest{} + // 请求参数 + memberInfoGetRequest.BizModel = model.MemberInfoGetModel{Name: "jim", Age: 22, Address: "xx"} + + // 添加上传文件 + //path, _ := os.Getwd() + //files := []common.UploadFile{ + // {Name:"file1", Filepath:path + "/test/aa.txt"}, + // {Name:"file2", Filepath:path + "/test/bb.txt"}, + //} + //memberInfoGetRequest.Files = files + + // 发送请求,返回json bytes + var jsonBytes = openClient.Execute(memberInfoGetRequest) + fmt.Printf("data:%s\n", string(jsonBytes)) + // 转换结果 + var memberInfoGetResponse response.MemberInfoGetResponse + response.ConvertResponse(jsonBytes, &memberInfoGetResponse) + + if memberInfoGetResponse.IsSuccess() { + fmt.Printf("is_vip:%d, vip_endtime:%s\n", memberInfoGetResponse.MemberInfo.IsVip, memberInfoGetResponse.MemberInfo.VipEndtime) + } else { + fmt.Printf("code:%s, msg:%s, subCode:%s, subMsg:%s\n", + memberInfoGetResponse.Code, memberInfoGetResponse.Msg, memberInfoGetResponse.SubCode, memberInfoGetResponse.SubMsg) + } +} +``` \ No newline at end of file diff --git a/sop-sdk/sdk-go/request/MemberInfoGetRequest.go b/sop-sdk/sdk-go/request/MemberInfoGetRequest.go new file mode 100644 index 00000000..1cba4765 --- /dev/null +++ b/sop-sdk/sdk-go/request/MemberInfoGetRequest.go @@ -0,0 +1,23 @@ +package request + +import "../common" + +type MemberInfoGetRequest struct { + common.Model +} + +func (MemberInfoGetRequest) GetMethod() string { + return "member.info.get" +} + +func (MemberInfoGetRequest) GetVersion() string { + return "1.0" +} + +func (MemberInfoGetRequest) GetRequestType() common.RequestType { + return common.GET +} + +func (req MemberInfoGetRequest) GetModel() common.Model { + return req.Model +} diff --git a/sop-sdk/sdk-go/response/BaseResponse.go b/sop-sdk/sdk-go/response/BaseResponse.go new file mode 100644 index 00000000..bda5b78d --- /dev/null +++ b/sop-sdk/sdk-go/response/BaseResponse.go @@ -0,0 +1,26 @@ +package response + +import "encoding/json" + +type IResponse interface { + IsSuccess() bool +} + +type BaseResponse struct { + RequestId string `json:"request_id"` + Code string `json:"code"` + Msg string `json:"msg"` + SubCode string `json:"sub_code"` + SubMsg string `json:"sub_msg"` +} + +func (resp BaseResponse) IsSuccess() bool { + return len(resp.SubCode) == 0 +} + +func ConvertResponse(data []byte, ptr interface{}) { + err := json.Unmarshal(data, ptr) + if err != nil { + panic(err) + } +} diff --git a/sop-sdk/sdk-go/response/MemberInfoGetResponse.go b/sop-sdk/sdk-go/response/MemberInfoGetResponse.go new file mode 100644 index 00000000..b566196f --- /dev/null +++ b/sop-sdk/sdk-go/response/MemberInfoGetResponse.go @@ -0,0 +1,14 @@ +package response + +type MemberInfoGetResponse struct { + BaseResponse + + Id int32 `json:"id"` + Name string `json:"name"` + MemberInfo MemberInfo `json:"member_info"` +} + +type MemberInfo struct { + IsVip int32 `json:"is_vip"` + VipEndtime string `json:"vip_endtime"` +} diff --git a/sop-sdk/sdk-go/test/aa.txt b/sop-sdk/sdk-go/test/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-go/test/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-go/test/bb.txt b/sop-sdk/sdk-go/test/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-go/test/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-go/test/test.go b/sop-sdk/sdk-go/test/test.go new file mode 100644 index 00000000..27b2b919 --- /dev/null +++ b/sop-sdk/sdk-go/test/test.go @@ -0,0 +1,48 @@ +package main + +import ( + "../common" + "../model" + "../request" + "../response" + "fmt" +) + +// 应用ID +const appId string = "201904035630907729292csharp" +// 应用私钥 +const privateKey string = "MIIEowIBAAKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQABAoIBAHFDsgrrJca+NKEan77ycwx3jnKx4WrWjOF4zVKL9AQjiSYDNgvKknJyPb3kpC/lEoHdxGERHSzJoxib7DkoIqRQYhPxj73pxj5QfYk3P7LLJNNg/LTrpXDb3nL8JV9wIflGf87qQvstZTDJEyFWE4jBs7Hr0BxovWvri8InnzkmERJ1cbGJgNHe1Y3Zo2tw0yaHxQCxLuajP+notRZhD9bEp7uKeI0w9AvlW6k8m/7y10F0BK/TlyW8rQiEC391yOiRYoMcUh4hd2Q9bMx3jngZgX8PXIvZZcup4/pvWlv1alwhB2tsnLdazP62r1MO80vLyLunzGO+7WwCjEYlVaECgYEA+lQRFmbhKaPuAuXMtY31Fbga8nedka5TjnEV7+/kX+yowE2OlNujF+ZG8UTddTxAGv56yVNi/mjRlgD74j8z0eOsgvOq9mwbCrgLhLo51H9O/wAxtb+hBKtC5l50pBr4gER6d8W6EQNTSGojnMIaLXTkAZ5Qf6Z8e2HFVdOn0X0CgYEA7SSrTokwzukt5KldNu5ukyyd+C3D1i6orbg6qD73EP9CfNMfGSBn7dDv9wMSJH01+Ty+RgTROgtjGRDbMJWnfbdt/61NePr9ar5sb6Nbsf7/I0w7cZF5dsaFYgzaOfQYquzXPbLQHkpMT64bqpv/Mwy4F2lFvaYWY5fA4pC2uckCgYEAg75Ym9ybJaoTqky8ttQ2Jy8UZ4VSVQhVC0My02sCWwWXLlXi8y7An+Rec73Ve0yxREOn5WrQT6pkmzh7V/ABWrYi5WxODpCIjtSbo0fLBa3Wqle00b0/hdCITetqIa/cFs1zUrOqICgK3bKWeXqiAkhhcwSZwwSgwOKM04Wn7ZUCgYBvhHX2mbdVJfyJ8kc+hMOE/E9RHRxiBVEXWHJlGi8PVCqNDq8qHr4g7Mdbzprig+s0yKblwHAvrpkseWvKHiZEjVTyDipHgShY4TGXEigVvUd37uppTrLi8xpYcJjS9gH/px7VCdiq1d+q/MJP6coJ1KphgATm2UrgDMYNBWaYWQKBgEHRxrmER7btUF60/YgcqPHFc8RpYQB2ZZE0kyKGDqk2Data1XYUY6vsPAU28yRLAaWr/D2H17iyLkxP80VLm6QhifxCadv90Q/Wl1DFfOJQMW6avyQ0so6G0wFq/LJxaFK4iLXQn1RJnmTp6BYiJMmK2BhFbRzw8ssMoF6ad2rr" +// 请求路径 +const url string = "http://localhost:8081" + +// 请求客户端 +var openClient = common.OpenClient{AppId: appId, PrivateKey: privateKey, Url: url} + +func main() { + // 创建请求 + memberInfoGetRequest := request.MemberInfoGetRequest{} + // 设置请求参数 + memberInfoGetRequest.BizModel = model.MemberInfoGetModel{Name: "jim", Age: 22, Address: "xx"} + + // 添加上传文件 + //path, _ := os.Getwd() + //files := []common.UploadFile{ + // {Name:"file1", Filepath:path + "/test/aa.txt"}, + // {Name:"file2", Filepath:path + "/test/bb.txt"}, + //} + //memberInfoGetRequest.Files = files + + // 发送请求,返回json bytes + var jsonBytes = openClient.Execute(memberInfoGetRequest) + fmt.Printf("data:%s\n", string(jsonBytes)) + // 转换结果 + var memberInfoGetResponse response.MemberInfoGetResponse + response.ConvertResponse(jsonBytes, &memberInfoGetResponse) + + if memberInfoGetResponse.IsSuccess() { + fmt.Printf("is_vip:%d, vip_endtime:%s\n", memberInfoGetResponse.MemberInfo.IsVip, memberInfoGetResponse.MemberInfo.VipEndtime) + } else { + fmt.Printf("code:%s, msg:%s, subCode:%s, subMsg:%s\n", + memberInfoGetResponse.Code, memberInfoGetResponse.Msg, memberInfoGetResponse.SubCode, memberInfoGetResponse.SubMsg) + } +} diff --git a/sop-sdk/sdk-python/.gitignore b/sop-sdk/sdk-python/.gitignore new file mode 100644 index 00000000..297111f9 --- /dev/null +++ b/sop-sdk/sdk-python/.gitignore @@ -0,0 +1,27 @@ +/target/ +/venv/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + diff --git a/sop-sdk/sdk-python/common/JsonUtil.py b/sop-sdk/sdk-python/common/JsonUtil.py new file mode 100644 index 00000000..73e316cd --- /dev/null +++ b/sop-sdk/sdk-python/common/JsonUtil.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import json + + +def to_json_string(obj): + """将对象转换成json字符串 + + :param obj: 对象 + :type obj: object + + :return: 返回json + :rtype: str + """ + if isinstance(obj, dict): + param = obj + else: + param = obj.__dict__ + return json.dumps(param, ensure_ascii=False) + diff --git a/sop-sdk/sdk-python/common/OpenClient.py b/sop-sdk/sdk-python/common/OpenClient.py new file mode 100644 index 00000000..821c5f3e --- /dev/null +++ b/sop-sdk/sdk-python/common/OpenClient.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import json +import time + +import requests + +from common import SignUtil, RequestTypes +from common.RequestType import RequestType + +_headers = {'Accept-Encoding': 'identity'} + + +class OpenClient: + """调用客户端""" + __app_id = '' + __private_key = '' + __url = '' + + def __init__(self, app_id, private_key, url): + """客户端 + + :param app_id: 应用ID + :type app_id: str + + :param private_key: 应用私钥 + :type private_key: str + + :param url: 请求URL + :type url: str + """ + self.__app_id = app_id + self.__private_key = private_key + self.__url = url + + def execute(self, request, token=None): + """ + + :param request: 请求对象,BaseRequest的子类 + + :param token: (Optional) token + :type token: str + + :return: 返回请求结果 + :rtype: BaseResponse + """ + biz_model = request.biz_model + request_type = request.get_request_type() + if not isinstance(request_type, RequestType): + raise Exception('get_request_type返回错误类型,正确方式:RequestTypes.XX') + + params = biz_model.__dict__ + if request.files is not None: + response = self._post_file(request, params, token) + elif request_type == RequestTypes.GET: + response = self._get(request, params, token) + elif request_type == RequestTypes.POST_FORM: + response = self._post_form(request, params, token) + elif request_type == RequestTypes.POST_JSON: + response = self._post_json(request, params, token) + elif request_type == RequestTypes.POST_UPLOAD: + response = self._post_file(request, params, token) + else: + raise Exception('get_request_type设置错误') + + return self._parse_response(response, request) + + def _get(self, request, params, token): + all_params = self._build_params(request, params, token) + return requests.get(self.__url, all_params, headers=_headers).text + + def _post_form(self, request, params, token): + all_params = self._build_params(request, params, token) + return requests.post(self.__url, data=all_params, headers=_headers).text + + def _post_json(self, request, params, token): + all_params = self._build_params(request, params, token) + return requests.post(self.__url, json=all_params, headers=_headers).text + + def _post_file(self, request, params, token): + all_params = self._build_params(request, params, token) + return requests.request('POST', self.__url, data=all_params, files=request.files, headers=_headers).text + + def _build_params(self, request, params, token): + """构建所有的请求参数 + + :param request: 请求对象 + :type request: request.BaseRequest + + :param params: 业务请求参数 + :type params: dict + + :param token: token + :type token: str + + :return: 返回请求参数 + :rtype: str + """ + all_params = { + 'app_id': self.__app_id, + 'method': request.get_method(), + 'charset': 'UTF-8', + 'sign_type': 'RSA2', + 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), + 'version': request.get_version() + } + + if token is not None: + all_params['access_token'] = token + + # 添加业务参数 + all_params.update(params) + + # 构建sign + sign = SignUtil.create_sign(all_params, self.__private_key, 'RSA2') + all_params['sign'] = sign + return all_params + + def _parse_response(self, resp, request): + response_dict = json.loads(resp) + return request.parse_response(response_dict) diff --git a/sop-sdk/sdk-python/common/RequestType.py b/sop-sdk/sdk-python/common/RequestType.py new file mode 100644 index 00000000..4df03044 --- /dev/null +++ b/sop-sdk/sdk-python/common/RequestType.py @@ -0,0 +1,8 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + + +class RequestType: + + def __init__(self): + pass diff --git a/sop-sdk/sdk-python/common/RequestTypes.py b/sop-sdk/sdk-python/common/RequestTypes.py new file mode 100644 index 00000000..edf08442 --- /dev/null +++ b/sop-sdk/sdk-python/common/RequestTypes.py @@ -0,0 +1,6 @@ +from common.RequestType import RequestType + +GET = RequestType() +POST_JSON = RequestType() +POST_FORM = RequestType() +POST_UPLOAD = RequestType() diff --git a/sop-sdk/sdk-python/common/SignUtil.py b/sop-sdk/sdk-python/common/SignUtil.py new file mode 100644 index 00000000..94045db5 --- /dev/null +++ b/sop-sdk/sdk-python/common/SignUtil.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import rsa +import base64 + +__pem_begin = '-----BEGIN RSA PRIVATE KEY-----\n' +__pem_end = '\n-----END RSA PRIVATE KEY-----' + + +def create_sign(all_params, private_key, sign_type): + """创建签名 + + :param all_params: 参数 + :type all_params: dict + + :param private_key: 私钥字符串 + :type private_key: str + + :param sign_type: 签名类型,'RSA', 'RSA2'二选一 + :type sign_type: str + + :return: 返回签名内容 + :rtype: str + """ + sign_content = get_sign_content(all_params) + private_key = _format_private_key(private_key) + return sign(sign_content, private_key, sign_type) + + +def _format_private_key(private_key): + if not private_key.startswith(__pem_begin): + private_key = __pem_begin + private_key + if not private_key.endswith(__pem_end): + private_key = private_key + __pem_end + return private_key + + +def get_sign_content(params): + """构建签名内容 + + 1.筛选并排序 + 获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照参数名ASCII码递增排序(字母升序排序), + 如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。 + + 2.拼接 + 将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。 + + :param params: 参数 + :type params: dict + + :return: 返回签名内容 + :rtype: str + """ + keys = params.keys() + keys.sort() + result = [] + for key in keys: + value = str(params.get(key)) + if len(value) > 0: + result.append(key + '=' + value) + + return '&'.join(result) + + +def sign(content, private_key, sign_type): + """签名 + + :param content: 签名内容 + :type content: str + + :param private_key: 私钥字符串 + :type private_key: str + + :param sign_type: 签名类型,'RSA', 'RSA2'二选一 + :type sign_type: str + + :return: 返回签名内容 + :rtype: str + """ + if sign_type.upper() == 'RSA': + return rsa_sign(content, private_key, 'SHA-1') + elif sign_type.upper() == 'RSA2': + return rsa_sign(content, private_key, 'SHA-256') + else: + raise Exception('sign_type错误') + + +def rsa_sign(content, private_key, hash): + """SHAWithRSA + + :param content: 签名内容 + :type content: str + + :param private_key: 私钥 + :type private_key: str + + :return: 签名内容 + :rtype: str + """ + pri_key = rsa.PrivateKey.load_pkcs1(private_key.encode('utf-8')) + sign_result = rsa.sign(content, pri_key, hash) + return base64.b64encode(sign_result) diff --git a/sop-sdk/sdk-python/common/__init__.py b/sop-sdk/sdk-python/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sop-sdk/sdk-python/model/MemberInfoGetModel.py b/sop-sdk/sdk-python/model/MemberInfoGetModel.py new file mode 100644 index 00000000..beef1ec8 --- /dev/null +++ b/sop-sdk/sdk-python/model/MemberInfoGetModel.py @@ -0,0 +1,7 @@ + +class MemberInfoGetModel: + """MemberInfoGetModel""" + + name = None + age = None + address = None diff --git a/sop-sdk/sdk-python/model/__init__.py b/sop-sdk/sdk-python/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sop-sdk/sdk-python/readme.md b/sop-sdk/sdk-python/readme.md new file mode 100644 index 00000000..3ac5e61a --- /dev/null +++ b/sop-sdk/sdk-python/readme.md @@ -0,0 +1,56 @@ +# sdk-python + +安装 + +`pip install requests` + +`pip install rsa` + +- 调用方式 + +```python + # 创建请求 + request = MemberInfoGetRequest() + # 请求参数 + model = MemberInfoGetModel() + model.age = 22 + model.name = 'jim' + model.address = 'xx' + # 添加请求参数 + request.biz_model = model + + # 添加上传文件 + # files = { + # 'file1': open('aa.txt', 'rb'), + # 'file2': open('bb.txt', 'rb') + # } + # request.files = files + + # 调用请求 + response = self.client.execute(request) + + if response.is_success(): + print 'response: ', response + print 'is_vip:', response.get('member_info').get('is_vip', 0) + else: + print '请求失败,code:%s, msg:%s, sub_code:%s, sub_msg:%s' % \ + (response.code, response.msg, response.sub_code, response.sub_msg) +``` + +详见`test.py` + +代码规范: + +| Type | Public | Internal | +| -------------------------- | ------------------ | ----------------------------------------------------------------- | +| Modules | lower_with_under | _lower_with_under | +| Packages | lower_with_under | | +| Classes | CapWords | _CapWords | +| Exceptions | CapWords | | +| Functions | lower_with_under() | _lower_with_under() | +| Global/Class Constants | CAPS_WITH_UNDER | _CAPS_WITH_UNDER | +| Global/Class Variables | lower_with_under | _lower_with_under | +| Instance Variables | lower_with_under | _lower_with_under (protected) or __lower_with_under (private) | +| Method Names | lower_with_under() | _lower_with_under() (protected) or __lower_with_under() (private) | +| Function/Method Parameters | lower_with_under | | +| Local Variables | lower_with_under | \ No newline at end of file diff --git a/sop-sdk/sdk-python/request/BaseRequest.py b/sop-sdk/sdk-python/request/BaseRequest.py new file mode 100644 index 00000000..6b4a6adc --- /dev/null +++ b/sop-sdk/sdk-python/request/BaseRequest.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +from response.BaseResponse import BaseResponse + + +class BaseRequest: + """请求类的父类""" + + biz_model = None + """请求参数""" + + files = None + """上传文件""" + + def __init__(self): + pass + + def get_method(self): + """返回接口名 + + :return: 返回接口名 + :rtype: str + """ + raise Exception('未实现BaseRequest.get_method()方法') + + def get_version(self): + """返回接口版本号 + + :return: 返回版本号,如:1.0 + :rtype: str + """ + raise Exception('未实现BaseRequest.get_version()方法') + + def get_request_type(self): + """返回请求类型 + + :return: 返回RequestType类实例 + :rtype: common.RequestType + """ + raise Exception('未实现BaseRequest.get_request_type()方法') + + def parse_response(self, response_dict): + response_data = response_dict.get('error_response') + if response_data is None: + data_name = self.get_method().replace('.', '_') + '_response' + response_data = response_dict.get(data_name) + base_response = BaseResponse(response_data) + base_response.request_id = response_dict.get('request_id') + base_response.code = response_data.get('code') + base_response.msg = response_data.get('msg') + base_response.sub_code = response_data.get('sub_code') + base_response.sub_msg = response_data.get('sub_msg') + return base_response + + + + + + diff --git a/sop-sdk/sdk-python/request/MemberInfoGetRequest.py b/sop-sdk/sdk-python/request/MemberInfoGetRequest.py new file mode 100644 index 00000000..6df98e3e --- /dev/null +++ b/sop-sdk/sdk-python/request/MemberInfoGetRequest.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +from common import RequestTypes +from request.BaseRequest import BaseRequest + + +class MemberInfoGetRequest(BaseRequest): + """获取会员信息请求""" + + def __init__(self): + BaseRequest.__init__(self) + + def get_method(self): + return 'member.info.get' + + def get_version(self): + return '1.0' + + def get_request_type(self): + return RequestTypes.GET diff --git a/sop-sdk/sdk-python/request/__init__.py b/sop-sdk/sdk-python/request/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sop-sdk/sdk-python/response/BaseResponse.py b/sop-sdk/sdk-python/response/BaseResponse.py new file mode 100644 index 00000000..4df2c650 --- /dev/null +++ b/sop-sdk/sdk-python/response/BaseResponse.py @@ -0,0 +1,23 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +from UserDict import UserDict + + +class BaseResponse(UserDict): + """返回类""" + request_id = None + code = None + msg = None + sub_code = None + sub_msg = None + + def __init__(self, _dict=None, **kwargs): + UserDict.__init__(self, _dict, **kwargs) + + def is_success(self): + """是否成功 + + :return: True,成功 + :rtype: bool + """ + return self.sub_code is None diff --git a/sop-sdk/sdk-python/response/__init__.py b/sop-sdk/sdk-python/response/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sop-sdk/sdk-python/test/__init__.py b/sop-sdk/sdk-python/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sop-sdk/sdk-python/test/aa.txt b/sop-sdk/sdk-python/test/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-python/test/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-python/test/bb.txt b/sop-sdk/sdk-python/test/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-python/test/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-python/test/test.py b/sop-sdk/sdk-python/test/test.py new file mode 100644 index 00000000..f98400cf --- /dev/null +++ b/sop-sdk/sdk-python/test/test.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import unittest + +from common import JsonUtil +from model.MemberInfoGetModel import MemberInfoGetModel +from request.MemberInfoGetRequest import MemberInfoGetRequest +from common.OpenClient import OpenClient + +import sys + +reload(sys) +sys.setdefaultencoding('utf8') + + +class MyTestCase(unittest.TestCase): + # 应用id + app_id = '201904035630907729292csharp' + # 应用私钥 + private_key = 'MIIEowIBAAKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQABAoIBAHFDsgrrJca+NKEan77ycwx3jnKx4WrWjOF4zVKL9AQjiSYDNgvKknJyPb3kpC/lEoHdxGERHSzJoxib7DkoIqRQYhPxj73pxj5QfYk3P7LLJNNg/LTrpXDb3nL8JV9wIflGf87qQvstZTDJEyFWE4jBs7Hr0BxovWvri8InnzkmERJ1cbGJgNHe1Y3Zo2tw0yaHxQCxLuajP+notRZhD9bEp7uKeI0w9AvlW6k8m/7y10F0BK/TlyW8rQiEC391yOiRYoMcUh4hd2Q9bMx3jngZgX8PXIvZZcup4/pvWlv1alwhB2tsnLdazP62r1MO80vLyLunzGO+7WwCjEYlVaECgYEA+lQRFmbhKaPuAuXMtY31Fbga8nedka5TjnEV7+/kX+yowE2OlNujF+ZG8UTddTxAGv56yVNi/mjRlgD74j8z0eOsgvOq9mwbCrgLhLo51H9O/wAxtb+hBKtC5l50pBr4gER6d8W6EQNTSGojnMIaLXTkAZ5Qf6Z8e2HFVdOn0X0CgYEA7SSrTokwzukt5KldNu5ukyyd+C3D1i6orbg6qD73EP9CfNMfGSBn7dDv9wMSJH01+Ty+RgTROgtjGRDbMJWnfbdt/61NePr9ar5sb6Nbsf7/I0w7cZF5dsaFYgzaOfQYquzXPbLQHkpMT64bqpv/Mwy4F2lFvaYWY5fA4pC2uckCgYEAg75Ym9ybJaoTqky8ttQ2Jy8UZ4VSVQhVC0My02sCWwWXLlXi8y7An+Rec73Ve0yxREOn5WrQT6pkmzh7V/ABWrYi5WxODpCIjtSbo0fLBa3Wqle00b0/hdCITetqIa/cFs1zUrOqICgK3bKWeXqiAkhhcwSZwwSgwOKM04Wn7ZUCgYBvhHX2mbdVJfyJ8kc+hMOE/E9RHRxiBVEXWHJlGi8PVCqNDq8qHr4g7Mdbzprig+s0yKblwHAvrpkseWvKHiZEjVTyDipHgShY4TGXEigVvUd37uppTrLi8xpYcJjS9gH/px7VCdiq1d+q/MJP6coJ1KphgATm2UrgDMYNBWaYWQKBgEHRxrmER7btUF60/YgcqPHFc8RpYQB2ZZE0kyKGDqk2Data1XYUY6vsPAU28yRLAaWr/D2H17iyLkxP80VLm6QhifxCadv90Q/Wl1DFfOJQMW6avyQ0so6G0wFq/LJxaFK4iLXQn1RJnmTp6BYiJMmK2BhFbRzw8ssMoF6ad2rr' + # 请求URL + url = 'http://localhost:8081' + # 创建请求客户端 + client = OpenClient(app_id, private_key, url) + + def test_api(self): + # 创建请求 + request = MemberInfoGetRequest() + # 请求参数 + model = MemberInfoGetModel() + model.age = 22 + model.name = 'jim' + model.address = 'xx' + # 添加请求参数 + request.biz_model = model + + # 添加上传文件 + # files = { + # 'file1': open('aa.txt', 'rb'), + # 'file2': open('bb.txt', 'rb') + # } + # request.files = files + + # 调用请求 + response = self.client.execute(request) + + # 关闭文件 + # for f in files.values(): + # f.close() + + if response.is_success(): + print 'response: ', response + print 'is_vip:', response.get('member_info').get('is_vip', 0) + else: + print '请求失败,code:%s, msg:%s, sub_code:%s, sub_msg:%s' % \ + (response.code, response.msg, response.sub_code, response.sub_msg) + + def test_to_json_string(self): + model = MemberInfoGetModel() + model.age = 1 + model.name = '张三' + model.address = 'xx' + json_string = JsonUtil.to_json_string(model) + print 'json:', json_string + + +if __name__ == '__main__': + unittest.main() diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java b/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java index 7afb7bd2..7c04af0d 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java +++ b/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java @@ -59,6 +59,7 @@ public class SandboxController { public SandboxResult proxy( @RequestParam String appId , @RequestParam String privateKey + , @RequestParam(required = false) String token , @RequestParam String method , @RequestParam String version , @RequestParam String bizContent @@ -82,6 +83,10 @@ public class SandboxController { params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); params.put("version", version); + if (StringUtils.isNotBlank(token)) { + params.put("app_auth_token", token); + } + // 业务参数 params.put("biz_content", bizContent); diff --git a/sop-website/src/main/resources/public/pages/sandbox/sandbox.html b/sop-website/src/main/resources/public/pages/sandbox/sandbox.html index 3514d938..0f389a68 100644 --- a/sop-website/src/main/resources/public/pages/sandbox/sandbox.html +++ b/sop-website/src/main/resources/public/pages/sandbox/sandbox.html @@ -117,6 +117,10 @@ PrivateKey + + Token + + diff --git a/sop-website/src/main/resources/public/pages/sandbox/sandbox.js b/sop-website/src/main/resources/public/pages/sandbox/sandbox.js index 851f0f56..4798ec18 100644 --- a/sop-website/src/main/resources/public/pages/sandbox/sandbox.js +++ b/sop-website/src/main/resources/public/pages/sandbox/sandbox.js @@ -43,7 +43,7 @@ function selectItem(docItem, layui) { function buildHttpMethodOptions(docItem) { var methodList = docItem.httpMethodList; - var html = [] + var html = []; for (var i = 0; i < methodList.length; i++) { var method = methodList[i]; html.push(''); @@ -118,6 +118,7 @@ function doTest() { var data = { appId: $('#appId').val() , privateKey: $('#privateKey').val() + , token: $('#token').val() , method: method , version: version , httpMethod: $('#httpMethodList').val()