- 新增ISV用户平台

- 新增门户网站(portal)
- 新增`C++`,`Rust`语言SDK
This commit is contained in:
tanghc
2020-11-07 10:55:12 +08:00
parent 1370883af9
commit 6ab696dfaf
599 changed files with 34834 additions and 141 deletions

View File

@@ -0,0 +1,112 @@
use std::collections::HashMap;
use chrono::Local;
use serde::de::DeserializeOwned;
use crate::http::HttpTool;
use crate::request::{Request, RequestType};
use crate::sign::{SignType, SignUtil};
use serde_json::Value;
pub struct OpenClient {
/// 应用ID
pub app_id: &'static str,
/// 应用私钥PKCS#1
pub private_key: &'static str,
/// 请求url
pub url: &'static str,
}
impl OpenClient {
/// 发送请求
///
/// - request 请求对象
///
/// 返回结果
pub fn execute<T: DeserializeOwned>(&self, request: impl Request) -> T {
self.execute_token(request, "")
}
///发送请求
///
/// - request 请求对象
/// - tokentoken
///
/// 返回结果
pub fn execute_token<T: DeserializeOwned>(&self, request: impl Request, token: &'static str) -> T {
let struct_obj: T;
let request_type = request.get_request_type();
let headers = &OpenClient::get_default_headers();
let all_params = &self.build_params(&request, token);
if request.get_base().files.len() > 0 {
let base = request.get_base();
let files = &base.files;
let resp = HttpTool::post_file(self.url, all_params, files, headers);
struct_obj = self.parse_response(resp, &request);
} else {
match request_type {
RequestType::Get => {
let resp = HttpTool::get(self.url, all_params, headers);
struct_obj = self.parse_response(resp, &request);
}
RequestType::PostForm => {
let resp = HttpTool::post_form(self.url, all_params, headers);
struct_obj = self.parse_response(resp, &request);
}
RequestType::PostJson => {
let resp = HttpTool::post_json(self.url, all_params, headers);
struct_obj = self.parse_response(resp, &request);
}
RequestType::PostFile => {
let base = request.get_base();
let files = &base.files;
let resp = HttpTool::post_file(self.url, all_params, files, headers);
struct_obj = self.parse_response(resp, &request);
}
}
}
struct_obj
}
fn get_default_headers() -> HashMap<&'static str, &'static str> {
let mut headers = HashMap::new();
headers.insert("Accept-Encoding", "identity");
headers
}
fn parse_response<T: DeserializeOwned>(&self, resp: reqwest::blocking::Response, request: &impl Request) -> T {
let root: HashMap<String, Value> = resp.json().expect("error");
request.parse_response(root)
}
/// 构建请求参数
fn build_params(&self, request: &impl Request, token: &str) -> HashMap<&'static str, String> {
let method = request.get_method();
let version = request.get_version();
let mut all_params = HashMap::new();
all_params.insert("app_id", self.app_id.to_string());
all_params.insert("method", method.to_string());
all_params.insert("charset", "UTF-8".to_string());
all_params.insert("timestamp", Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
all_params.insert("version", version.to_string());
// 添加业务参数
for entry in &request.get_base().biz_model {
all_params.insert(entry.0, entry.1.to_string());
}
if !token.is_empty() {
all_params.insert("app_auth_token", token.to_string());
}
// 创建签名
let sign = SignUtil::create_sign(&all_params, self.private_key, SignType::RSA2);
all_params.insert("sign", sign);
all_params
}
}

View File

