gx
This commit is contained in:
1 parent
c2dcedd238
commit
16cdd3d46e
34 files changed
+837
-481
No files matched your search
@@ -15,5 +15,10 @@ namespace Store.Client.Agent
|
||||
/// 代理码
|
||||
/// </summary>
|
||||
public static string AgentCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代理名称
|
||||
/// </summary>
|
||||
public static string AgentName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Store.Client.Apis
|
||||
/// </summary>
|
||||
public DateTime GetServerTime()
|
||||
{
|
||||
HttpResult result = ServerRequest(new ServerHttpItem { URL = $"{_config.Host}/time.php" });
|
||||
HttpResult result = ServerRequest(new HttpItem { URL = $"{_config.Host}/time.php" });
|
||||
if (result.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
if (DateTime.TryParse(result.Html, out DateTime dt))
|
||||
@@ -42,7 +42,7 @@ namespace Store.Client.Apis
|
||||
/// </summary>
|
||||
public string GetAuthConfig()
|
||||
{
|
||||
ServerHttpItem item = new ServerHttpItem();
|
||||
HttpItem item = new HttpItem();
|
||||
item.URL = $"{_config.Host}?obj=api/config&act=auth_config";
|
||||
HttpResult result = ServerRequest(item);
|
||||
if (result.StatusCode == HttpStatusCode.OK)
|
||||
@@ -59,7 +59,7 @@ namespace Store.Client.Apis
|
||||
public string GetCacheServerRatio()
|
||||
{
|
||||
|
||||
ServerHttpItem item = new ServerHttpItem();
|
||||
HttpItem item = new HttpItem();
|
||||
item.URL = _config.Host + "?" + _config.ObjUrl + "config&act=cache_service_mode";
|
||||
item.Method = MethodType.Post;
|
||||
//item.SignUser();
|
||||
@@ -80,7 +80,7 @@ namespace Store.Client.Apis
|
||||
//public HttpResult GetImageCache(string imgUrl)
|
||||
//{
|
||||
// long id = MD5Helper.GetLong(imgUrl);
|
||||
// ServerHttpItem item = new ServerHttpItem();
|
||||
// HttpItem item = new HttpItem();
|
||||
// item.Method = MethodType.Post;
|
||||
// item.URL = _config.Host + "?" + _config.ObjUrl + "add_img";
|
||||
// item.Postdata = "act=get&table=" + Math.Abs(id % 1024) + "&id=" + id;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
using MAX.Core.Extensions;
|
||||
using MAX.Core.Helpers;
|
||||
using Store.Client.Agent;
|
||||
using Store.Client.Commons;
|
||||
using Store.Client.Extensions;
|
||||
using Store.Client.HttpExtension;
|
||||
using Store.Framework;
|
||||
using Store.Framework.Tools;
|
||||
@@ -26,7 +24,7 @@ namespace Store.Client.Apis
|
||||
{
|
||||
_logger.Info("123");
|
||||
int r2 = RandomHelper.GetRandomNumber(100000000, 899999999); //生成随机码
|
||||
ServerHttpItem item = new ServerHttpItem();
|
||||
HttpItem item = new HttpItem();
|
||||
item.URL = base.BuildServiceUrl("user/user_login");
|
||||
item.Method = MethodType.Post;
|
||||
Dictionary<string, string> param = new Dictionary<string, string>
|
||||
@@ -44,14 +42,13 @@ namespace Store.Client.Apis
|
||||
//{ "client_addr", _client.Address }
|
||||
};
|
||||
item.Postdata = param.ToUrlParams();
|
||||
item.Sign();
|
||||
HttpResult result = ServerRequest(item);
|
||||
if (result.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
string chCode = Regex.Match(result.Html, "<c>([^<]+)</c>")?.Groups[1]?.Value ?? "";
|
||||
if (!string.IsNullOrEmpty(chCode))
|
||||
{
|
||||
string codex = SignTool.Encryption(r2);
|
||||
string codex = Encryption(r2);
|
||||
if (chCode != codex)
|
||||
{
|
||||
result.StatusCode = HttpStatusCode.Unauthorized;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using MAX.Core.Infrastructure;
|
||||
using Store.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Framework
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端信息
|
||||
/// </summary>
|
||||
public class ClientInfo : SingleInstance<ClientInfo>
|
||||
{
|
||||
|
||||
public static UserInfo User
|
||||
{
|
||||
get
|
||||
{
|
||||
return Instance.UserInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ClientInfo()
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 客户端IP
|
||||
/// </summary>
|
||||
public string Ip { get; }
|
||||
/// <summary>
|
||||
/// 客户端地址
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户
|
||||
/// </summary>
|
||||
public UserInfo UserInfo { get; private set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using MAX.Core.Cryptography;
|
||||
using MAX.Core.Helpers;
|
||||
using Store.Client;
|
||||
using Store.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Client.Commons
|
||||
{
|
||||
/// <summary>
|
||||
/// 签名工具
|
||||
/// </summary>
|
||||
public class SignTool
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取签名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSign()
|
||||
{
|
||||
long time = GetReviseUnix();
|
||||
string s = Encryption(time);
|
||||
return "time=" + time + "&code=" + s + "&NbCodeVersion=1";
|
||||
}
|
||||
/// <summary>
|
||||
/// 用户签名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetUserSign()
|
||||
{
|
||||
return GetUserSign("", "");
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取当前用户签名
|
||||
/// </summary>
|
||||
/// <param name="shopId"></param>
|
||||
/// <param name="shopName"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetUserSign(string shopId, string shopName)
|
||||
{
|
||||
long time = GetReviseUnix();
|
||||
string time1 = Encryption(time);
|
||||
return "time=" + time + "&code=" + time1 + "&NbCodeVersion=1" + "&uid=" + ClientInfo.User.UserId + "&key=" + ClientInfo.User.UserKey +
|
||||
"&cid=" + ClientInfo.User.CompanyId + "&company=" + UrlHelper.Encode(ClientInfo.User.CompanyName) +
|
||||
"&sid=" + shopId + "&shop=" + UrlHelper.Encode(shopName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取校正后的时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetReviseUnix()
|
||||
{
|
||||
return DateHelper.GetCurrentUnixTimestampInSeconds() + ServerConfig.TimeDifferent;
|
||||
}
|
||||
/// <summary>
|
||||
/// 签名算法
|
||||
/// </summary>
|
||||
/// <param name="s">随机数</param>
|
||||
/// <returns>加密签名结果</returns>
|
||||
public static string Encryption(long s)
|
||||
{
|
||||
string value = $"{s}sdhewu*/fhAAue48485ew5+5%@3485esGFFHFD";
|
||||
return MD5Helper.Md5_32(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,11 @@ public static class Config
|
||||
{
|
||||
public const string AppVersion = "1312";
|
||||
|
||||
/// <summary>
|
||||
/// API 项目路径。
|
||||
/// </summary>
|
||||
public const string MainPath = "29wx";
|
||||
|
||||
public static bool IsRelease
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
using MAX.Core.Helpers;
|
||||
using Store.Client.Commons;
|
||||
using Store.Client.HttpExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Client.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求扩展
|
||||
/// </summary>
|
||||
public static class HttpItemExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加签名
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public static void Sign(this ServerHttpItem item)
|
||||
{
|
||||
SetSign(item, SignTool.GetSign());
|
||||
}
|
||||
/// <summary>
|
||||
/// 增加用户签名
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public static void SignUser(this ServerHttpItem item)
|
||||
{
|
||||
item.SignUser("", "");
|
||||
}
|
||||
/// <summary>
|
||||
/// 增加用户签名
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="shopId"></param>
|
||||
/// <param name="shopName"></param>
|
||||
public static void SignUser(this ServerHttpItem item, string shopId, string shopName)
|
||||
{
|
||||
SetSign(item, SignTool.GetUserSign(shopId, shopName));
|
||||
}
|
||||
private static void SetSign(ServerHttpItem item, string sign)
|
||||
{
|
||||
if (item.Method == MethodType.Get)
|
||||
{
|
||||
if (item.URL.Contains("?"))
|
||||
{
|
||||
item.URL += "&";
|
||||
}
|
||||
else
|
||||
{
|
||||
item.URL += "?";
|
||||
}
|
||||
item.URL += sign;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Postdata.Length > 0)
|
||||
{
|
||||
item.Postdata += "&";
|
||||
}
|
||||
item.Postdata += sign;
|
||||
}
|
||||
item.IsSign = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
using MAX.Core.Autofac;
|
||||
using MAX.Core.Autofac;
|
||||
using MAX.Core.Cryptography;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Helpers;
|
||||
using MAX.Core.Logger;
|
||||
using Store.Client.Extensions;
|
||||
using Store.Client.Agent;
|
||||
using Store.Framework;
|
||||
using Store.Framework.Inerterfaces;
|
||||
using Store.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
@@ -21,6 +27,7 @@ namespace Store.Client.HttpExtension
|
||||
[Autowired]
|
||||
protected ILoggerFacade _logger => LazyServiceProvider.Instance.LazyGetService<ILoggerFacade>();
|
||||
protected IApiServerConfig _config => LazyServiceProvider.Instance.LazyGetService<IApiServerConfig>();
|
||||
protected ICurrentUser CurrentUser => LazyServiceProvider.Instance.LazyGetService<ICurrentUser>();
|
||||
|
||||
/// <summary>
|
||||
/// 默认服务请求。
|
||||
@@ -69,39 +76,36 @@ namespace Store.Client.HttpExtension
|
||||
{
|
||||
return Request(BuildServiceUrl(urlpath), postdata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用当前 Key 构建服务请求地址。
|
||||
/// </summary>
|
||||
protected string BuildServiceUrl(string urlPath)
|
||||
{
|
||||
return $"{_config.Host}/wxxd_{urlPath}?obj=29wx/wxxd_{urlPath}";
|
||||
return $"{_config.Host}/wxxd_{urlPath}?obj={Config.MainPath}/wxxd_{urlPath}";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建并执行服务端请求,默认使用当前用户签名。
|
||||
/// </summary>
|
||||
protected HttpResult Request(string url, string postdata = "", MethodType method = MethodType.Post, bool isUserSign = true)
|
||||
{
|
||||
ServerHttpItem item = new ServerHttpItem
|
||||
HttpItem item = new HttpItem
|
||||
{
|
||||
URL = url,
|
||||
Method = method,
|
||||
Postdata = postdata ?? string.Empty
|
||||
};
|
||||
|
||||
if (isUserSign)
|
||||
{
|
||||
item.SignUser();
|
||||
}
|
||||
ApplySign(item, isUserSign);
|
||||
|
||||
return ServerRequest(item);
|
||||
return Send(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器请求。
|
||||
/// </summary>
|
||||
public HttpResult ServerRequest(ServerHttpItem item)
|
||||
public HttpResult ServerRequest(HttpItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -111,26 +115,15 @@ namespace Store.Client.HttpExtension
|
||||
#if RELEASE
|
||||
item.WebProxy = null;
|
||||
#endif
|
||||
if (!item.IsSign)
|
||||
{
|
||||
if (item.Shop != null)
|
||||
{
|
||||
item.SignUser(item.Shop.Id, item.Shop.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Sign();
|
||||
}
|
||||
}
|
||||
ApplySign(item, false);
|
||||
|
||||
return Send(item);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行底层 HTTP 请求。
|
||||
/// </summary>
|
||||
protected HttpResult Send(ServerHttpItem item)
|
||||
protected HttpResult Send(HttpItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -155,5 +148,98 @@ namespace Store.Client.HttpExtension
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为请求添加公共签名或用户签名。
|
||||
/// </summary>
|
||||
private void ApplySign(HttpItem item, bool isUserSign)
|
||||
{
|
||||
string sign = isUserSign
|
||||
? GetUserSign("", "")
|
||||
: GetSign();
|
||||
|
||||
SetSign(item, sign);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取公共签名。
|
||||
/// </summary>
|
||||
private static string GetSign()
|
||||
{
|
||||
long time = GetReviseUnix();
|
||||
string code = Encryption(time);
|
||||
return "time=" + time + "&code=" + code + "&NbCodeVersion=1";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户签名。
|
||||
/// </summary>
|
||||
private string GetUserSign(string shopId, string shopName)
|
||||
{
|
||||
UserInfo user = CurrentUser.User;
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException("当前用户未登录,无法生成用户签名。");
|
||||
}
|
||||
|
||||
long time = GetReviseUnix();
|
||||
string code = Encryption(time);
|
||||
var dict = new Dictionary<string, string>
|
||||
{
|
||||
{ "time", time.ToString() },
|
||||
{ "code", code },
|
||||
{ "code_version", "1" },
|
||||
{ "version", Config.Version },
|
||||
{ "user_name", user.UserName },
|
||||
{ "path", Config.MainPath }
|
||||
};
|
||||
if (!string.IsNullOrEmpty(AgentConfig.AgentCode))
|
||||
{
|
||||
dict.Add("agent_code", AgentConfig.AgentCode);
|
||||
dict.Add("agent_name", AgentConfig.AgentName);
|
||||
}
|
||||
|
||||
return string.Join("&", dict.Select(kv => $"{kv.Key}={kv.Value}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取校正后的时间戳。
|
||||
/// </summary>
|
||||
protected static long GetReviseUnix()
|
||||
{
|
||||
return DateHelper.GetCurrentUnixTimestampInSeconds() + ServerConfig.TimeDifferent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 签名算法。
|
||||
/// </summary>
|
||||
/// <param name="s">随机数</param>
|
||||
/// <returns>加密签名结果</returns>
|
||||
protected static string Encryption(long s)
|
||||
{
|
||||
string value = $"{s}sdhewu*/fhAAue48485ew5+5%@3485esGFFHFD";
|
||||
return MD5Helper.Md5_32(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将签名参数拼接到请求。
|
||||
/// </summary>
|
||||
private static void SetSign(HttpItem item, string sign)
|
||||
{
|
||||
if (item.Method == MethodType.Get)
|
||||
{
|
||||
item.URL += item.URL.Contains("?") ? "&" : "?";
|
||||
item.URL += sign;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Postdata = item.Postdata ?? string.Empty;
|
||||
if (item.Postdata.Length > 0)
|
||||
{
|
||||
item.Postdata += "&";
|
||||
}
|
||||
item.Postdata += sign;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,28 @@
|
||||
|
||||
using MAX.Core.Helpers;
|
||||
using Store.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
//using MAX.Core.Helpers;
|
||||
//using Store.Framework.Models;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Client.HttpExtension
|
||||
{
|
||||
public class ServerHttpItem : HttpItem
|
||||
{
|
||||
public ShopBase Shop { get; private set; }
|
||||
public ServerHttpItem()
|
||||
{
|
||||
//namespace Store.Client.HttpExtension
|
||||
//{
|
||||
// public class HttpItem : HttpItem
|
||||
// {
|
||||
// public ShopBase Shop { get; private set; }
|
||||
// public HttpItem()
|
||||
// {
|
||||
|
||||
}
|
||||
public ServerHttpItem(ShopBase shop)
|
||||
{
|
||||
Shop = shop;
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否已签名
|
||||
/// </summary>
|
||||
public bool IsSign { get; set; }
|
||||
}
|
||||
}
|
||||
// }
|
||||
// public HttpItem(ShopBase shop)
|
||||
// {
|
||||
// Shop = shop;
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// 是否已签名
|
||||
// /// </summary>
|
||||
// public bool IsSign { get; set; }
|
||||
// }
|
||||
//}
|
||||
@@ -76,7 +76,7 @@
|
||||
// /// <summary>
|
||||
// /// 执行底层 HTTP 请求。
|
||||
// /// </summary>
|
||||
// protected HttpResult Send(ServerHttpItem item)
|
||||
// protected HttpResult Send(HttpItem item)
|
||||
// {
|
||||
// if (item == null)
|
||||
// {
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Framework.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Cookie集合,支持普通 Cookie 字符串和 API 返回的 Cookie JSON。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(CookiesJsonConverter))]
|
||||
public class Cookies : Dictionary<string, string>
|
||||
{
|
||||
private readonly string oldCookie;
|
||||
|
||||
public Cookies(string cookie)
|
||||
{
|
||||
oldCookie = cookie;
|
||||
SplitCookie(oldCookie);
|
||||
Load(cookie);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cookie字符串
|
||||
/// </summary>
|
||||
@@ -26,28 +31,6 @@ namespace Store.Framework.Models
|
||||
return GetCookieString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拆分Cookie
|
||||
/// </summary>
|
||||
private void SplitCookie(string cookie)
|
||||
{
|
||||
this.Clear();
|
||||
Regex regCookie = new Regex("(?<key>.+?)=(?<value>.+?)(;|$)");
|
||||
if (regCookie.IsMatch(cookie))
|
||||
{
|
||||
MatchCollection matchs = regCookie.Matches(cookie);
|
||||
foreach (Match item in matchs)
|
||||
{
|
||||
string key = item.Groups["key"].Value.Trim();
|
||||
string value = item.Groups["value"].Value.Trim();
|
||||
if (!ContainsKey(key))
|
||||
{
|
||||
this.Add(key.Trim(), value.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定值
|
||||
@@ -63,6 +46,7 @@ namespace Store.Framework.Models
|
||||
}
|
||||
return defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加或修改
|
||||
/// </summary>
|
||||
@@ -79,6 +63,7 @@ namespace Store.Framework.Models
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Cookies字符串
|
||||
/// </summary>
|
||||
@@ -89,14 +74,156 @@ namespace Store.Framework.Models
|
||||
return string.Join(";", list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转为浏览器 Cookie 字符串。
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var list = this.Select(a => { return $"{a.Key}={a.Value}"; });
|
||||
return string.Join("; ", list);
|
||||
}
|
||||
/// <summary>
|
||||
/// 隐式转换
|
||||
/// </summary>
|
||||
/// <param name="cookie"></param>
|
||||
public static implicit operator string (Cookies cookie)
|
||||
public static implicit operator string(Cookies cookie)
|
||||
{
|
||||
return cookie.GetCookieString();
|
||||
}
|
||||
|
||||
internal static Cookies FromToken(JToken token)
|
||||
{
|
||||
Cookies cookies = new Cookies(string.Empty);
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return cookies;
|
||||
|
||||
if (token.Type == JTokenType.String)
|
||||
return new Cookies(token.ToString());
|
||||
|
||||
cookies.AddToken(token);
|
||||
return cookies;
|
||||
}
|
||||
|
||||
private void Load(string cookie)
|
||||
{
|
||||
Clear();
|
||||
if (string.IsNullOrWhiteSpace(cookie))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
AddToken(JToken.Parse(cookie));
|
||||
if (Count > 0)
|
||||
return;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
SplitCookie(Decode(cookie));
|
||||
}
|
||||
|
||||
private void AddToken(JToken token)
|
||||
{
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return;
|
||||
|
||||
if (token is JArray array)
|
||||
{
|
||||
foreach (JToken item in array)
|
||||
{
|
||||
AddCookie(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AddCookie(token);
|
||||
}
|
||||
|
||||
private void AddCookie(JToken token)
|
||||
{
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return;
|
||||
|
||||
if (token.Type == JTokenType.String)
|
||||
{
|
||||
string value = token.ToString();
|
||||
int index = value.IndexOf('=');
|
||||
if (index <= 0)
|
||||
return;
|
||||
|
||||
AddOrUpdate(value.Substring(0, index).Trim(), value.Substring(index + 1).Trim());
|
||||
return;
|
||||
}
|
||||
|
||||
JToken name = token["Name"] ?? token["name"];
|
||||
JToken valueToken = token["Value"] ?? token["value"];
|
||||
if (name == null || valueToken == null)
|
||||
return;
|
||||
|
||||
AddOrUpdate(name.ToString(), valueToken.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拆分普通 Cookie 字符串。
|
||||
/// </summary>
|
||||
private void SplitCookie(string cookie)
|
||||
{
|
||||
Clear();
|
||||
if (string.IsNullOrEmpty(cookie))
|
||||
return;
|
||||
|
||||
Regex regCookie = new Regex("(?<key>.+?)=(?<value>.+?)(;|$)");
|
||||
if (regCookie.IsMatch(cookie))
|
||||
{
|
||||
MatchCollection matchs = regCookie.Matches(cookie);
|
||||
foreach (Match item in matchs)
|
||||
{
|
||||
string key = item.Groups["key"].Value.Trim();
|
||||
string value = item.Groups["value"].Value.Trim();
|
||||
if (!ContainsKey(key))
|
||||
{
|
||||
this.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string Decode(string value)
|
||||
{
|
||||
return (value ?? string.Empty)
|
||||
.Replace("&", "&")
|
||||
.Replace("%", "%")
|
||||
.Replace("+", "+")
|
||||
.Replace(""", "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cookie JSON 转换器。
|
||||
/// </summary>
|
||||
public class CookiesJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Cookies).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
return new Cookies(reader.Value?.ToString());
|
||||
|
||||
return Cookies.FromToken(JToken.Load(reader));
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
Cookies cookies = value as Cookies;
|
||||
writer.WriteValue(cookies?.GetCookieString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Store.Framework.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 店铺信息。
|
||||
/// </summary>
|
||||
public class Shop : ShopBase
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -9,35 +10,45 @@ namespace Store.Framework.Models
|
||||
/// <summary>
|
||||
/// 商铺信息
|
||||
/// </summary>
|
||||
public class ShopBase
|
||||
public abstract class ShopBase
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("shop_account")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("login_pass")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cookie信息
|
||||
public Cookies Cookie { get; set; }
|
||||
/// </summary>
|
||||
[JsonProperty("cookie")]
|
||||
public Cookies Cookie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分组名称
|
||||
/// </summary>
|
||||
[JsonProperty("group_name")]
|
||||
public string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商铺提示信息
|
||||
/// </summary>
|
||||
[JsonProperty("login_status")]
|
||||
public string Msg { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商铺信息
|
||||
/// </summary>
|
||||
/// <typeparam name="TStatus">状态码</typeparam>
|
||||
public class ShopBase<TStatus> : ShopBase
|
||||
public abstract class ShopBase<TStatus> : ShopBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public TStatus Status { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Framework.Services;
|
||||
|
||||
public record UserNotification(UserInfo user) : INotification;
|
||||
//public record UserNotification(UserInfo user) : INotification;
|
||||
/// <summary>
|
||||
/// 当前用户
|
||||
/// </summary>
|
||||
internal class CurrentUserService : ICurrentUser, ISingletonDependency, INotificationHandler<UserNotification>
|
||||
internal class CurrentUserService : ICurrentUser, ISingletonDependency//, INotificationHandler<UserNotification>
|
||||
{
|
||||
public string UserName => User?.UserName;
|
||||
|
||||
@@ -28,10 +28,14 @@ internal class CurrentUserService : ICurrentUser, ISingletonDependency, INotific
|
||||
{
|
||||
return User.UserAuthority.Contains(code);
|
||||
}
|
||||
|
||||
Task INotificationHandler<UserNotification>.Handle(UserNotification notification, CancellationToken cancellationToken)
|
||||
internal void SetUser(UserInfo user)
|
||||
{
|
||||
User = notification.user;
|
||||
return Task.CompletedTask;
|
||||
User = user;
|
||||
}
|
||||
|
||||
//Task INotificationHandler<UserNotification>.Handle(UserNotification notification, CancellationToken cancellationToken)
|
||||
//{
|
||||
// User = notification.user;
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Store.Client.Apis;
|
||||
using Store.Framework.Apis;
|
||||
using Store.Framework.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Store.Client.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 商铺管理服务
|
||||
/// </summary>
|
||||
public class ShopManager : ITransientDependency
|
||||
{
|
||||
private const int DefaultPageSize = 100;
|
||||
private const int MaxPageCount = 10000;
|
||||
|
||||
private readonly ShopApi _shopApi;
|
||||
|
||||
public ShopManager(ShopApi shopApi)
|
||||
{
|
||||
_shopApi = shopApi ?? throw new ArgumentNullException(nameof(shopApi));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户的所有商铺。
|
||||
/// </summary>
|
||||
/// <param name="pageSize">每页数量。</param>
|
||||
/// <returns>商铺列表。</returns>
|
||||
public List<Shop> GetShops(int pageSize = DefaultPageSize)
|
||||
{
|
||||
return GetShops(false, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商铺列表。
|
||||
/// </summary>
|
||||
/// <param name="isAdmin">是否调用管理端接口。</param>
|
||||
/// <param name="pageSize">每页数量。</param>
|
||||
/// <returns>商铺列表。</returns>
|
||||
public List<Shop> GetShops(bool isAdmin, int pageSize = DefaultPageSize)
|
||||
{
|
||||
List<Shop> shops = new List<Shop>();
|
||||
if (pageSize <= 0)
|
||||
pageSize = DefaultPageSize;
|
||||
|
||||
int page = 0;
|
||||
while (page < MaxPageCount)
|
||||
{
|
||||
page++;
|
||||
|
||||
string postdata = "page=" + page + "&page_size=" + pageSize;
|
||||
HttpResult result = isAdmin
|
||||
? _shopApi.AdminGetShopList(postdata)
|
||||
: _shopApi.GetShopList(postdata);
|
||||
|
||||
ApiResponse<List<Shop>> response = DeserializeShopResponse(result);
|
||||
if (response == null || response.Code != 200 || response.Data == null)
|
||||
break;
|
||||
|
||||
if (response.Data.Count == 0)
|
||||
break;
|
||||
|
||||
shops.AddRange(response.Data);
|
||||
if (response.Data.Count < pageSize)
|
||||
break;
|
||||
}
|
||||
|
||||
return shops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取服务端商铺列表,兼容旧调用。
|
||||
/// </summary>
|
||||
public List<Shop> GetServerShop()
|
||||
{
|
||||
return GetShops();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务端刷新商铺 Cookie。
|
||||
/// </summary>
|
||||
/// <param name="shops">待刷新商铺。</param>
|
||||
/// <returns>服务端原始响应。</returns>
|
||||
public string GetShopCookie(List<Shop> shops)
|
||||
{
|
||||
if (shops == null || shops.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
string shopJson = JsonConvert.SerializeObject(shops);
|
||||
string postdata = "shops=" + Uri.EscapeDataString(shopJson);
|
||||
HttpResult result = _shopApi.GetPddShopCookiesList(postdata);
|
||||
if (result == null || result.StatusCode != HttpStatusCode.OK || string.IsNullOrEmpty(result.Html))
|
||||
return string.Empty;
|
||||
|
||||
MatchCollection matches = Regex.Matches(
|
||||
result.Html,
|
||||
@"<shop\s+(?:(?:id|name)=""(?<key>[^""]*?)""[^>]*)>(?<json>[\w\W]*?)</shop>",
|
||||
RegexOptions.IgnoreCase);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string key = match.Groups["key"].Value;
|
||||
Shop shop = shops.FirstOrDefault(s => s.Id == key || s.Name == key);
|
||||
if (shop == null)
|
||||
continue;
|
||||
|
||||
string cookie = ParseCookie(match.Groups["json"].Value);
|
||||
if (cookie != null)
|
||||
shop.Cookie = new Cookies(DecodeCookie(cookie));
|
||||
}
|
||||
|
||||
return result.Html;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测商铺登录状态。
|
||||
/// </summary>
|
||||
/// <param name="shop">商铺。</param>
|
||||
public void CheckShopStatus(Shop shop)
|
||||
{
|
||||
if (shop == null)
|
||||
throw new ArgumentNullException(nameof(shop));
|
||||
|
||||
if (shop.Cookie == null)
|
||||
{
|
||||
shop.Msg = "Cookie为空";
|
||||
return;
|
||||
}
|
||||
|
||||
HttpItem item = new HttpItem
|
||||
{
|
||||
URL = "https://myseller.taobao.com/ajaxProxy.do?_ksTS=1548152154745_13&callback=seller_layout_head&action=FrameworkLayoutAction&event_submit_do_layout_data=true",
|
||||
Referer = "https://myseller.taobao.com",
|
||||
Method = MethodType.Post,
|
||||
Cookie = shop.Cookie.GetCookieString()
|
||||
};
|
||||
|
||||
HttpResult result = HttpHelper.Request(item);
|
||||
shop.Msg = result != null
|
||||
&& result.StatusCode == HttpStatusCode.OK
|
||||
&& !string.IsNullOrEmpty(result.Html)
|
||||
&& result.Html.Contains("\"shopName\":\"")
|
||||
? "登录成功"
|
||||
: "会话已过期";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除服务端商铺。
|
||||
/// </summary>
|
||||
/// <param name="loginName">店铺登录名。</param>
|
||||
/// <returns>服务端原始响应。</returns>
|
||||
public string DeleteShop(string loginName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(loginName))
|
||||
throw new ArgumentException("店铺登录名不能为空。", nameof(loginName));
|
||||
|
||||
HttpResult result = _shopApi.DelShop("login_name=" + Uri.EscapeDataString(loginName));
|
||||
return result?.Html ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除服务端店铺,兼容旧方法命名。
|
||||
/// </summary>
|
||||
public string DelServerDouShop(string shopId)
|
||||
{
|
||||
return DeleteShop(shopId);
|
||||
}
|
||||
|
||||
private static ApiResponse<List<Shop>> DeserializeShopResponse(HttpResult result)
|
||||
{
|
||||
if (result == null || result.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(result.Html))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ApiResponse<List<Shop>>>(result.Html);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ParseCookie(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
JObject root = JObject.Parse(json);
|
||||
return GetString(root, "Cookies") ?? GetString(root, "cookie") ?? GetString(root, "Cookie");
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetString(JObject source, string propertyName)
|
||||
{
|
||||
JToken token = source?[propertyName];
|
||||
return token == null || token.Type == JTokenType.Null
|
||||
? null
|
||||
: token.ToString();
|
||||
}
|
||||
|
||||
private static string DecodeCookie(string cookie)
|
||||
{
|
||||
return (cookie ?? string.Empty)
|
||||
.Replace("&", "&")
|
||||
.Replace("%", "%")
|
||||
.Replace("+", "+")
|
||||
.Replace(""", "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
//using FkServices = LittleCow.Framework.Services;
|
||||
|
||||
namespace Store.Client.Services
|
||||
{
|
||||
///// <summary>
|
||||
///// 商铺服务
|
||||
///// </summary>
|
||||
//internal class ShopService : ITransientDependency
|
||||
//{
|
||||
// private readonly ShopApi _shopApi;
|
||||
// private readonly IEventBus _eventBus;
|
||||
|
||||
// public ShopService(ShopApi shopApi, IEventBus eventBus)
|
||||
// {
|
||||
// _shopApi = shopApi;
|
||||
// _eventBus = eventBus;
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// 获取所有商铺信息
|
||||
// /// </summary>
|
||||
// /// <typeparam name="T"></typeparam>
|
||||
// /// <returns></returns>
|
||||
// public List<T> GetShops<T>() where T : ShopBase
|
||||
// {
|
||||
// HttpResult result = _shopApi.GetShops();
|
||||
// if (result.StatusCode == System.Net.HttpStatusCode.OK)
|
||||
// {
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.Append("[");
|
||||
// Regex regShopList = new Regex("<shop([^>]+?)>");
|
||||
// if (regShopList.IsMatch(result.Html))
|
||||
// {
|
||||
// int index = 0;
|
||||
// MatchCollection matchs = regShopList.Matches(result.Html);
|
||||
// foreach (Match item in matchs)
|
||||
// {
|
||||
// string value = item.Value;
|
||||
// value = value.Replace("<shop '", "{\"");
|
||||
// value = value.Replace("'>", "\"}");
|
||||
// value = value.Replace("':'", "\":\"");
|
||||
// value = value.Replace("' '", "\",\"");
|
||||
// if (index > 0)
|
||||
// {
|
||||
// sb.Append(",");
|
||||
// }
|
||||
// sb.Append(value);
|
||||
// index++;
|
||||
|
||||
// }
|
||||
// sb.Append("]");
|
||||
// List<T> list = JsonConvert.DeserializeObject<List<T>>(sb.ToString());
|
||||
// list.RemoveAll(a => string.IsNullOrEmpty(a.Name));
|
||||
// return list;
|
||||
// }
|
||||
// }
|
||||
// return new List<T>();
|
||||
// }
|
||||
|
||||
|
||||
// public string GetShopCookie(List<ShopBase> shops)
|
||||
// {
|
||||
// HttpResult result = _shopApi.GetShopsCookie(shops);
|
||||
// if (result.StatusCode == System.Net.HttpStatusCode.OK)
|
||||
// {
|
||||
// MatchCollection matchs = Regex.Matches(result.Html, @"<shop id=""([^""]+?)"">([\w\W]+?)</shop>");
|
||||
// if (matchs != null)
|
||||
// {
|
||||
// foreach (Match item in matchs)
|
||||
// {
|
||||
// string shopId = item.Groups[1].Value;
|
||||
// string json = item.Groups[2].Value;
|
||||
// var rootToken = JToken.Parse(json);
|
||||
// string cookie = rootToken["Cookies"]?.ToString() ?? "";
|
||||
// var shop = shops.FirstOrDefault(a => a.Id == shopId);
|
||||
// if (shop != null)
|
||||
// {
|
||||
// shop.Cookie = new Cookies(cookie);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// 检测商铺登录状态
|
||||
// /// </summary>
|
||||
// public void CheckShopStatus(ShopBase shop)
|
||||
// {
|
||||
// HttpItem item = new HttpItem();
|
||||
// item.URL = "https://myseller.taobao.com/ajaxProxy.do?_ksTS=1548152154745_13&callback=seller_layout_head&action=FrameworkLayoutAction&event_submit_do_layout_data=true";
|
||||
// item.Referer = "https://myseller.taobao.com";
|
||||
// item.Method = MethodType.Post;
|
||||
// item.Cookie = shop.Cookie.GetCookieString();
|
||||
// HttpResult result = HttpHelper.Request(item);
|
||||
// if (result.StatusCode == System.Net.HttpStatusCode.OK)
|
||||
// {
|
||||
// string a = result.Html.Contains("\"shopName\":\"") ? "登录成功" : "会话已过期";
|
||||
// }
|
||||
// //string urlx =
|
||||
// //string refere =
|
||||
// //string htmlx = GetPostTaobaoHtml.GetUrl(urlx, refere, true, "", Shop.Cookies);
|
||||
// //Shop.MsgForCookiesLogin = htmlx.Contains("\"shopName\":\"") ? "登录成功" : "会话已过期";
|
||||
// //GetTaobaoShops.ShopsTemp.Add(Shop);
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// 获取值
|
||||
// /// </summary>
|
||||
// /// <param name="html"></param>
|
||||
// /// <param name="reg"></param>
|
||||
// /// <returns></returns>
|
||||
// private string GetUserValue(string html, string reg)
|
||||
// {
|
||||
// string temp = Regex.Match(html, @"[""]" + reg + @"[""][\s]*:[\s]*[""]([^""]*?)[""]")?.Value;
|
||||
// if (string.IsNullOrEmpty(temp))
|
||||
// temp = Regex.Match(html, @"[']" + reg + @"['][\s]*:[\s]*[']([^']*?)[']")?.Groups[1].Value;
|
||||
// if (string.IsNullOrEmpty(temp))
|
||||
// temp = Regex.Match(html, @"['""]" + reg + @"['""][\s]*:[\s]*([\d]+)")?.Value;
|
||||
|
||||
// return temp;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ using MAX.Core.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Store.Client.Apis;
|
||||
using Store.Framework.Apis;
|
||||
using Store.Framework.Inerterfaces;
|
||||
using Store.Framework.Models;
|
||||
using Store.Framework.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -21,10 +23,12 @@ namespace Store.Client.Services
|
||||
public class UserService : ITransientDependency
|
||||
{
|
||||
private readonly UserApi _userApi;
|
||||
private readonly ICurrentUser _currentUser;
|
||||
|
||||
public UserService(UserApi userApi)
|
||||
public UserService(UserApi userApi, ICurrentUser currentUser)
|
||||
{
|
||||
_userApi = userApi;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,6 +79,8 @@ namespace Store.Client.Services
|
||||
ret.Success = true;
|
||||
ret.Msg = apiResponse.Msg;
|
||||
ret.Data = result;
|
||||
(_currentUser as CurrentUserService).SetUser(user);
|
||||
//_mediator.Publish(new UserNotification(user));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +179,6 @@ public class ProductBase
|
||||
/// </summary>
|
||||
public string SkuSecretJson { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信图片映射JSON。
|
||||
/// </summary>
|
||||
public string WxImgMapJson { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:商品来源类型名称。
|
||||
/// </summary>
|
||||
public string ProductGetTypeText { get; set; }
|
||||
@@ -195,14 +191,6 @@ public class ProductBase
|
||||
/// </summary>
|
||||
public string SpecialProdType { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:是否存在微信回采格式数据。
|
||||
/// </summary>
|
||||
public string HasWxFormat { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信安全状态。
|
||||
/// </summary>
|
||||
public string WxSafeStatus { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:价格文本。
|
||||
/// </summary>
|
||||
public string PriceText { get; set; }
|
||||
@@ -215,22 +203,6 @@ public class ProductBase
|
||||
/// </summary>
|
||||
public string AddUserName { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信一级类目ID。
|
||||
/// </summary>
|
||||
public long? WxCat1 { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信二级类目ID。
|
||||
/// </summary>
|
||||
public long? WxCat2 { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信三级类目ID。
|
||||
/// </summary>
|
||||
public long? WxCat3 { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:微信叶子类目ID。
|
||||
/// </summary>
|
||||
public long? WxCatLeaf { get; set; }
|
||||
/// <summary>
|
||||
/// 新增:是否发送到外部服务。
|
||||
/// </summary>
|
||||
public int? SendToOutServer { get; set; }
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Store.Product.Models.Product;
|
||||
|
||||
/// <summary>
|
||||
/// 微信商品上传使用的商品模型。继承通用商品基类,并承载微信平台专属字段。
|
||||
/// </summary>
|
||||
public class WxProductUploadProduct : ProductBase
|
||||
{
|
||||
/// <summary>微信图片映射 JSON。</summary>
|
||||
public string WxImgMapJson { get; set; }
|
||||
|
||||
/// <summary>是否存在微信回采格式数据。</summary>
|
||||
public string HasWxFormat { get; set; }
|
||||
|
||||
/// <summary>微信安全状态。</summary>
|
||||
public string WxSafeStatus { get; set; }
|
||||
|
||||
/// <summary>微信一级类目 ID。</summary>
|
||||
public long? WxCat1 { get; set; }
|
||||
|
||||
/// <summary>微信二级类目 ID。</summary>
|
||||
public long? WxCat2 { get; set; }
|
||||
|
||||
/// <summary>微信三级类目 ID。</summary>
|
||||
public long? WxCat3 { get; set; }
|
||||
|
||||
/// <summary>微信叶子类目 ID。</summary>
|
||||
public long? WxCatLeaf { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using AllBase.Model.Result.PDDResult;
|
||||
using AllBase.Model.Result.WXResult;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Store.Framework.Extensions;
|
||||
using Store.Product.Models.Product;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
@@ -34,11 +35,27 @@ public class WxProductUploadParam
|
||||
public class WxProductUploadSource
|
||||
{
|
||||
public ShopLoginInfo Shop { get; set; }
|
||||
public KJDDBProduct DbProduct { get; set; }
|
||||
public TemuDBProductSelect SelectProduct { get; set; }
|
||||
/// <summary>已完成商品与选品定制组合后的商品数据。</summary>
|
||||
public WxProductUploadProduct Product { get; set; } = new();
|
||||
/// <summary>上传任务状态,仅用于结果回写,不作为商品数据源。</summary>
|
||||
public WxProductPublishTask Task { get; set; } = new();
|
||||
public DateTime StartTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传任务结果。只保存任务状态和最终草稿参数,不保存原始商品数据。
|
||||
/// </summary>
|
||||
public class WxProductPublishTask
|
||||
{
|
||||
public long id { get; set; }
|
||||
public string out_product_Id { get; set; }
|
||||
public string shop_id { get; set; }
|
||||
public string draft_id { get; set; }
|
||||
public string status { get; set; }
|
||||
public DateTime add_time { get; set; }
|
||||
public WXUploadProductParam wxDraftProduct { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 原 UploadProduct 方法中跨 #region 使用的可变状态。
|
||||
/// TODO(迁移): 个别字段当前为 dynamic,需按 Store.Product 最终 DTO 改为明确类型。
|
||||
@@ -100,7 +117,7 @@ public class WxProductPublishState
|
||||
public List<dynamic> AddPriceRules { get; set; } = new();
|
||||
public dynamic WxUploadRule { get; set; }
|
||||
public string PddProductCates { get; set; }
|
||||
public dynamic UploadDraftProductList { get; set; }
|
||||
public List<WxProductPublishTask> UploadDraftProductList { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -260,4 +277,4 @@ public class WxControlState
|
||||
Text = numericUpDown?.Text ?? ""
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class PublishContextBase
|
||||
/// <summary>
|
||||
/// 商铺
|
||||
/// </summary>
|
||||
public ShopBase Shop { get; set; }
|
||||
public Shop Shop { get; set; }
|
||||
/// <summary>
|
||||
/// 商铺名称
|
||||
/// </summary>
|
||||
|
||||
@@ -28,8 +28,11 @@ public class WXPublishParameters: PublishParameters
|
||||
public bool IsCompleted { get; set; }
|
||||
/// <summary>候补:当前上传店铺。</summary>
|
||||
public ShopLoginInfo Shop { get; set; }
|
||||
/// <summary>候补:当前选品商品。</summary>
|
||||
public TemuDBProductSelect SelectProduct { get; set; }
|
||||
/// <summary>
|
||||
/// 上传任务对象。仅在上传完成后回写状态、草稿 ID 等任务信息,
|
||||
/// 不参与商品标题、裂变、SKU、图片等商品内容构造。
|
||||
/// </summary>
|
||||
public WxProductPublishTask Task { get; set; } = new WxProductPublishTask();
|
||||
/// <summary>候补:本次上传开始时间。</summary>
|
||||
public DateTime StartTime { get; set; } = DateTime.Now;
|
||||
/// <summary>候补:实际提交的佣金比例。</summary>
|
||||
@@ -121,7 +124,7 @@ public class WXPublishParameters: PublishParameters
|
||||
/// <summary>候补:资质图片列表。</summary>
|
||||
public List<wx_qualification_image_info> QualificationImages { get; set; } = new List<wx_qualification_image_info>();
|
||||
/// <summary>候补:待上传草稿商品列表。</summary>
|
||||
public dynamic UploadDraftProductList { get; set; }
|
||||
public List<WxProductPublishTask> UploadDraftProductList { get; set; } = new List<WxProductPublishTask>();
|
||||
/// <summary>
|
||||
/// 预售天数(页面限制为 3-45 天)。
|
||||
/// </summary>
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class AddRandomImageBorderStep : WxProductPublishStepBase
|
||||
|
||||
protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.Parameters.SelectProduct.is_fission_title && context.Parameters.IsRandomImageBorder)
|
||||
if (context.Parameters.IsFissionTitle && context.Parameters.IsRandomImageBorder)
|
||||
{
|
||||
// selectProd.isFission
|
||||
int mainCount = 9;
|
||||
|
||||
@@ -99,7 +99,7 @@ public sealed class BuildSkuInfoStep : WxProductPublishStepBase
|
||||
// 聚水潭上微信小店,加运费(其他平台shipPrice默认为0)
|
||||
skuParam.productSkuInfo.salePrice += (context.Parameters.ShipPrice * context.Parameters.DefaultAddPriceRule.ShippingPriceTimes).ParseToInt();
|
||||
// 商品裂变随机增加价格
|
||||
if (context.Parameters.SelectProduct.is_fission_title && context.Parameters.IsFissionRandomPriceChange)
|
||||
if (context.Parameters.IsFissionTitle && context.Parameters.IsFissionRandomPriceChange)
|
||||
{
|
||||
Invoke(new Action(() =>
|
||||
{
|
||||
|
||||
@@ -43,11 +43,11 @@ public sealed class FissionTitleStep : WxProductPublishStepBase
|
||||
{
|
||||
throw new Exception("裂变标题数量不足,将暂停上传");
|
||||
}
|
||||
context.Parameters.SelectProduct.product_name = titles[0];
|
||||
context.Parameters.SelectProduct.is_fission_title = true;
|
||||
context.Product.Title = titles[0];
|
||||
context.Parameters.IsFissionTitle = true;
|
||||
titles.RemoveAt(0);
|
||||
context.Parameters.FissionTitles = string.Join("\r\n", titles);
|
||||
context.Progress.Report("标题裂变成功,更改为:" + context.Parameters.SelectProduct.product_name);
|
||||
context.Progress.Report("标题裂变成功,更改为:" + context.Product.Title);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ public sealed class InitializeProductDataStep : WxProductPublishStepBase
|
||||
{
|
||||
if (context.Parameters.IsSkipHasFilterWordsProd)
|
||||
{
|
||||
if (lstTitleReplaceText.Any(t => context.Parameters.SelectProduct.product_name.Contains(t)))
|
||||
throw new Exception("命中敏感词");
|
||||
if (lstTitleReplaceText.Any(t => context.Product.Title.Contains(t)))
|
||||
throw new Exception("命中敏感词");
|
||||
var goods_name = context.Parameters.ProdJObject["detail"]["goods_name"].ParseToString();
|
||||
@@ -70,7 +68,6 @@ public sealed class InitializeProductDataStep : WxProductPublishStepBase
|
||||
foreach (var txt in lstTitleReplaceText)
|
||||
{
|
||||
context.Product.Title = context.Product.Title.Replace(txt, "");
|
||||
context.Parameters.SelectProduct.product_name = context.Parameters.SelectProduct.product_name.Replace(txt, "");
|
||||
}
|
||||
|
||||
var goods_name = context.Parameters.ProdJObject["detail"]["goods_name"].ParseToString();
|
||||
@@ -111,9 +108,6 @@ public sealed class InitializeProductDataStep : WxProductPublishStepBase
|
||||
if (context.Product.Title.Contains(ch) && context.Product.Title.IndexOf(ch) < context.Product.Title.Length / 2 && context.Product.Title.Length > 10)
|
||||
context.Product.Title = context.Product.Title.Substring(context.Product.Title.IndexOf(ch)).Trim(ch);
|
||||
|
||||
if (context.Parameters.SelectProduct.product_name.Contains(ch) && context.Parameters.SelectProduct.product_name.IndexOf(ch) < context.Parameters.SelectProduct.product_name.Length / 2 && context.Parameters.SelectProduct.product_name.Length > 10)
|
||||
context.Parameters.SelectProduct.product_name = context.Parameters.SelectProduct.product_name.Substring(context.Parameters.SelectProduct.product_name.IndexOf(ch)).Trim(ch);
|
||||
|
||||
var goods_name = context.Parameters.ProdJObject["detail"]["goods_name"].ParseToString();
|
||||
if (goods_name.Contains(ch) && goods_name.IndexOf(ch) < goods_name.Length / 2 && goods_name.Length > 10)
|
||||
context.Parameters.ProdJObject["detail"]["goods_name"] = goods_name.Substring(goods_name.IndexOf(ch)).Trim(ch);
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
6. `WxShopTokenInfoList` 当前在入口处做了列表快照;若步骤内需要直接回写窗体静态列表,需要改为共享集合或增加回调。
|
||||
7. `SubmitProductStep` 中的 `DataGridViewRow` 统计逻辑仍直接依赖表格控件,后续建议迁到运行时回调。
|
||||
|
||||
## 新上传参数边界
|
||||
|
||||
- 商品内容在进入步骤流水线前完成组合,步骤内统一使用 `WXPublishContext.Product`,其类型为继承 `ProductBase` 的 `WxProductUploadProduct`。
|
||||
- 最终提交参数只在 `WXPublishParameters.ProductParameter` 上继续修改。
|
||||
- 裂变状态使用 `WXPublishParameters.IsFissionTitle`,不再从 `TemuDBProductSelect` 读取。
|
||||
- 上传任务结果使用 `WxProductPublishTask`,只保存状态、草稿 ID 和草稿参数,不参与商品内容判断。
|
||||
|
||||
## 步骤顺序
|
||||
|
||||
步骤顺序由 `WxProductPublishPipelineFactory.CreateDefault()` 统一维护,和原 `UploadProduct` 从上到下的执行顺序一致。
|
||||
步骤顺序由 `WxProductPublishPipelineFactory.CreateDefault()` 统一维护,和原 `UploadProduct` 从上到下的执行顺序一致。
|
||||
@@ -34,9 +34,8 @@ public sealed class SetProductTitleStep : WxProductPublishStepBase
|
||||
|
||||
protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
context.Product.Title = context.Parameters.SelectProduct.product_name;
|
||||
if (context.Product.Title.IsNullOrEmpty())
|
||||
context.Product.Title = context.Product.Title.IsNullOrEmpty() ? context.Parameters.ProdInfo.detail.goods_name : context.Product.Title;
|
||||
context.Product.Title = context.Parameters.ProdInfo.detail.goods_name;
|
||||
|
||||
context.Parameters.InitParameter.product.info.title = context.Product.Title;
|
||||
context.Parameters.ProductParameter.product.info.title = context.Product.Title;
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class SkipUploadedProductStep : WxProductPublishStepBase
|
||||
protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// 裂变按标题名称
|
||||
if (context.Parameters.SelectProduct.is_fission_title)
|
||||
if (context.Parameters.IsFissionTitle)
|
||||
{
|
||||
HttpResult result = WXApi.searchProductWithTitle(context.Shop.Cookie, GetHtml.UrlEncodeUtf8(context.Product.Title));
|
||||
string searchRst = result.Html;
|
||||
|
||||
@@ -50,11 +50,12 @@ public sealed class SubmitProductStep : WxProductPublishStepBase
|
||||
|
||||
if (context.Parameters.UpDraft)
|
||||
{
|
||||
context.Parameters.SelectProduct.wxDraftProduct = context.Parameters.ProductParameter;
|
||||
context.Parameters.SelectProduct.draft_id = context.Parameters.ProductParameter.product.productKey.productId;
|
||||
context.Parameters.SelectProduct.status = "草稿箱";
|
||||
context.Parameters.SelectProduct.add_time = DateTime.Now;
|
||||
context.Parameters.UploadDraftProductList.Add(context.Parameters.SelectProduct);
|
||||
// Task 仅用于回写上传任务状态,最终商品数据只来自 ProductParameter。
|
||||
context.Parameters.Task.wxDraftProduct = context.Parameters.ProductParameter;
|
||||
context.Parameters.Task.draft_id = context.Parameters.ProductParameter.product.productKey.productId;
|
||||
context.Parameters.Task.status = "草稿箱";
|
||||
context.Parameters.Task.add_time = DateTime.Now;
|
||||
context.Parameters.UploadDraftProductList.Add(context.Parameters.Task);
|
||||
NaiBa.Util.Helper.ConfigHelper.SaveConfig("wx_select_upload_draft_products", context.Parameters.UploadDraftProductList);
|
||||
|
||||
StopWithResult("{\"success\":true,\"draftId\":" + context.Parameters.ProductParameter.product.productKey.productId + ",\"status\":\"草稿箱\",\"errMsg\":\"指定上传到草稿箱" + context.Parameters.CommissionRstStr + "\"}");
|
||||
|
||||
@@ -3,10 +3,31 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Store.Product.Models.Product;
|
||||
|
||||
namespace Store.Product.Publish.Steps.Wx;
|
||||
|
||||
public class WXPublishContext : PublishContextBase
|
||||
{
|
||||
private WxProductUploadProduct _product = new WxProductUploadProduct();
|
||||
|
||||
/// <summary>
|
||||
/// 微信商品上传模型
|
||||
/// </summary>
|
||||
public new WxProductUploadProduct Product
|
||||
{
|
||||
get => _product;
|
||||
set
|
||||
{
|
||||
_product = value ?? new WxProductUploadProduct();
|
||||
base.Product = _product;
|
||||
}
|
||||
}
|
||||
|
||||
public WXPublishContext()
|
||||
{
|
||||
base.Product = _product;
|
||||
}
|
||||
|
||||
public IProgress<string> Progress { get; set; }
|
||||
}
|
||||
@@ -1326,7 +1326,7 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
{
|
||||
this.pbLoading.Visible = false;
|
||||
}));
|
||||
|
||||
GlobalConfig.ShopLoginList
|
||||
Task.Run(() =>
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -17,6 +18,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Store.Product\Store.Product.csproj" />
|
||||
<ProjectReference Include="..\..\Store.Management.Framework\Store.Management.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -24,4 +26,4 @@
|
||||
<Using Include="NUnit.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
using Autofac;
|
||||
using Autofac;
|
||||
using LittleCow.Core.Infrastructure;
|
||||
using MAX.Core.Autofac;
|
||||
using MAX.Core.Cryptography;
|
||||
@@ -7,6 +7,10 @@ using Store.Client;
|
||||
using Store.Client.Apis;
|
||||
using Store.Client.HttpExtension;
|
||||
using Store.Client.Services;
|
||||
using Store.Framework.Models;
|
||||
using Store.Product.Models.Product;
|
||||
using Store.Product.Publish;
|
||||
using Store.Product.Publish.Steps.Wx;
|
||||
|
||||
namespace Store.Test;
|
||||
|
||||
@@ -15,7 +19,8 @@ public class Tests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Bootstrap.Builder();
|
||||
Bootstrap.Builder();
|
||||
TestUserLogin();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -28,5 +33,156 @@ public class Tests
|
||||
});
|
||||
//new UserApi().Login("18990991234", MD5Helper.Md5_Lower32("18990991234"));
|
||||
}
|
||||
[Test]
|
||||
public void TestGetShops()
|
||||
{
|
||||
var ss = IocManager.Resolve<ShopManager>().GetShops();
|
||||
}
|
||||
[Test]
|
||||
public void TestNewWxUploadProductUsesProductBase()
|
||||
{
|
||||
var context = new WXPublishContext();
|
||||
var product = new WxProductUploadProduct
|
||||
{
|
||||
Title = "新版微信商品",
|
||||
SpuId = "wx-spu-1001",
|
||||
WxCatLeaf = 547591
|
||||
};
|
||||
|
||||
}
|
||||
context.Product = product;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(context.Product, Is.TypeOf<WxProductUploadProduct>());
|
||||
Assert.That(context.Product, Is.InstanceOf<ProductBase>());
|
||||
Assert.That(((PublishContextBase)context).Product, Is.SameAs(product));
|
||||
Assert.That(context.Product.Title, Is.EqualTo("新版微信商品"));
|
||||
Assert.That(context.Product.WxCatLeaf, Is.EqualTo(547591));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestNewWxUploadFissionTitleUpdatesProductModel()
|
||||
{
|
||||
var context = new WXPublishContext
|
||||
{
|
||||
Product = new WxProductUploadProduct { Title = "原商品标题" },
|
||||
Progress = new Progress<string>(_ => { })
|
||||
};
|
||||
context.Parameters.IsFissionTitle = true;
|
||||
context.Parameters.FissionTitles = "裂变标题一\r\n裂变标题二";
|
||||
|
||||
var step = new FissionTitleStep();
|
||||
await step.ExecuteAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(context.Product.Title, Is.EqualTo("裂变标题一"));
|
||||
Assert.That(context.Parameters.IsFissionTitle, Is.True);
|
||||
Assert.That(context.Parameters.FissionTitles, Is.EqualTo("裂变标题二"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestNewWxUploadSetTitleWritesFinalParameters()
|
||||
{
|
||||
var context = new WXPublishContext
|
||||
{
|
||||
Product = new WxProductUploadProduct { Title = "最终上传标题" },
|
||||
Progress = new Progress<string>(_ => { })
|
||||
};
|
||||
|
||||
var step = new SetProductTitleStep();
|
||||
await step.ExecuteAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(context.Parameters.InitParameter.product.info.title, Is.EqualTo("最终上传标题"));
|
||||
Assert.That(context.Parameters.ProductParameter.product.info.title, Is.EqualTo("最终上传标题"));
|
||||
Assert.That(
|
||||
context.Parameters.ProductParameter.sessionInfo.sessionId,
|
||||
Is.EqualTo(context.Parameters.InitParameter.product.sessionId));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestNewWxUploadPipelineRunsCompositionToSubmit()
|
||||
{
|
||||
var context = new WXPublishContext
|
||||
{
|
||||
Shop = new Shop
|
||||
{
|
||||
Name = "乐乐人小店",
|
||||
Cookie = new Cookies("biz_token=b_000001a0_80071145_44612ea2_a87897a5_12c2c339; biz_ticket=; biz_magic=09bcc3e825c8826c562d62ca3442e35e8eb4ce13b73e4097661f11c9bd90f46e; biz_rand=CAESIO762C8Iu/hxVBpyNsXZiqDGlOsa5U3WBKBd3PvU0p6d; device_id=7b7080668b28f2aadba6d7f208ca35833c4ce75fe4c107eca6a5e5785fccc8ad")
|
||||
},
|
||||
Product = new WxProductUploadProduct { Title = "原商品标题" },
|
||||
Progress = new Progress<string>(_ => { })
|
||||
};
|
||||
context.Parameters.IsFissionTitle = true;
|
||||
context.Parameters.FissionTitles = "裂变商品标题";
|
||||
|
||||
var pipeline = new WXPublishPipeline(new IPublishStep<WXPublishContext>[]
|
||||
{
|
||||
new FissionTitleStep(),
|
||||
new SkipUploadedProductStep(),
|
||||
new SetProductTitleStep(),
|
||||
new TestSubmitProductStep()
|
||||
});
|
||||
|
||||
await pipeline.ExecuteAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(context.Product.Title, Is.EqualTo("裂变商品标题"));
|
||||
Assert.That(context.Parameters.InitParameter.product.info.title, Is.EqualTo("裂变商品标题"));
|
||||
Assert.That(context.Parameters.ProductParameter.product.info.title, Is.EqualTo("裂变商品标题"));
|
||||
Assert.That(context.Parameters.Result, Is.EqualTo("{\"success\":true}"));
|
||||
Assert.That(context.Parameters.IsCompleted, Is.True);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestSubmitProductStep : IPublishStep<WXPublishContext>
|
||||
{
|
||||
public string Description => "测试提交商品";
|
||||
|
||||
public Task<bool> CanExecuteAsync(WXPublishContext context, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(WXPublishContext context, CancellationToken token)
|
||||
{
|
||||
var title = context.Parameters.ProductParameter.product.info.title;
|
||||
if (title != context.Product.Title)
|
||||
{
|
||||
throw new InvalidOperationException("提交参数中的标题不是最终组合标题。");
|
||||
}
|
||||
|
||||
context.Parameters.Result = "{\"success\":true}";
|
||||
context.Parameters.IsCompleted = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AlwaysExecuteStep : IPublishStep<WXPublishContext>
|
||||
{
|
||||
private readonly IPublishStep<WXPublishContext> _inner;
|
||||
|
||||
public AlwaysExecuteStep(IPublishStep<WXPublishContext> inner)
|
||||
{
|
||||
_inner = inner;
|
||||
}
|
||||
|
||||
public string Description => _inner.Description;
|
||||
|
||||
public Task<bool> CanExecuteAsync(WXPublishContext context, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(WXPublishContext context, CancellationToken token)
|
||||
{
|
||||
return _inner.ExecuteAsync(context, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user