diff --git a/Store.Framework/Agent/AgentConfig.cs b/Store.Framework/Agent/AgentConfig.cs
index 8bf107a..25aa10e 100644
--- a/Store.Framework/Agent/AgentConfig.cs
+++ b/Store.Framework/Agent/AgentConfig.cs
@@ -15,5 +15,10 @@ namespace Store.Client.Agent
/// 代理码
///
public static string AgentCode { get; set; }
+
+ ///
+ /// 代理名称
+ ///
+ public static string AgentName { get; set; }
}
}
diff --git a/Store.Framework/Apis/CommonApi.cs b/Store.Framework/Apis/CommonApi.cs
index ffb1e71..09f4788 100644
--- a/Store.Framework/Apis/CommonApi.cs
+++ b/Store.Framework/Apis/CommonApi.cs
@@ -21,7 +21,7 @@ namespace Store.Client.Apis
///
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
///
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;
diff --git a/Store.Framework/Apis/UserApi.cs b/Store.Framework/Apis/UserApi.cs
index 5e3d559..8f6c8d1 100644
--- a/Store.Framework/Apis/UserApi.cs
+++ b/Store.Framework/Apis/UserApi.cs
@@ -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 param = new Dictionary
@@ -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, "([^<]+)")?.Groups[1]?.Value ?? "";
if (!string.IsNullOrEmpty(chCode))
{
- string codex = SignTool.Encryption(r2);
+ string codex = Encryption(r2);
if (chCode != codex)
{
result.StatusCode = HttpStatusCode.Unauthorized;
diff --git a/Store.Framework/ClientInfo.cs b/Store.Framework/ClientInfo.cs
deleted file mode 100644
index cd06bed..0000000
--- a/Store.Framework/ClientInfo.cs
+++ /dev/null
@@ -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
-{
- ///
- /// 客户端信息
- ///
- public class ClientInfo : SingleInstance
- {
-
- public static UserInfo User
- {
- get
- {
- return Instance.UserInfo;
- }
- }
-
-
-
- private ClientInfo()
- {
-
- }
- ///
- /// 客户端IP
- ///
- public string Ip { get; }
- ///
- /// 客户端地址
- ///
- public string Address { get; set; }
-
- ///
- /// 获取当前登录用户
- ///
- public UserInfo UserInfo { get; private set; }
-
- }
-}
diff --git a/Store.Framework/Commons/SignTool.cs b/Store.Framework/Commons/SignTool.cs
deleted file mode 100644
index 1e176fa..0000000
--- a/Store.Framework/Commons/SignTool.cs
+++ /dev/null
@@ -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
-{
- ///
- /// 签名工具
- ///
- public class SignTool
- {
- ///
- /// 获取签名
- ///
- ///
- public static string GetSign()
- {
- long time = GetReviseUnix();
- string s = Encryption(time);
- return "time=" + time + "&code=" + s + "&NbCodeVersion=1";
- }
- ///
- /// 用户签名
- ///
- ///
- public static string GetUserSign()
- {
- return GetUserSign("", "");
- }
- ///
- /// 获取当前用户签名
- ///
- ///
- ///
- ///
- 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);
- }
-
- ///
- /// 获取校正后的时间戳
- ///
- ///
- public static long GetReviseUnix()
- {
- return DateHelper.GetCurrentUnixTimestampInSeconds() + ServerConfig.TimeDifferent;
- }
- ///
- /// 签名算法
- ///
- /// 随机数
- /// 加密签名结果
- public static string Encryption(long s)
- {
- string value = $"{s}sdhewu*/fhAAue48485ew5+5%@3485esGFFHFD";
- return MD5Helper.Md5_32(value);
- }
- }
-}
diff --git a/Store.Framework/Config.cs b/Store.Framework/Config.cs
index 81644fe..5aa506a 100644
--- a/Store.Framework/Config.cs
+++ b/Store.Framework/Config.cs
@@ -13,6 +13,11 @@ public static class Config
{
public const string AppVersion = "1312";
+ ///
+ /// API 项目路径。
+ ///
+ public const string MainPath = "29wx";
+
public static bool IsRelease
{
get
diff --git a/Store.Framework/Extensions/HttpItemExtensions.cs b/Store.Framework/Extensions/HttpItemExtensions.cs
deleted file mode 100644
index 33c3718..0000000
--- a/Store.Framework/Extensions/HttpItemExtensions.cs
+++ /dev/null
@@ -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
-{
- ///
- /// 请求扩展
- ///
- public static class HttpItemExtensions
- {
- ///
- /// 增加签名
- ///
- ///
- public static void Sign(this ServerHttpItem item)
- {
- SetSign(item, SignTool.GetSign());
- }
- ///
- /// 增加用户签名
- ///
- ///
- public static void SignUser(this ServerHttpItem item)
- {
- item.SignUser("", "");
- }
- ///
- /// 增加用户签名
- ///
- ///
- ///
- ///
- 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;
- }
- }
-}
diff --git a/Store.Framework/HttpExtension/AppApiRequestBase.cs b/Store.Framework/HttpExtension/AppApiRequestBase.cs
index 5148529..1b165e2 100644
--- a/Store.Framework/HttpExtension/AppApiRequestBase.cs
+++ b/Store.Framework/HttpExtension/AppApiRequestBase.cs
@@ -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();
protected IApiServerConfig _config => LazyServiceProvider.Instance.LazyGetService();
+ protected ICurrentUser CurrentUser => LazyServiceProvider.Instance.LazyGetService();
///
/// 默认服务请求。
@@ -69,39 +76,36 @@ namespace Store.Client.HttpExtension
{
return Request(BuildServiceUrl(urlpath), postdata);
}
+
///
/// 使用当前 Key 构建服务请求地址。
///
protected string BuildServiceUrl(string urlPath)
{
- return $"{_config.Host}/wxxd_{urlPath}?obj=29wx/wxxd_{urlPath}";
+ return $"{_config.Host}/wxxd_{urlPath}?obj={Config.MainPath}/wxxd_{urlPath}";
}
-
///
/// 创建并执行服务端请求,默认使用当前用户签名。
///
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);
}
///
/// 服务器请求。
///
- 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);
}
-
///
/// 执行底层 HTTP 请求。
///
- protected HttpResult Send(ServerHttpItem item)
+ protected HttpResult Send(HttpItem item)
{
if (item == null)
{
@@ -155,5 +148,98 @@ namespace Store.Client.HttpExtension
return result;
}
+
+ ///
+ /// 为请求添加公共签名或用户签名。
+ ///
+ private void ApplySign(HttpItem item, bool isUserSign)
+ {
+ string sign = isUserSign
+ ? GetUserSign("", "")
+ : GetSign();
+
+ SetSign(item, sign);
+ }
+
+ ///
+ /// 获取公共签名。
+ ///
+ private static string GetSign()
+ {
+ long time = GetReviseUnix();
+ string code = Encryption(time);
+ return "time=" + time + "&code=" + code + "&NbCodeVersion=1";
+ }
+
+ ///
+ /// 获取当前用户签名。
+ ///
+ 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
+ {
+ { "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}"));
+ }
+
+ ///
+ /// 获取校正后的时间戳。
+ ///
+ protected static long GetReviseUnix()
+ {
+ return DateHelper.GetCurrentUnixTimestampInSeconds() + ServerConfig.TimeDifferent;
+ }
+
+ ///
+ /// 签名算法。
+ ///
+ /// 随机数
+ /// 加密签名结果
+ protected static string Encryption(long s)
+ {
+ string value = $"{s}sdhewu*/fhAAue48485ew5+5%@3485esGFFHFD";
+ return MD5Helper.Md5_32(value);
+ }
+
+ ///
+ /// 将签名参数拼接到请求。
+ ///
+ 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;
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Store.Framework/HttpExtension/ServerHttpItem.cs b/Store.Framework/HttpExtension/ServerHttpItem.cs
index 6f4f06e..1323b9a 100644
--- a/Store.Framework/HttpExtension/ServerHttpItem.cs
+++ b/Store.Framework/HttpExtension/ServerHttpItem.cs
@@ -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;
- }
- ///
- /// 是否已签名
- ///
- public bool IsSign { get; set; }
- }
-}
+// }
+// public HttpItem(ShopBase shop)
+// {
+// Shop = shop;
+// }
+// ///
+// /// 是否已签名
+// ///
+// public bool IsSign { get; set; }
+// }
+//}
diff --git a/Store.Framework/HttpExtension/ServiceRequestBase.cs b/Store.Framework/HttpExtension/ServiceRequestBase.cs
index 08f38ac..23d5803 100644
--- a/Store.Framework/HttpExtension/ServiceRequestBase.cs
+++ b/Store.Framework/HttpExtension/ServiceRequestBase.cs
@@ -76,7 +76,7 @@
// ///
// /// 执行底层 HTTP 请求。
// ///
-// protected HttpResult Send(ServerHttpItem item)
+// protected HttpResult Send(HttpItem item)
// {
// if (item == null)
// {
diff --git a/Store.Framework/Models/Cookies.cs b/Store.Framework/Models/Cookies.cs
index e3e3738..2584fcc 100644
--- a/Store.Framework/Models/Cookies.cs
+++ b/Store.Framework/Models/Cookies.cs
@@ -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
{
+ ///
+ /// Cookie集合,支持普通 Cookie 字符串和 API 返回的 Cookie JSON。
+ ///
+ [JsonConverter(typeof(CookiesJsonConverter))]
public class Cookies : Dictionary
{
private readonly string oldCookie;
+
public Cookies(string cookie)
{
oldCookie = cookie;
- SplitCookie(oldCookie);
+ Load(cookie);
}
-
+
///
/// Cookie字符串
///
@@ -26,28 +31,6 @@ namespace Store.Framework.Models
return GetCookieString();
}
}
-
- ///
- /// 拆分Cookie
- ///
- private void SplitCookie(string cookie)
- {
- this.Clear();
- Regex regCookie = new Regex("(?.+?)=(?.+?)(;|$)");
- 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());
- }
- }
- }
- }
///
/// 获取指定值
@@ -63,6 +46,7 @@ namespace Store.Framework.Models
}
return defvalue;
}
+
///
/// 添加或修改
///
@@ -79,6 +63,7 @@ namespace Store.Framework.Models
this[key] = value;
}
}
+
///
/// 获取Cookies字符串
///
@@ -89,14 +74,156 @@ namespace Store.Framework.Models
return string.Join(";", list);
}
+ ///
+ /// 转为浏览器 Cookie 字符串。
+ ///
+ public override string ToString()
+ {
+ var list = this.Select(a => { return $"{a.Key}={a.Value}"; });
+ return string.Join("; ", list);
+ }
///
/// 隐式转换
///
///
- 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());
+ }
+
+ ///
+ /// 拆分普通 Cookie 字符串。
+ ///
+ private void SplitCookie(string cookie)
+ {
+ Clear();
+ if (string.IsNullOrEmpty(cookie))
+ return;
+
+ Regex regCookie = new Regex("(?.+?)=(?.+?)(;|$)");
+ 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(""", "\"");
+ }
}
-}
+
+ ///
+ /// Cookie JSON 转换器。
+ ///
+ 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Store.Framework/Models/Shop.cs b/Store.Framework/Models/Shop.cs
new file mode 100644
index 0000000..8d7af43
--- /dev/null
+++ b/Store.Framework/Models/Shop.cs
@@ -0,0 +1,9 @@
+namespace Store.Framework.Models
+{
+ ///
+ /// 店铺信息。
+ ///
+ public class Shop : ShopBase
+ {
+ }
+}
\ No newline at end of file
diff --git a/Store.Framework/Models/ShopBase.cs b/Store.Framework/Models/ShopBase.cs
index 9223e76..9d59c1d 100644
--- a/Store.Framework/Models/ShopBase.cs
+++ b/Store.Framework/Models/ShopBase.cs
@@ -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
///
/// 商铺信息
///
- 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; }
+
///
/// Cookie信息
- public Cookies Cookie { get; set; }
///
+ [JsonProperty("cookie")]
+ public Cookies Cookie { get; set; }
+
///
/// 分组名称
///
+ [JsonProperty("group_name")]
public string GroupName { get; set; }
///
/// 商铺提示信息
///
+ [JsonProperty("login_status")]
public string Msg { get; set; }
}
+
///
/// 商铺信息
///
/// 状态码
- public class ShopBase : ShopBase
+ public abstract class ShopBase : ShopBase
{
///
/// 状态
///
public TStatus Status { get; set; }
-
}
-}
+}
\ No newline at end of file
diff --git a/Store.Framework/Services/CurrentUserService.cs b/Store.Framework/Services/CurrentUserService.cs
index e242caa..472ca3f 100644
--- a/Store.Framework/Services/CurrentUserService.cs
+++ b/Store.Framework/Services/CurrentUserService.cs
@@ -10,11 +10,11 @@ using System.Threading.Tasks;
namespace Store.Framework.Services;
-public record UserNotification(UserInfo user) : INotification;
+//public record UserNotification(UserInfo user) : INotification;
///
/// 当前用户
///
-internal class CurrentUserService : ICurrentUser, ISingletonDependency, INotificationHandler
+internal class CurrentUserService : ICurrentUser, ISingletonDependency//, INotificationHandler
{
public string UserName => User?.UserName;
@@ -28,10 +28,14 @@ internal class CurrentUserService : ICurrentUser, ISingletonDependency, INotific
{
return User.UserAuthority.Contains(code);
}
-
- Task INotificationHandler.Handle(UserNotification notification, CancellationToken cancellationToken)
+ internal void SetUser(UserInfo user)
{
- User = notification.user;
- return Task.CompletedTask;
+ User = user;
}
+
+ //Task INotificationHandler.Handle(UserNotification notification, CancellationToken cancellationToken)
+ //{
+ // User = notification.user;
+ // return Task.CompletedTask;
+ //}
}
diff --git a/Store.Framework/Services/ShopManager.cs b/Store.Framework/Services/ShopManager.cs
new file mode 100644
index 0000000..c72142d
--- /dev/null
+++ b/Store.Framework/Services/ShopManager.cs
@@ -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
+{
+ ///
+ /// 商铺管理服务
+ ///
+ 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));
+ }
+
+ ///
+ /// 获取当前用户的所有商铺。
+ ///
+ /// 每页数量。
+ /// 商铺列表。
+ public List GetShops(int pageSize = DefaultPageSize)
+ {
+ return GetShops(false, pageSize);
+ }
+
+ ///
+ /// 获取商铺列表。
+ ///
+ /// 是否调用管理端接口。
+ /// 每页数量。
+ /// 商铺列表。
+ public List GetShops(bool isAdmin, int pageSize = DefaultPageSize)
+ {
+ List shops = new List();
+ 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> 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;
+ }
+
+ ///
+ /// 获取服务端商铺列表,兼容旧调用。
+ ///
+ public List GetServerShop()
+ {
+ return GetShops();
+ }
+
+ ///
+ /// 从服务端刷新商铺 Cookie。
+ ///
+ /// 待刷新商铺。
+ /// 服务端原始响应。
+ public string GetShopCookie(List 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,
+ @"[^""]*?)""[^>]*)>(?[\w\W]*?)",
+ 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;
+ }
+
+ ///
+ /// 检测商铺登录状态。
+ ///
+ /// 商铺。
+ 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\":\"")
+ ? "登录成功"
+ : "会话已过期";
+ }
+
+ ///
+ /// 删除服务端商铺。
+ ///
+ /// 店铺登录名。
+ /// 服务端原始响应。
+ 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;
+ }
+
+ ///
+ /// 删除服务端店铺,兼容旧方法命名。
+ ///
+ public string DelServerDouShop(string shopId)
+ {
+ return DeleteShop(shopId);
+ }
+
+ private static ApiResponse> DeserializeShopResponse(HttpResult result)
+ {
+ if (result == null || result.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(result.Html))
+ return null;
+
+ try
+ {
+ return JsonConvert.DeserializeObject>>(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(""", "\"");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Store.Framework/Services/ShopService.cs b/Store.Framework/Services/ShopService.cs
deleted file mode 100644
index 2091d42..0000000
--- a/Store.Framework/Services/ShopService.cs
+++ /dev/null
@@ -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
-{
- /////
- ///// 商铺服务
- /////
- //internal class ShopService : ITransientDependency
- //{
- // private readonly ShopApi _shopApi;
- // private readonly IEventBus _eventBus;
-
- // public ShopService(ShopApi shopApi, IEventBus eventBus)
- // {
- // _shopApi = shopApi;
- // _eventBus = eventBus;
- // }
-
-
- // ///
- // /// 获取所有商铺信息
- // ///
- // ///
- // ///
- // public List GetShops() where T : ShopBase
- // {
- // HttpResult result = _shopApi.GetShops();
- // if (result.StatusCode == System.Net.HttpStatusCode.OK)
- // {
- // StringBuilder sb = new StringBuilder();
- // sb.Append("[");
- // Regex regShopList = new Regex("]+?)>");
- // 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("", "\"}");
- // value = value.Replace("':'", "\":\"");
- // value = value.Replace("' '", "\",\"");
- // if (index > 0)
- // {
- // sb.Append(",");
- // }
- // sb.Append(value);
- // index++;
-
- // }
- // sb.Append("]");
- // List list = JsonConvert.DeserializeObject>(sb.ToString());
- // list.RemoveAll(a => string.IsNullOrEmpty(a.Name));
- // return list;
- // }
- // }
- // return new List();
- // }
-
-
- // public string GetShopCookie(List shops)
- // {
- // HttpResult result = _shopApi.GetShopsCookie(shops);
- // if (result.StatusCode == System.Net.HttpStatusCode.OK)
- // {
- // MatchCollection matchs = Regex.Matches(result.Html, @"([\w\W]+?)");
- // 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;
- // }
-
-
- // ///
- // /// 检测商铺登录状态
- // ///
- // 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);
- // }
- // ///
- // /// 获取值
- // ///
- // ///
- // ///
- // ///
- // 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;
- // }
- //}
-}
diff --git a/Store.Framework/Services/UserService.cs b/Store.Framework/Services/UserService.cs
index 891daa9..f1be233 100644
--- a/Store.Framework/Services/UserService.cs
+++ b/Store.Framework/Services/UserService.cs
@@ -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;
}
///
@@ -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;
}
diff --git a/Store.Product/Models/Product/ProductBase.cs b/Store.Product/Models/Product/ProductBase.cs
index 0d8d237..faf74f9 100644
--- a/Store.Product/Models/Product/ProductBase.cs
+++ b/Store.Product/Models/Product/ProductBase.cs
@@ -179,10 +179,6 @@ public class ProductBase
///
public string SkuSecretJson { get; set; }
///
- /// 新增:微信图片映射JSON。
- ///
- public string WxImgMapJson { get; set; }
- ///
/// 新增:商品来源类型名称。
///
public string ProductGetTypeText { get; set; }
@@ -195,14 +191,6 @@ public class ProductBase
///
public string SpecialProdType { get; set; }
///
- /// 新增:是否存在微信回采格式数据。
- ///
- public string HasWxFormat { get; set; }
- ///
- /// 新增:微信安全状态。
- ///
- public string WxSafeStatus { get; set; }
- ///
/// 新增:价格文本。
///
public string PriceText { get; set; }
@@ -215,22 +203,6 @@ public class ProductBase
///
public string AddUserName { get; set; }
///
- /// 新增:微信一级类目ID。
- ///
- public long? WxCat1 { get; set; }
- ///
- /// 新增:微信二级类目ID。
- ///
- public long? WxCat2 { get; set; }
- ///
- /// 新增:微信三级类目ID。
- ///
- public long? WxCat3 { get; set; }
- ///
- /// 新增:微信叶子类目ID。
- ///
- public long? WxCatLeaf { get; set; }
- ///
/// 新增:是否发送到外部服务。
///
public int? SendToOutServer { get; set; }
diff --git a/Store.Product/Models/Product/WxProductUploadProduct.cs b/Store.Product/Models/Product/WxProductUploadProduct.cs
new file mode 100644
index 0000000..3caa156
--- /dev/null
+++ b/Store.Product/Models/Product/WxProductUploadProduct.cs
@@ -0,0 +1,28 @@
+namespace Store.Product.Models.Product;
+
+///
+/// 微信商品上传使用的商品模型。继承通用商品基类,并承载微信平台专属字段。
+///
+public class WxProductUploadProduct : ProductBase
+{
+ /// 微信图片映射 JSON。
+ public string WxImgMapJson { get; set; }
+
+ /// 是否存在微信回采格式数据。
+ public string HasWxFormat { get; set; }
+
+ /// 微信安全状态。
+ public string WxSafeStatus { get; set; }
+
+ /// 微信一级类目 ID。
+ public long? WxCat1 { get; set; }
+
+ /// 微信二级类目 ID。
+ public long? WxCat2 { get; set; }
+
+ /// 微信三级类目 ID。
+ public long? WxCat3 { get; set; }
+
+ /// 微信叶子类目 ID。
+ public long? WxCatLeaf { get; set; }
+}
diff --git a/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs b/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs
index 88126c9..1d3a801 100644
--- a/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs
+++ b/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs
@@ -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; }
+ /// 已完成商品与选品定制组合后的商品数据。
+ public WxProductUploadProduct Product { get; set; } = new();
+ /// 上传任务状态,仅用于结果回写,不作为商品数据源。
+ public WxProductPublishTask Task { get; set; } = new();
public DateTime StartTime { get; set; } = DateTime.Now;
}
+///
+/// 上传任务结果。只保存任务状态和最终草稿参数,不保存原始商品数据。
+///
+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; }
+}
+
///
/// 原 UploadProduct 方法中跨 #region 使用的可变状态。
/// TODO(迁移): 个别字段当前为 dynamic,需按 Store.Product 最终 DTO 改为明确类型。
@@ -100,7 +117,7 @@ public class WxProductPublishState
public List AddPriceRules { get; set; } = new();
public dynamic WxUploadRule { get; set; }
public string PddProductCates { get; set; }
- public dynamic UploadDraftProductList { get; set; }
+ public List UploadDraftProductList { get; set; } = new();
}
///
@@ -260,4 +277,4 @@ public class WxControlState
Text = numericUpDown?.Text ?? ""
};
}
-}
\ No newline at end of file
+}
diff --git a/Store.Product/Publish/PublishContext .cs b/Store.Product/Publish/PublishContext .cs
index 11b1d7b..aa28196 100644
--- a/Store.Product/Publish/PublishContext .cs
+++ b/Store.Product/Publish/PublishContext .cs
@@ -19,7 +19,7 @@ public class PublishContextBase
///
/// 商铺
///
- public ShopBase Shop { get; set; }
+ public Shop Shop { get; set; }
///
/// 商铺名称
///
diff --git a/Store.Product/Publish/Steps/WXPublishParameters.cs b/Store.Product/Publish/Steps/WXPublishParameters.cs
index 754ed50..7e8276b 100644
--- a/Store.Product/Publish/Steps/WXPublishParameters.cs
+++ b/Store.Product/Publish/Steps/WXPublishParameters.cs
@@ -28,8 +28,11 @@ public class WXPublishParameters: PublishParameters
public bool IsCompleted { get; set; }
/// 候补:当前上传店铺。
public ShopLoginInfo Shop { get; set; }
- /// 候补:当前选品商品。
- public TemuDBProductSelect SelectProduct { get; set; }
+ ///
+ /// 上传任务对象。仅在上传完成后回写状态、草稿 ID 等任务信息,
+ /// 不参与商品标题、裂变、SKU、图片等商品内容构造。
+ ///
+ public WxProductPublishTask Task { get; set; } = new WxProductPublishTask();
/// 候补:本次上传开始时间。
public DateTime StartTime { get; set; } = DateTime.Now;
/// 候补:实际提交的佣金比例。
@@ -121,7 +124,7 @@ public class WXPublishParameters: PublishParameters
/// 候补:资质图片列表。
public List QualificationImages { get; set; } = new List();
/// 候补:待上传草稿商品列表。
- public dynamic UploadDraftProductList { get; set; }
+ public List UploadDraftProductList { get; set; } = new List();
///
/// 预售天数(页面限制为 3-45 天)。
///
diff --git a/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs b/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs
index 3e39943..df1f2b1 100644
--- a/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs
+++ b/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs
@@ -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;
diff --git a/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs b/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs
index a328d45..cb90ba4 100644
--- a/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs
+++ b/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs
@@ -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(() =>
{
diff --git a/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs b/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs
index da18b34..a35d622 100644
--- a/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs
+++ b/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs
@@ -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;
}
diff --git a/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs b/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs
index a5dfe1e..eda6755 100644
--- a/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs
+++ b/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs
@@ -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);
diff --git a/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md b/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md
index 724f851..35591b8 100644
--- a/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md
+++ b/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md
@@ -21,6 +21,13 @@
6. `WxShopTokenInfoList` 当前在入口处做了列表快照;若步骤内需要直接回写窗体静态列表,需要改为共享集合或增加回调。
7. `SubmitProductStep` 中的 `DataGridViewRow` 统计逻辑仍直接依赖表格控件,后续建议迁到运行时回调。
+## 新上传参数边界
+
+- 商品内容在进入步骤流水线前完成组合,步骤内统一使用 `WXPublishContext.Product`,其类型为继承 `ProductBase` 的 `WxProductUploadProduct`。
+- 最终提交参数只在 `WXPublishParameters.ProductParameter` 上继续修改。
+- 裂变状态使用 `WXPublishParameters.IsFissionTitle`,不再从 `TemuDBProductSelect` 读取。
+- 上传任务结果使用 `WxProductPublishTask`,只保存状态、草稿 ID 和草稿参数,不参与商品内容判断。
+
## 步骤顺序
-步骤顺序由 `WxProductPublishPipelineFactory.CreateDefault()` 统一维护,和原 `UploadProduct` 从上到下的执行顺序一致。
\ No newline at end of file
+步骤顺序由 `WxProductPublishPipelineFactory.CreateDefault()` 统一维护,和原 `UploadProduct` 从上到下的执行顺序一致。
diff --git a/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs b/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs
index a303288..b10d758 100644
--- a/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs
+++ b/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs
@@ -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;
diff --git a/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs b/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs
index d502b93..e5b590d 100644
--- a/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs
+++ b/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs
@@ -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;
diff --git a/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs b/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs
index 083797b..948449b 100644
--- a/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs
+++ b/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs
@@ -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 + "\"}");
diff --git a/Store.Product/Publish/Steps/Wx/WXPublishContext.cs b/Store.Product/Publish/Steps/Wx/WXPublishContext.cs
index 12e9ca0..471f2cf 100644
--- a/Store.Product/Publish/Steps/Wx/WXPublishContext.cs
+++ b/Store.Product/Publish/Steps/Wx/WXPublishContext.cs
@@ -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();
+
+ ///
+ /// 微信商品上传模型
+ ///
+ public new WxProductUploadProduct Product
+ {
+ get => _product;
+ set
+ {
+ _product = value ?? new WxProductUploadProduct();
+ base.Product = _product;
+ }
+ }
+
+ public WXPublishContext()
+ {
+ base.Product = _product;
+ }
+
public IProgress Progress { get; set; }
}
diff --git a/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs b/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs
index c623ae6..6e31b46 100644
--- a/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs
+++ b/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs
@@ -1326,7 +1326,7 @@ namespace TemuKJD.WXForms.ProductForms
{
this.pbLoading.Visible = false;
}));
-
+ GlobalConfig.ShopLoginList
Task.Run(() =>
{
DateTime startTime = DateTime.Now;
diff --git a/test/Store.Test/Store.Test.csproj b/test/Store.Test/Store.Test.csproj
index 1abc697..a9e8144 100644
--- a/test/Store.Test/Store.Test.csproj
+++ b/test/Store.Test/Store.Test.csproj
@@ -2,6 +2,7 @@
net462
+ x64
latest
enable
enable
@@ -17,6 +18,7 @@
+
@@ -24,4 +26,4 @@
-
\ No newline at end of file
+
diff --git a/test/Store.Test/UnitTest1.cs b/test/Store.Test/UnitTest1.cs
index 08c1a36..bc66f6e 100644
--- a/test/Store.Test/UnitTest1.cs
+++ b/test/Store.Test/UnitTest1.cs
@@ -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().GetShops();
+ }
+ [Test]
+ public void TestNewWxUploadProductUsesProductBase()
+ {
+ var context = new WXPublishContext();
+ var product = new WxProductUploadProduct
+ {
+ Title = "新版微信商品",
+ SpuId = "wx-spu-1001",
+ WxCatLeaf = 547591
+ };
-}
\ No newline at end of file
+ context.Product = product;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(context.Product, Is.TypeOf());
+ Assert.That(context.Product, Is.InstanceOf());
+ 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(_ => { })
+ };
+ 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(_ => { })
+ };
+
+ 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(_ => { })
+ };
+ context.Parameters.IsFissionTitle = true;
+ context.Parameters.FissionTitles = "裂变商品标题";
+
+ var pipeline = new WXPublishPipeline(new IPublishStep[]
+ {
+ 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
+ {
+ public string Description => "测试提交商品";
+
+ public Task 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
+ {
+ private readonly IPublishStep _inner;
+
+ public AlwaysExecuteStep(IPublishStep inner)
+ {
+ _inner = inner;
+ }
+
+ public string Description => _inner.Description;
+
+ public Task CanExecuteAsync(WXPublishContext context, CancellationToken token)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task ExecuteAsync(WXPublishContext context, CancellationToken token)
+ {
+ return _inner.ExecuteAsync(context, token);
+ }
+ }
+}