123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784 |
- //! 盛大数据模型
- use crate::{
- backend::{self},
- config::Config,
- gmssl, Sign, Verify,
- };
- use anyhow::Context;
- use axum::{response::IntoResponse, Json};
- use md5::{Digest, Md5};
- use serde::de::DeserializeOwned;
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct PubReqHead {
- pub app_id: String,
- pub method: String,
- pub sign_type: String,
- pub sign: String,
- pub timestamp: String, // yyyy-MM-dd HH:mm:ss
- pub version: String,
- pub request_id: String,
- pub biz_content: String,
- }
- pub trait TryFromPubHeadConfig: Sized {
- fn try_from_pub_req_head_config(head: &PubReqHead, config: &Config) -> anyhow::Result<Self>;
- }
- impl<T> TryFromPubHeadConfig for T
- where
- T: Sized + DeserializeOwned + crate::Verify,
- {
- fn try_from_pub_req_head_config(head: &PubReqHead, config: &Config) -> anyhow::Result<Self> {
- let req: T = head.decopy(config)?;
- req.verify(config).context("验证出错")?;
- Ok(req)
- }
- }
- impl crate::Verify for PubReqHead {
- #[tracing::instrument(skip_all, fields(PubReqHead=?self))]
- fn verify(&self, config: &Config) -> anyhow::Result<()> {
- if self.sign_type != "md5"
- || self.app_id != config.sd_config.app_id
- || self.method != "sd.tovc.recharge"
- {
- return Err(anyhow::Error::msg("PubReqHead Verify Value Error"));
- }
- match hex::decode(&self.sign) {
- Ok(o) => {
- let new_sign = self.sign_var(config);
- if o == new_sign {
- Ok(())
- } else {
- Err(anyhow::Error::msg("sign 不匹配"))
- }
- }
- Err(e) => {
- tracing::error!("verify: {self:?} error: {e}");
- Err(anyhow::Error::msg(format!(
- "PubReqHead Verify Value sign hex::decode Error: {e:#?}"
- )))
- }
- }
- }
- }
- impl Default for PubReqHead {
- fn default() -> Self {
- let time_format = time::macros::format_description!(
- "[year]-[month padding:zero]-[day padding:zero] [hour padding:zero]:[minute padding:zero]:[second padding:zero]"
- );
- let offset = time::UtcOffset::from_hms(8, 0, 0).unwrap();
- let now = time::OffsetDateTime::now_utc().to_offset(offset);
- Self {
- app_id: "".to_string(),
- method: "".to_string(),
- sign_type: "md5".to_string(),
- sign: "".to_string(),
- timestamp: now.format(time_format).unwrap(),
- version: "v1.0".to_string(),
- request_id: "".to_string(),
- biz_content: "".to_string(),
- }
- }
- }
- macro_rules! str_per {
- ( $($p:ident).*) => {
- (str_per!(@internal $($p).*), $($p).*)
- };
- (@internal $p:ident ) => {
- {
- let s = stringify!($p);
- let mut str = String::new();
- let mut capitalize = false;
- for i in s.chars(){
- if i == '_'{
- capitalize = true;
- }else if capitalize {
- str.push(i.to_ascii_uppercase());
- capitalize = false;
- }else{
- str.push(i)
- }
- }
- str
- }
- };
- (@internal $_p:ident.$($p:ident).*) => {
- str_per!(@internal $($p).*)
- };
- }
- macro_rules! bt_map {
- ( $( ($k:expr, $v:expr) ),* $(,)?) => {
- {
- let mut map = std::collections::BTreeMap::new();
- $(
- map.insert($k, $v)
- )*
- map
- }
- };
- ($($k:expr),* $(,)?) => {
- {
- let mut map = std::collections::BTreeMap::new();
- $(
- let entry: (_,_) = $k;
- map.insert(entry.0, entry.1);
- )*
- map
- }
- }
- }
- impl PubReqHead {
- pub fn encrypt_sign<T>(&mut self, config: &Config, data: T) -> anyhow::Result<()>
- where
- T: serde::Serialize,
- {
- let data = serde_json::to_string(&data)?;
- let key = config
- .sd_config
- .get_pk()
- .context("PayReqHead::encrypt_sign")?;
- let r = key.encrypt(data, gmssl::sm2::PassOrder::C1C3C2)?;
- let data = hex::encode(r);
- self.biz_content = data;
- self.sign(config);
- Ok(())
- }
- pub fn decopy<T>(&self, config: &Config) -> anyhow::Result<T>
- where
- T: for<'a> serde::Deserialize<'a>,
- {
- let key = config.sd_config.get_sk().context("PayReqHead::decopy")?;
- let data = hex::decode(&self.biz_content).context("hex::decode Error")?;
- let s = key
- .decrypt(data, gmssl::sm2::PassOrder::C1C3C2)
- .context("解密 biz_content")?;
- serde_json::from_slice(&s).context("反序列化 biz_content")
- }
- fn sign(&mut self, config: &Config) {
- self.sign = hex::encode_upper(self.sign_var(config))
- }
- pub fn sign_var(&self, config: &Config) -> Vec<u8> {
- let mut aa = Md5::new();
- let s = self.format_str(config);
- aa.update(s);
- let r = aa.finalize().to_vec();
- tracing::debug!("{self:?} Sing:{}", hex::encode(&r));
- r
- }
- pub fn format_str(&self, config: &Config) -> String {
- let Self {
- app_id,
- method,
- sign_type,
- sign,
- timestamp,
- version,
- request_id,
- biz_content,
- } = self;
- let map = bt_map!(
- str_per!(app_id),
- str_per!(method),
- str_per!(sign_type),
- str_per!(sign),
- str_per!(timestamp),
- str_per!(version),
- str_per!(request_id),
- str_per!(biz_content)
- );
- let items: Vec<_> = map
- .into_iter()
- .map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned()))
- .filter(|(k, v)| !v.is_empty() && k != "sign")
- .map(|(k, v)| format!("{}={}", k, v))
- .collect();
- let mut result = items.join("&");
- result.push('&');
- result.push_str(&format!("key={}", config.sd_config.sin_key));
- result
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct PayReqBody {
- pub game_user_id: String,
- pub server_type: String,
- pub product_no: String,
- pub sp_order_id: String,
- pub source: String,
- }
- impl crate::Verify for PayReqBody {
- fn verify(&self, _: &Config) -> anyhow::Result<()> {
- if self.server_type != "tel" {
- return Err(anyhow::Error::msg("仅支持话费"));
- }
- Ok(())
- }
- }
- impl PayReqBody {
- pub fn into_backend_pay_req(
- &self,
- head: &PubReqHead,
- config: &Config,
- ) -> backend::Body<backend::PayReqBody> {
- let timestamp = head.timestamp.trim().replace(
- |c: char| c.is_whitespace() || c == '-' || c == '_' || c == ':',
- "",
- );
- let mut body = backend::Body {
- header: backend::Header {
- timestamp,
- seqno: head.request_id.clone(),
- appid: config.fscg_config.app_id.clone(),
- ..Default::default()
- },
- msgbody: backend::PayReqBody {
- content: Some(backend::PayReqContent {
- user: self.game_user_id.clone(),
- packageid: self.product_no.clone(),
- extorder: self.sp_order_id.clone(),
- note: Some(self.source.clone()),
- ..Default::default()
- }),
- resp: None,
- },
- };
- body.sign(config);
- body
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct PayRespBodyData {
- state: String,
- order_cash: String,
- order_id: String,
- sp_order_id: String,
- game_user_id: String,
- product_name: String,
- }
- pub type PayRespBody = PubRespBody<PayRespBodyData>;
- impl PayRespBody {
- pub fn from_backend_pay_resp(
- config: &Config,
- body: backend::Body<backend::PayRespBody>,
- req: &PayReqBody,
- ) -> Self {
- if let Err(e) = body.verify(config) {
- // return Self::error("99", "服务响应验证错误");
- return Self::error("99", format!("服务响应验证错误: {:#?}", e));
- }
- let resp = match body.msgbody.resp {
- Some(resp) => resp,
- None => {
- return Self::error("99", "服务没有响应字段: resp");
- }
- };
- let content = match body.msgbody.content {
- Some(content) => content,
- None => {
- return Self::error(
- resp.rcode,
- resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()),
- );
- }
- };
- let state = if resp.rcode == "00" {
- "0".to_string()
- } else {
- "9".to_string()
- };
- Self::success(PayRespBodyData {
- state,
- order_cash: "".to_string(),
- order_id: content.orderid,
- sp_order_id: content.extorder,
- game_user_id: req.game_user_id.clone(),
- product_name: req.product_no.clone(),
- })
- }
- }
- impl crate::Sign for PayRespBody {
- fn sign(&mut self, _: &Config) {}
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct BalanceRespBody {
- pub result_code: String,
- pub result_desc: String,
- pub balance: Option<String>,
- }
- impl BalanceRespBody {
- pub fn from_backend_account_resp(
- config: &Config,
- body: backend::Body<backend::AccountRespBody>,
- ) -> Self {
- if let Err(e) = body.verify(config) {
- return Self::error("99", format!("服务响应验证错误: {:#?}", e));
- }
- let resp = match body.msgbody.resp {
- Some(resp) => resp,
- None => {
- return Self::error("99", "服务没有响应字段: resp");
- }
- };
- let content = match body.msgbody.content {
- Some(content) => content,
- None => {
- return Self::error(
- resp.rcode,
- resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()),
- );
- }
- };
- Self::success(content.balance)
- }
- pub fn success(data: impl Into<Option<String>>) -> Self {
- Self {
- result_code: "000".to_string(),
- result_desc: "success".to_string(),
- balance: data.into(),
- }
- }
- pub fn error(code: impl ToString, desc: impl ToString) -> Self {
- Self {
- result_code: code.to_string(),
- result_desc: desc.to_string(),
- balance: None,
- }
- }
- }
- impl crate::Sign for BalanceRespBody {
- fn sign(&mut self, _: &Config) {}
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct BalanceReqBody {
- source: String,
- }
- impl BalanceReqBody {
- pub fn into_backend_account_req(
- &self,
- config: &Config,
- head: &PubReqHead,
- ) -> backend::Body<backend::AccountReqBody> {
- let backend_req = backend::AccountReqBody::new();
- let mut body = backend::Body {
- header: backend::Header {
- seqno: head.request_id.clone(),
- appid: config.fscg_config.app_id.clone(),
- ..Default::default()
- },
- msgbody: backend_req,
- };
- body.sign(config);
- body
- }
- }
- impl crate::Verify for BalanceReqBody {
- fn verify(&self, _: &Config) -> anyhow::Result<()> {
- if self.source == "SD" {
- Ok(())
- } else {
- Err(anyhow::Error::msg("BalanceReqBody Verify False"))
- }
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct SearchReqBody {
- sp_order_id: String,
- }
- impl SearchReqBody {
- pub fn into_backend_search_req(
- &self,
- head: &PubReqHead,
- config: &Config,
- ) -> backend::Body<backend::SearchReqBody> {
- let mut body = backend::Body {
- header: backend::Header {
- seqno: head.request_id.clone(),
- appid: config.fscg_config.app_id.clone(),
- ..Default::default()
- },
- msgbody: backend::SearchReqBody {
- content: Some(backend::SearchReqContent {
- // orderid: Some(req.sp_order_id.clone()),
- extorderid: Some(self.sp_order_id.clone()),
- ..Default::default()
- }),
- resp: None,
- },
- };
- body.sign(config);
- body
- }
- }
- impl crate::Verify for SearchReqBody {}
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct SearchRespData {
- state: String,
- message: String,
- success_time: Option<String>,
- order_id: String,
- sp_order_id: String,
- game_user_id: String,
- operator_order: Option<String>,
- card_no: Option<String>, // 卡密
- card_pwd: Option<String>, // 卡密
- effective_time: Option<String>, // 卡密
- }
- pub type SearchRespBody = PubRespBody<SearchRespData>;
- impl SearchRespBody {
- pub fn from_backend_search_resp(
- config: &Config,
- body: backend::Body<backend::SearchRespBody>,
- ) -> Self {
- if let Err(e) = body.verify(config) {
- Self::error("99", format!("服务响应验证错误: {:#?}", e));
- }
- let resp = match body.msgbody.resp {
- Some(resp) => resp,
- None => {
- return Self::error("99", "服务没有响应字段: resp".to_string());
- }
- };
- let content = match body.msgbody.content {
- Some(content) => content,
- None => {
- return Self::error(
- resp.rcode,
- resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()),
- );
- }
- };
- let state = if content.status == "6" {
- "1"
- } else if content.status == "4" {
- "9"
- } else {
- "0"
- };
- let time_str = if content.checktime.is_empty() {
- None
- } else {
- let time_format = time::macros::format_description!("[year]-[month padding:zero]-[day padding:zero] [hour padding:zero]:[minute padding:zero]:[second padding:zero]");
- let backend_time = time::macros::format_description!("[year][month padding:zero][day padding:zero][hour padding:zero][minute padding:zero][second padding:zero][subsecond digits:1+]");
- let time = time::PrimitiveDateTime::parse(&content.checktime, backend_time).unwrap();
- let time_str = time.format(time_format).unwrap();
- Some(time_str)
- };
- let data = SearchRespData {
- state: state.to_string(),
- message: content.msg,
- success_time: time_str,
- order_id: content.orderid,
- sp_order_id: content.extorder,
- game_user_id: content.user,
- operator_order: content.attr1,
- card_no: None,
- card_pwd: None,
- effective_time: None,
- };
- Self::success(data)
- }
- }
- impl crate::Sign for SearchRespBody {
- fn sign(&mut self, _: &Config) {}
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct PubRespBody<T> {
- pub result_code: String,
- pub result_desc: String,
- pub data: Option<T>,
- }
- impl<T> PubRespBody<T> {
- pub fn success(data: T) -> Self {
- Self {
- result_code: "000".to_string(),
- result_desc: "success".to_string(),
- data: Some(data),
- }
- }
- pub fn error(code: impl ToString, desc: impl ToString) -> Self {
- Self {
- result_code: code.to_string(),
- result_desc: desc.to_string(),
- data: None,
- }
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct PubRespHead {
- #[serde(default = "String::new")]
- pub code: String,
- #[serde(default = "String::new")]
- pub msg: String,
- #[serde(default = "String::new")]
- pub data: String,
- #[serde(default = "String::new")]
- pub sign: String,
- #[serde(default = "String::new")]
- pub request_id: String,
- }
- impl IntoResponse for PubRespHead {
- fn into_response(self) -> axum::response::Response {
- Json(self).into_response()
- }
- }
- impl PubRespHead {
- pub fn new(config: &Config, data: impl ToString, request_id: impl ToString) -> Self {
- let mut result = Self {
- data: data.to_string(),
- request_id: request_id.to_string(),
- ..Default::default()
- };
- result.sign(config);
- result
- }
- pub fn sign(&mut self, config: &Config) {
- self.sign = hex::encode_upper(self.sign_var(config))
- }
- pub fn sign_var(&self, config: &Config) -> Vec<u8> {
- let mut aa = Md5::new();
- let s = self.format_str(config);
- aa.update(&s);
- let r = aa.finalize().to_vec();
- tracing::debug!(sign = hex::encode(&r), data = s, "签名");
- r
- }
- pub fn format_str(&self, config: &Config) -> String {
- let Self {
- code,
- msg,
- data,
- sign,
- request_id,
- } = self;
- let map = bt_map!(
- str_per!(code),
- str_per!(msg),
- str_per!(data),
- str_per!(sign),
- str_per!(request_id),
- );
- let items: Vec<_> = map
- .into_iter()
- .map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned()))
- .filter(|(k, v)| !v.is_empty() && k != "sign")
- .map(|(k, v)| format!("{}={}", k, v))
- .collect();
- let mut result = items.join("&");
- result.push('&');
- result.push_str(&format!("key={}", config.sd_config.sin_key));
- result
- }
- }
- impl Default for PubRespHead {
- fn default() -> Self {
- Self {
- code: "000".to_string(),
- msg: "success".to_string(),
- data: "".to_string(),
- sign: "".to_string(),
- request_id: "".to_string(),
- }
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct CallbackReqBody {
- pub state: String,
- pub message: String,
- pub order_id: String,
- pub sp_order_id: String,
- pub game_user_id: String,
- pub operator_order: Option<String>,
- pub card_no: Option<String>,
- pub card_pwd: Option<String>,
- pub effective_time: Option<String>,
- }
- impl Default for CallbackReqBody {
- fn default() -> Self {
- Self {
- state: "".to_string(),
- message: "".to_string(),
- order_id: "".to_string(),
- sp_order_id: "".to_string(),
- game_user_id: "".to_string(),
- operator_order: None,
- card_no: None,
- card_pwd: None,
- effective_time: None,
- }
- }
- }
- #[derive(serde::Serialize, serde::Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct CallbackRespBody {
- pub result_code: String,
- pub result_desc: String,
- }
- #[cfg(test)]
- #[allow(unused)]
- mod tests {
- use crate::config::SdConfig;
- use super::*;
- #[test]
- fn test_aaaa() {
- let s = "04f97e835ec530a95df4c999ab0184996b68464bf308239ebb692174812fb4c54349fc217ac5ab13ea8313e458e2cf37fa35345afc4290f8d25dc7c8bb37da1a0ff87a7752c8d03998067b3bf9fec1d7e397aba05fb016b3b48e269e9c87c22b6eef61508de4572c7dacd980530dac913f507ad9aa2a2a8c62edae35c21bf3aaa446168989a1350914499322ebb5b37038e432ed8f37e25083e35087b4fc1dbff3488c1e7cd7810712725a1fd78365a73892bf6b45352ca775b7b2e66a334ed93986728d2c2c78af2bbfaeb590d55a494163371c0c5df7";
- let head = PubReqHead{biz_content: s.to_string(), ..Default::default()};
- let cfg = Config::default();
- let r = head.decopy::<serde_json::Value>(&cfg);
- println!("{:#?}", r)
- }
- #[test]
- fn test_sign() {
- // 2C5A4C48A9BD07B79C250049A17C21C2
- // 2C5A4C48A9BD07B79C250049A17C21C2
- let mut a = PubReqHead {
- app_id: "asdaw".to_string(),
- method: "asdaw".to_string(),
- sign_type: "asdaw".to_string(),
- sign: "asdaw".to_string(),
- timestamp: "2023-03-23 16:25:00".to_string(),
- version: "asdaw".to_string(),
- request_id: "asdaw".to_string(),
- biz_content: "".to_string(),
- };
- let c = Default::default();
- a.sign(&c);
- println!("{a:?}");
- }
- #[test]
- fn feature() {
- let r = PayReqBody {
- game_user_id: "13856023633".to_string(),
- server_type: "tel".to_string(),
- product_no: "LD_5477".to_string(),
- sp_order_id: "QHGHFBGF0235641202".to_string(),
- source: "SD".to_string(),
- };
- let r = serde_json::to_string(&r).unwrap();
- println!("{r}")
- }
- #[test]
- fn test_try_into_fscg_req() {
- // 470EF517335D444BD003098D6F6ABCE0
- // 470EF517335D444BD003098D6F6ABCE0
- let now = std::time::Instant::now();
- let mut a = PubReqHead {
- app_id: "asdaw".to_string(),
- method: "asdaw".to_string(),
- sign_type: "asdaw".to_string(),
- sign: "asdaw".to_string(),
- timestamp: "2023-03-23 16:25:00".to_string(),
- version: "asdaw".to_string(),
- request_id: "asdaw".to_string(),
- biz_content: r#"{"gameUserId":"13856023633","serverType":"tel","productNo":"LD_5477","spOrderId":"QHGHFBGF0235641202","source":"SD"}"#.to_string(),
- };
- let c: Config = Default::default();
- // let r = c.sd_config.;
- let key = c.sd_config.get_sk().unwrap();
- let r = key
- .encrypt(a.biz_content, gmssl::sm2::PassOrder::C1C2C3)
- .unwrap();
- a.biz_content = hex::encode(r);
- a.sign(&c);
- println!("{a:?}");
- let new = std::time::Instant::now();
- println!("use Time{:?}", new - now);
- }
- }
|