@@ -0,0 +1,158 @@
/*
Cargo.toml:
[dependencies]
reqwest = { version = "0.10", features = ["blocking", "json"] }
tokio = { version = "0.2", features = ["full"] }
serde = { version = "1.0.114", features = ["derive"] }
serde_json = "1.0.56"
*/
use std::collections::HashMap;
use reqwest::blocking::Response;
use reqwest::header::{HeaderMap, HeaderValue, HeaderName};
use std::str::FromStr;
use serde::Serialize;
/// HTTP请求工具
pub struct HttpTool {}
/// 上传文件对象
pub struct UploadFile {
/// 上传文件表单名称
pub name: &'static str,
/// 文件全路径
pub path: String,
}
impl HttpTool {
/// get请求
///
/// - url请求url
/// - params请求参数
/// - headers请求header
///
/// # Example
///
/// ```rust
/// let mut params = HashMap::new();
/// params.insert("name", "Jim");
/// params.insert("age", "12");
/// let resp = HttpTool::get(url, &params, &HashMap::new());
/// ```
/// return: Response对象
pub fn get<T: Serialize + ?Sized>(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response {
let client = reqwest::blocking::Client::new();
let res = client
.get(url)
.query(params)
.headers(super::http::HttpTool::get_headers_map(headers))
.send();
let resp = res.expect("request error");
resp
}
/// post模拟表单请求
///
/// - url请求url
/// - params请求参数
/// - headers请求header
///
/// # Example
///
/// ```rust
/// let mut params = HashMap::new();
/// params.insert("name", "Jim");
/// params.insert("age", "12");
/// let resp = HttpTool::post_form(url, &params, &HashMap::new());
/// ```
/// return: Response对象
pub fn post_form<T: Serialize + ?Sized>(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response {
let client = reqwest::blocking::Client::new();
let res = client
.post(url)
.form(params)
.headers(super::http::HttpTool::get_headers_map(headers))
.send();
let resp = res.expect("request error");
resp
}
/// post发送json
///
/// - url请求url
/// - params请求参数
/// - headers请求header
///
/// # Example
///
/// ```rust
/// let mut params = HashMap::new();
/// params.insert("name", "Jim");
/// params.insert("age", "12");
/// let resp = HttpTool::post_json(url, &params, &HashMap::new());
/// ```
/// return: Response对象
pub fn post_json<T: Serialize + ?Sized>(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response {
let client = reqwest::blocking::Client::new();
let res = client
.post(url)
.json(params)
.headers(super::http::HttpTool::get_headers_map(headers))
.send();
let resp = res.expect("request error");
resp
}
/// post上传文件
///
/// - url请求url
/// - params请求参数
/// - files上传文件
/// - headers请求header
///
/// # Example
///
/// ```rust
/// let mut params = HashMap::new();
/// params.insert("name", String::from("Jim"));
/// params.insert("age",String::from("12"));
///
/// // 设置上传文件
/// let mut files = vec![
/// UploadFile { name:"file1", path: String::from("/User/xx/aa.txt") },
/// UploadFile { name:"file2", path: String::from("/User/xx/bb.txt") },
/// ];
///
/// let resp = HttpTool::post_file(url, &params, &files, &HashMap::new());
/// ```
/// return: Response对象
pub fn post_file(url: &str, params: &HashMap<&str, String>, files: &Vec<UploadFile>, headers: &HashMap<&str, &str>) -> Response {
let client = reqwest::blocking::Client::new();
let mut form = reqwest::blocking::multipart::Form::new();
// 添加普通参数
for entry in params {
form = form.text(entry.0.to_string(), entry.1.to_string());
}
// 添加文件
for file in files {
form = form.file(file.name.to_string(), file.path.to_string()).unwrap();
}
let res = client
.post(url)
.multipart(form)
.headers(super::http::HttpTool::get_headers_map(headers))
.send();
let resp = res.expect("request error");
resp
}
fn get_headers_map(headers: &HashMap<&str, &str>) -> HeaderMap {
let mut header_map = HeaderMap::new();
for entry in headers {
header_map.insert(HeaderName::from_str(entry.0).unwrap(), HeaderValue::from_str(entry.1).unwrap());
}
header_map
}
}

View File

@@ -0,0 +1,14 @@
pub mod sign;
pub mod client;
pub mod request;
pub mod response;
pub mod http;
extern crate chrono;
/*#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}*/

View File

@@ -0,0 +1,26 @@
use crate::request::{BaseRequest, Request, RequestType};
pub struct MemberInfoGetRequest {
// 固定这么写
pub base: BaseRequest
}
impl Request for MemberInfoGetRequest {
fn get_method(&self) -> &str {
"member.info.get"
}
fn get_version(&self) -> &str {
"1.0"
}
fn get_request_type(&self) -> RequestType {
RequestType::Get
}
// 固定这么写
fn get_base(&self) -> &BaseRequest {
&self.base
}
}

View File

@@ -0,0 +1,45 @@
use std::collections::HashMap;
use serde_json::Value;
use crate::http::UploadFile;
use serde::de::DeserializeOwned;
pub mod memberinfoget;
pub enum RequestType {
Get,
PostJson,
PostForm,
PostFile,
}
pub trait Request {
/// 返回接口名称
fn get_method(&self) -> &str;
/// 返回版本号
fn get_version(&self) -> &str;
/// 返回请求方式
fn get_request_type(&self) -> RequestType;
/// 返回base
fn get_base(&self) -> &BaseRequest;
fn parse_response<T: DeserializeOwned>(&self, root: HashMap<String, Value>) -> T {
let mut data = root.get("error_response");
if data.is_none() {
let data_name = self.get_method().replace(".", "_") + "_response";
data = root.get(data_name.as_str());
}
let value = serde_json::to_value(data.unwrap()).unwrap();
serde_json::from_value(value).unwrap()
}
}
pub struct BaseRequest {
pub biz_model: HashMap<&'static str, String>,
pub files: Vec<UploadFile>
}

View File

@@ -0,0 +1,36 @@
extern crate serde;
use serde::{Deserialize};
// 响应参数
#[derive(Deserialize, Debug)]
pub struct MemberInfoGetResponse {
// ~~~ 固定部分 ~~~
pub code: String,
pub msg: String,
// json中可能没有sub_code属性因此需要加上 #[serde(default)]
// 详见https://serde.rs/field-attrs.html
#[serde(default)]
pub sub_code: String,
#[serde(default)]
pub sub_msg: String,
// ~~~ 固定部分 ~~~
// 下面是业务字段
#[serde(default)]
pub id: u32,
#[serde(default)]
pub name: String,
pub member_info: MemberInfo
}
#[derive(Deserialize, Debug)]
pub struct MemberInfo {
#[serde(default)]
pub is_vip: i8,
#[serde(default)]
pub vip_endtime: String
}

View File

@@ -0,0 +1 @@
pub mod memberinfoget;

View File

@@ -0,0 +1,108 @@
extern crate rsa;
extern crate crypto;
use std::collections::HashMap;
use rsa::{RSAPrivateKey, PaddingScheme};
use rsa::Hash;
use crypto::sha2::Sha256;
use crypto::digest::Digest;
use std::iter::repeat;
pub struct SignUtil {}
pub enum SignType {
RSA,
RSA2,
}
pub enum HashType {
Sha1,
Sha256
}
impl SignUtil {
pub fn create_sign(all_params: &HashMap<&str, String>, private_key: &str, sign_type: SignType) -> String {
let content = SignUtil::get_sign_content(all_params);
// println!("content:{}", content);
let hash_type;
match sign_type {
SignType::RSA => hash_type = HashType::Sha1,
SignType::RSA2 => hash_type = HashType::Sha256,
}
SignUtil::rsa_sign(content.as_str(), private_key, hash_type)
}
/// RSA签名
///
/// - content: 签名内容
/// - private_key: 私钥PKCS#1
/// - hash_type: hash类型
///
/// # Examples
///
/// ```
/// use sdk::sign::{SignUtil, HashType};
///
/// let content = "123";
/// let private_key = "your private key";
/// let sign = SignUtil::rsa_sign(content, private_key, HashType::Sha256);
///
/// println!("sign:{}", sign);
/// ```
/// return: 返回base64字符串
pub fn rsa_sign(content: &str, private_key: &str, hash_type: HashType) -> String {
// 格式化私钥
let der_encoded = private_key
.lines()
.filter(|line| !line.starts_with("-"))
.fold(String::new(), |mut data, line| {
data.push_str(&line);
data
});
let der_bytes = base64::decode(der_encoded).expect("failed to decode base64 content");
// 获取私钥对象
let private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key");
// 创建一个Sha256对象
let mut hasher = Sha256::new();
// 对内容进行摘要
hasher.input_str(content);
// 将摘要结果保存到buf中
let mut buf: Vec<u8> = repeat(0).take((hasher.output_bits()+7)/8).collect();
hasher.result(&mut buf);
// 对摘要进行签名
let hash;
match hash_type {
HashType::Sha1 => hash = Hash::SHA1,
HashType::Sha256 => hash = Hash::SHA2_256
}
let sign_result = private_key.sign(PaddingScheme::PKCS1v15Sign {hash: Option::from(hash) }, &buf);
// 签名结果转化为base64
let vec = sign_result.expect("create sign error for base64");
base64::encode(vec)
}
fn get_sign_content(all_params: &HashMap<&str, String>) -> String {
let mut content = Vec::new();
let keys = all_params.keys();
let mut sorted_keys = Vec::new();
for key in keys {
sorted_keys.push(key);
}
// 排序
sorted_keys.sort();
for key in sorted_keys {
let val = all_params.get(key);
if val.is_some() {
content.push(key.to_string() + "=" + val.unwrap());
}
}
content.join("&")
}
}