From b28940c02d2b35d78007ad2c44c7eb5efe89ea4a Mon Sep 17 00:00:00 2001 From: chfei Date: Wed, 4 Nov 2026 18:39:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E7=99=BB=E5=BD=95=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TEMUKJD/UserForm/LoginForm.cs | 894 +++++++++++++++-------------- Work/Services/CloudGoodsService.cs | 108 +--- Work/Services/ImageCacheService.cs | 70 +-- Work/Store.Api/ApiException.cs | 17 + Work/Store.Api/ApiTransport.cs | 146 +++++ Work/Store.Api/AuthApi.cs | 75 +++ Work/Store.Api/CloudGoodsApi.cs | 55 ++ Work/Store.Api/ImageCacheApi.cs | 41 ++ Work/Store.Api/StoreApiClient.cs | 50 ++ Work/Work.csproj | 1 + 10 files changed, 865 insertions(+), 592 deletions(-) create mode 100644 Work/Store.Api/ApiException.cs create mode 100644 Work/Store.Api/ApiTransport.cs create mode 100644 Work/Store.Api/AuthApi.cs create mode 100644 Work/Store.Api/CloudGoodsApi.cs create mode 100644 Work/Store.Api/ImageCacheApi.cs create mode 100644 Work/Store.Api/StoreApiClient.cs diff --git a/TEMUKJD/UserForm/LoginForm.cs b/TEMUKJD/UserForm/LoginForm.cs index fc63834..b5b6ef8 100644 --- a/TEMUKJD/UserForm/LoginForm.cs +++ b/TEMUKJD/UserForm/LoginForm.cs @@ -14,58 +14,59 @@ using System.Threading.Tasks; using System.Windows.Forms; using TemuKJD.BaseForms; using Work; +using Work.Store.Api; namespace TemuKJD.UserForm { - public partial class LoginForm : BaseForm - { + public partial class LoginForm : BaseForm + { - private Point mouseOffset;//记录鼠标指针的坐标 - private bool isMouseDown;//记录鼠标左键是否按下 - public MainForm mf; - public delegate void delegateView(); - private DevComponents.DotNetBar.PanelEx pnlRememberBox = null; - public static List UserParamList { get; set; } = new List(); + private Point mouseOffset;//记录鼠标指针的坐标 + private bool isMouseDown;//记录鼠标左键是否按下 + public MainForm mf; + public delegate void delegateView(); + private DevComponents.DotNetBar.PanelEx pnlRememberBox = null; + public static List UserParamList { get; set; } = new List(); - public LoginForm() - { - InitializeComponent(); - } + public LoginForm() + { + InitializeComponent(); + } - public void SetMobile(string m) - { - txtMobile.Text = m; - } + public void SetMobile(string m) + { + txtMobile.Text = m; + } - private void LoginForm_Load(object sender, EventArgs e) - { - if (BaseClass.MainPath == "29pdd") - { - this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.pdd_bg; - this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; - //this.pbLogo.Image = Properties.Resources.pdd_logo; - } - if (BaseClass.MainPath == "29wx") - { - this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.weixin_bg; - this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; - //this.pbLogo.Image = Properties.Resources.weixin_logo; - } - this.pbLogo.Image = BaseClass.LogoImage; + private void LoginForm_Load(object sender, EventArgs e) + { + if (BaseClass.MainPath == "29pdd") + { + this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.pdd_bg; + this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; + //this.pbLogo.Image = Properties.Resources.pdd_logo; + } + if (BaseClass.MainPath == "29wx") + { + this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.weixin_bg; + this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; + //this.pbLogo.Image = Properties.Resources.weixin_logo; + } + this.pbLogo.Image = BaseClass.LogoImage; - GetUserList(); + GetUserList(); #if DEBUG - if (File.Exists(System.Environment.CurrentDirectory + "\\choice.debug.tools")) - { - grpEnvironmentChoice.Visible = true; - pnlShowRemember.Visible = true; - } - else - { - grpEnvironmentChoice.Visible = false; - //pnlShowRemember.Visible = false; - rbtPrd.Checked = true; - } + if (File.Exists(System.Environment.CurrentDirectory + "\\choice.debug.tools")) + { + grpEnvironmentChoice.Visible = true; + pnlShowRemember.Visible = true; + } + else + { + grpEnvironmentChoice.Visible = false; + //pnlShowRemember.Visible = false; + rbtPrd.Checked = true; + } grpEnvironmentChoice.Visible = true; rbtDev.Checked = EnvironmentHelper.IsDevelopment; #else @@ -75,436 +76,461 @@ namespace TemuKJD.UserForm #endif if (BaseClass.Ico != null) - Icon = BaseClass.Ico; + Icon = BaseClass.Ico; - Text = "登录 " + BaseClass.ToolName; - if (BaseClass.LoginImage != null) - pnlLoginBg.Style.BackgroundImage = BaseClass.LoginImage; + Text = "登录 " + BaseClass.ToolName; + if (BaseClass.LoginImage != null) + pnlLoginBg.Style.BackgroundImage = BaseClass.LoginImage; - lblSignUp.Visible = BaseClass.ToolName == "跨境达-管理工具"; - lblSignUp.Visible = false; - if (FileBase.Exists("locales/ii.pak")) - { - string agentConfig = FileBase.ReadAllText("locales/ii.pak"); - if (agentConfig.Contains("\n")) - { - List lstAgentConfig = agentConfig.Split('\n').ToList(); - //0--显示 1--隐藏 - lblSignUp.Visible = lstAgentConfig[0].Trim().Equals("0"); // 是否显示注册功能 - //BaseClass.NoAnnounce = lstAgentConfig[1].Trim().Equals("1"); // 是否显示产品公告 - //if (lstAgentConfig.Count == 3) - //{ - // BaseClass.ShowGuidance = lstAgentConfig[2].Trim().Equals("0"); //是否显示使用指南 - //} - } - else - { - lblSignUp.Visible = agentConfig.Equals("0"); - } - } - else - { - lblSignUp.Visible = BaseClass.IsShowRegister; - lblBuy.Visible = BaseClass.IsShowOpenAccount; - } - lblSignUp.Visible = false; - lblBuy.Visible = false; - txtMobile.Text = BaseClass.UserName; - if (BaseClass.Remember) - { - chkRemember.Checked = true; - if (!string.IsNullOrEmpty(BaseClass.PassWord)) - password.Text = "******"; - } - LogSave.ClearFile(); + lblSignUp.Visible = BaseClass.ToolName == "跨境达-管理工具"; + lblSignUp.Visible = false; + if (FileBase.Exists("locales/ii.pak")) + { + string agentConfig = FileBase.ReadAllText("locales/ii.pak"); + if (agentConfig.Contains("\n")) + { + List lstAgentConfig = agentConfig.Split('\n').ToList(); + //0--显示 1--隐藏 + lblSignUp.Visible = lstAgentConfig[0].Trim().Equals("0"); // 是否显示注册功能 + //BaseClass.NoAnnounce = lstAgentConfig[1].Trim().Equals("1"); // 是否显示产品公告 + //if (lstAgentConfig.Count == 3) + //{ + // BaseClass.ShowGuidance = lstAgentConfig[2].Trim().Equals("0"); //是否显示使用指南 + //} + } + else + { + lblSignUp.Visible = agentConfig.Equals("0"); + } + } + else + { + lblSignUp.Visible = BaseClass.IsShowRegister; + lblBuy.Visible = BaseClass.IsShowOpenAccount; + } + lblSignUp.Visible = false; + lblBuy.Visible = false; + txtMobile.Text = BaseClass.UserName; + if (BaseClass.Remember) + { + chkRemember.Checked = true; + if (!string.IsNullOrEmpty(BaseClass.PassWord)) + password.Text = "******"; + } + LogSave.ClearFile(); - if (File.Exists("locales/wl.pak")) - { - lblSignUp.Visible = false; - lblBuy.Visible = false; - } + if (File.Exists("locales/wl.pak")) + { + lblSignUp.Visible = false; + lblBuy.Visible = false; + } - // 是否显示联系客服按钮 - if (FileBase.Exists("locales/ig.pak") || FileBase.Exists("locales/wl.pak")) - lblContactSupport.Visible = false; - else - lblContactSupport.Visible = true; - lblContactSupport.Visible = false; - } + // 是否显示联系客服按钮 + if (FileBase.Exists("locales/ig.pak") || FileBase.Exists("locales/wl.pak")) + lblContactSupport.Visible = false; + else + lblContactSupport.Visible = true; + lblContactSupport.Visible = false; + } - private void lblSignUp_Click(object sender, EventArgs e) - { - GetHtml.OpenUrl("http://dd.9u99.com/?obj=public/register"); - //RegeditForm r = new RegeditForm(); - //r.lf = this; - //r.ShowDialog(); - } + private void lblSignUp_Click(object sender, EventArgs e) + { + GetHtml.OpenUrl("http://dd.9u99.com/?obj=public/register"); + //RegeditForm r = new RegeditForm(); + //r.lf = this; + //r.ShowDialog(); + } - private void lblResetPassword_Click(object sender, EventArgs e) - { - ResetPasswordForm r = new ResetPasswordForm(); - r.ShowDialog(); - } + private void lblResetPassword_Click(object sender, EventArgs e) + { + ResetPasswordForm r = new ResetPasswordForm(); + r.ShowDialog(); + } - private void remember_CheckedChanged(object sender, EventArgs e) - { - BaseClass.Remember = chkRemember.Checked; - } + private void remember_CheckedChanged(object sender, EventArgs e) + { + BaseClass.Remember = chkRemember.Checked; + } - private void autoLogin_CheckedChanged(object sender, EventArgs e) - { - BaseClass.AutoLogin = autoLogin.Checked; - if (autoLogin.Checked) - chkRemember.Checked = true; - } + private void autoLogin_CheckedChanged(object sender, EventArgs e) + { + BaseClass.AutoLogin = autoLogin.Checked; + if (autoLogin.Checked) + chkRemember.Checked = true; + } - private void loginPanel_Click(object sender, EventArgs e) - { - if (rbtDev.Checked) - { - BaseClass.LoginApiUrl = "http://localhost:9000"; - } - else - BaseClass.LoginApiUrl = BaseClass.ServerUrl; - //"http://localhost:8000/user/user_login" + private void loginPanel_Click(object sender, EventArgs e) + { + if (rbtDev.Checked) + { + BaseClass.LoginApiUrl = "http://localhost:9000"; + } + else + BaseClass.LoginApiUrl = BaseClass.ServerUrl; + //"http://localhost:8000/user/user_login" - UserLogin ul = new UserLogin(); - ul.Mobile = txtMobile.Text; - ul.Password = password.Text; - if (txtMobile.Text.Length != 11) - { - MessageBox.Show("手机号不正确", "提示"); - txtMobile.Focus(); - return; - } - if (password.Text.Length < 6) - { - MessageBox.Show("密码长度不正确", "提示"); - password.Text = ""; - password.Focus(); - return; - } + UserLogin ul = new UserLogin(); + ul.Mobile = txtMobile.Text; + ul.Password = password.Text; + if (txtMobile.Text.Length != 11) + { + MessageBox.Show("手机号不正确", "提示"); + txtMobile.Focus(); + return; + } + if (password.Text.Length < 6) + { + MessageBox.Show("密码长度不正确", "提示"); + password.Text = ""; + password.Focus(); + return; + } - string message = ""; - int weakPassword = 0; - int lockUserName = 0; - string messageInfo = ""; - bool ok = ul.Get(ref message, out lockUserName, out weakPassword, out messageInfo); - if (ok && BaseClass.Remember) - { - var eUser = UserParamList.FirstOrDefault(u => u.UserName == BaseClass.UserName); - if (eUser != null) - UserParamList.Remove(eUser); - UserParamList.Add(new UserParam() - { - UserName = BaseClass.UserName, - PasswordMd5 = BaseClass.PassWord - }); - SaveUserList(); - } - if (!ok) - { - if (message.Contains("账户已过期")) - { - if (lblBuy.Visible == false) - BaseMsgForm.ShowError(message + ",请先联系客服商务开户!", "登录失败"); - else - { - BaseMsgForm.ShowError(message + ",请在弹出网页购买开户", "登录失败"); - GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); - } - } - else - BaseMsgForm.ShowError(message, "登录失败"); - return; - } - if (lockUserName == 1) - { - BaseMsgForm.ShowError(messageInfo, "账户锁定提醒", FormLevel.MEDIUM); - return; - } - //if (lockUserName == 0 && weakPassword == 1) - //{ - // BaseMsgForm.ShowError(messageInfo, "登录密码提醒", FormLevel.MEDIUM); - //} - //if (BaseClass.GroupId != 3 && BaseClass.GroupId != 4 && BaseClass.GroupId != 8 && BaseClass.GroupId != 9 && BaseClass.GroupId != 10 && BaseClass.GroupId != 11) - //{ - // BaseMsgForm.ShowError("您当前无权限登录系统", "登录失败"); - // return; - //} + string message = ""; + int weakPassword = 0; + int lockUserName = 0; + string messageInfo = ""; + bool ok = ul.Get(ref message, out lockUserName, out weakPassword, out messageInfo); + if (ok && BaseClass.Remember) + { + var eUser = UserParamList.FirstOrDefault(u => u.UserName == BaseClass.UserName); + if (eUser != null) + UserParamList.Remove(eUser); + UserParamList.Add(new UserParam() + { + UserName = BaseClass.UserName, + PasswordMd5 = BaseClass.PassWord + }); + SaveUserList(); + } + if (!ok) + { + if (message.Contains("账户已过期")) + { + if (lblBuy.Visible == false) + BaseMsgForm.ShowError(message + ",请先联系客服商务开户!", "登录失败"); + else + { + BaseMsgForm.ShowError(message + ",请在弹出网页购买开户", "登录失败"); + GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); + } + } + else + BaseMsgForm.ShowError(message, "登录失败"); + return; + } + if (lockUserName == 1) + { + BaseMsgForm.ShowError(messageInfo, "账户锁定提醒", FormLevel.MEDIUM); + return; + } + //if (lockUserName == 0 && weakPassword == 1) + //{ + // BaseMsgForm.ShowError(messageInfo, "登录密码提醒", FormLevel.MEDIUM); + //} + //if (BaseClass.GroupId != 3 && BaseClass.GroupId != 4 && BaseClass.GroupId != 8 && BaseClass.GroupId != 9 && BaseClass.GroupId != 10 && BaseClass.GroupId != 11) + //{ + // BaseMsgForm.ShowError("您当前无权限登录系统", "登录失败"); + // return; + //} - //this.mf.pnlShop.Visible /*= this.mf.pnlCollect.Visible*/ = this.mf.pnlTools.Visible = this.mf.pnlUpload.Visible = true; - //if (BaseClass.DyProductCollect) - //{ - // this.mf.pnlCollect.Visible = true; - //} - //mf.SetTitle(); + //this.mf.pnlShop.Visible /*= this.mf.pnlCollect.Visible*/ = this.mf.pnlTools.Visible = this.mf.pnlUpload.Visible = true; + //if (BaseClass.DyProductCollect) + //{ + // this.mf.pnlCollect.Visible = true; + //} + //mf.SetTitle(); - //if (BaseClass.GroupId == 5 || BaseClass.GroupId == 10 || BaseClass.GroupId == 11) // 上货 - //{ - // mf.ShowTaobaoLoginAndInitModule(); - // Close(); - // return; - //} - //if (BaseClass.GroupId == 4) // 客服 - //{ - // Task.Run(() => - // { - // Thread.Sleep(1000); - // Invoke(new Action(() => - // { - // ChatForms.ChatForm cf = new ChatForms.ChatForm(); - // cf.Show(); - // cf.SetTitle(); - // this.mf.Hide(); - // Hide(); - // })); - // }); - // return; - //} - //if (BaseClass.GroupId == 8) - //{ - // Process p = new Process(); - // p.StartInfo.FileName = "TaobaoLoginer.exe"; - // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; - // p.StartInfo.UseShellExecute = true; - // p.StartInfo.Verb = "runas"; - // p.Start(); - // Process.GetCurrentProcess().Kill(); - // return; - //} - //if (BaseClass.GroupId == 9) - //{ - // Process p = new Process(); - // p.StartInfo.FileName = "FindLittleRabbitOrder.exe"; - // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; - // p.StartInfo.UseShellExecute = true; - // p.StartInfo.Verb = "runas"; - // p.Start(); - // Process.GetCurrentProcess().Kill(); - // return; - //} + //if (BaseClass.GroupId == 5 || BaseClass.GroupId == 10 || BaseClass.GroupId == 11) // 上货 + //{ + // mf.ShowTaobaoLoginAndInitModule(); + // Close(); + // return; + //} + //if (BaseClass.GroupId == 4) // 客服 + //{ + // Task.Run(() => + // { + // Thread.Sleep(1000); + // Invoke(new Action(() => + // { + // ChatForms.ChatForm cf = new ChatForms.ChatForm(); + // cf.Show(); + // cf.SetTitle(); + // this.mf.Hide(); + // Hide(); + // })); + // }); + // return; + //} + //if (BaseClass.GroupId == 8) + //{ + // Process p = new Process(); + // p.StartInfo.FileName = "TaobaoLoginer.exe"; + // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; + // p.StartInfo.UseShellExecute = true; + // p.StartInfo.Verb = "runas"; + // p.Start(); + // Process.GetCurrentProcess().Kill(); + // return; + //} + //if (BaseClass.GroupId == 9) + //{ + // Process p = new Process(); + // p.StartInfo.FileName = "FindLittleRabbitOrder.exe"; + // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; + // p.StartInfo.UseShellExecute = true; + // p.StartInfo.Verb = "runas"; + // p.Start(); + // Process.GetCurrentProcess().Kill(); + // return; + //} - Hide(); - mf.Hide(); - ChangeForm c = new ChangeForm(); - c.mf = mf; - c.ShowDialog(); + + _ = Task.Run(() => + { + try + { + StoreApiClient.Instance.Auth.Login(new LoginRequest + { + UserName = BaseClass.UserName, + Password = BaseClass.PassWord + }); + } + catch (Exception ex) + { + try + { + LogSave.AddFile("Store API 登录失败:" + ex.Message); + } + catch + { + // 日志失败也不能影响主程序。 + } + } + }); - Close(); - return; - } + Hide(); + mf.Hide(); - private void panelEx1_MouseMove(object sender, MouseEventArgs e) - { - if (isMouseDown) - { - //获取鼠标的位置 - Point mousePos = MousePosition; - mousePos.Offset(mouseOffset.X, mouseOffset.Y); - Location = mousePos; - } - } + ChangeForm c = new ChangeForm(); + c.mf = mf; + c.ShowDialog(); - private void panelEx1_MouseDown(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Left) - { - mouseOffset = new Point(-e.X, -e.Y); - isMouseDown = true; - } - } + Close(); + return; + } - private void panelEx1_MouseUp(object sender, MouseEventArgs e) - { - isMouseDown = false; - } + private void panelEx1_MouseMove(object sender, MouseEventArgs e) + { + if (isMouseDown) + { + //获取鼠标的位置 + Point mousePos = MousePosition; + mousePos.Offset(mouseOffset.X, mouseOffset.Y); + Location = mousePos; + } + } - private void closePanel_Click(object sender, EventArgs e) - { - if (BaseMsgForm.ShowConfirm("是否关闭软件?\r\n必须登录账户才能使用,无账户请先注册一个", "请先登录或者注册", FormLevel.LARGE).DialogResult == DialogResult.OK) - Process.GetCurrentProcess().Kill(); - } + private void panelEx1_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + mouseOffset = new Point(-e.X, -e.Y); + isMouseDown = true; + } + } - private void password_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == 13) - loginPanel_Click(null, null); - } + private void panelEx1_MouseUp(object sender, MouseEventArgs e) + { + isMouseDown = false; + } - private void pnlShowRemember_Click(object sender, EventArgs e) - { - if (pnlShowRemember.Text == "▲") - { - if (pnlRememberBox != null) - { - pnlRememberBox.Controls.Clear(); - pnlRememberBox.Visible = false; - } - pnlShowRemember.Text = "▼"; - return; - } + private void closePanel_Click(object sender, EventArgs e) + { + if (BaseMsgForm.ShowConfirm("是否关闭软件?\r\n必须登录账户才能使用,无账户请先注册一个", "请先登录或者注册", FormLevel.LARGE).DialogResult == DialogResult.OK) + Process.GetCurrentProcess().Kill(); + } - pnlShowRemember.Text = "▲"; + private void password_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == 13) + loginPanel_Click(null, null); + } - ShowUserDropDown(); - } + private void pnlShowRemember_Click(object sender, EventArgs e) + { + if (pnlShowRemember.Text == "▲") + { + if (pnlRememberBox != null) + { + pnlRememberBox.Controls.Clear(); + pnlRememberBox.Visible = false; + } + pnlShowRemember.Text = "▼"; + return; + } - private void ShowUserDropDown() - { - if (pnlRememberBox == null) - { - pnlRememberBox = new DevComponents.DotNetBar.PanelEx(); - pnlRememberBox.Location = new Point(panelEx3.Location.X + txtMobile.Location.X, panelEx3.Location.Y + txtMobile.Location.Y + txtMobile.Height + 1); - pnlRememberBox.Height = 120; - pnlRememberBox.Width = pnlShowRemember.Location.X + pnlShowRemember.Width - txtMobile.Location.X; - pnlRememberBox.AutoScroll = true; - pnlRememberBox.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; - pnlRememberBox.Style.BorderColor.Color = Color.FromArgb(210, 210, 210); - pnlRememberBox.Padding = new Padding(1); - pnlRememberBox.Style.BackColor1.Color = Color.White; - pnlRememberBox.Style.BackColor2.Color = Color.White; - pnlRememberBox.MouseLeave += PnlRememberBox_MouseLeave; + pnlShowRemember.Text = "▲"; - this.Controls.Add(pnlRememberBox); - pnlRememberBox.BringToFront(); - } + ShowUserDropDown(); + } - pnlRememberBox.Visible = true; - pnlRememberBox.Controls.Clear(); - int top = 0; - foreach (var user in UserParamList) - { - var pnlUser = new DevComponents.DotNetBar.PanelEx(); - pnlUser.Dock = DockStyle.Top; - pnlUser.Location = new Point(0, top); - pnlUser.Tag = user; - pnlUser.Height = 30; - pnlUser.Text = user.UserName; - pnlUser.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); - pnlUser.Style.Font = txtMobile.Font; - pnlUser.Style.BackColor1.Color = Color.Transparent; - pnlUser.Style.BackColor2.Color = Color.Transparent; - pnlUser.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom; - pnlUser.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; - pnlUser.Style.BorderColor.Color = Color.FromArgb(225, 225, 225); - pnlUser.Click += PnlUser_Click; + private void ShowUserDropDown() + { + if (pnlRememberBox == null) + { + pnlRememberBox = new DevComponents.DotNetBar.PanelEx(); + pnlRememberBox.Location = new Point(panelEx3.Location.X + txtMobile.Location.X, panelEx3.Location.Y + txtMobile.Location.Y + txtMobile.Height + 1); + pnlRememberBox.Height = 120; + pnlRememberBox.Width = pnlShowRemember.Location.X + pnlShowRemember.Width - txtMobile.Location.X; + pnlRememberBox.AutoScroll = true; + pnlRememberBox.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; + pnlRememberBox.Style.BorderColor.Color = Color.FromArgb(210, 210, 210); + pnlRememberBox.Padding = new Padding(1); + pnlRememberBox.Style.BackColor1.Color = Color.White; + pnlRememberBox.Style.BackColor2.Color = Color.White; + pnlRememberBox.MouseLeave += PnlRememberBox_MouseLeave; - pnlRememberBox.Controls.Add(pnlUser); + this.Controls.Add(pnlRememberBox); + pnlRememberBox.BringToFront(); + } - var pnlClose = new DevComponents.DotNetBar.PanelEx(); - pnlClose.Location = new Point(146, 7); - pnlClose.Tag = user; - pnlClose.Height = 16; - pnlClose.Width = 16; - pnlClose.Text = "x"; - pnlClose.Style.Font = txtMobile.Font; - pnlClose.Style.Alignment = StringAlignment.Center; - pnlClose.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); - pnlClose.Style.BackColor1.Color = Color.Transparent; - pnlClose.Style.BackColor2.Color = Color.Transparent; - pnlClose.Style.Border = DevComponents.DotNetBar.eBorderType.None; - pnlClose.StyleMouseOver.Alignment = StringAlignment.Center; - pnlClose.StyleMouseOver.ForeColor.Color = Color.FromArgb(255, 255, 255); - pnlClose.StyleMouseOver.BackColor1.Color = Color.FromArgb(127, 127, 127); - pnlClose.StyleMouseOver.BackColor2.Color = Color.FromArgb(127, 127, 127); - pnlClose.Cursor = Cursors.Hand; - pnlClose.Click += PnlClose_Click; + pnlRememberBox.Visible = true; + pnlRememberBox.Controls.Clear(); + int top = 0; + foreach (var user in UserParamList) + { + var pnlUser = new DevComponents.DotNetBar.PanelEx(); + pnlUser.Dock = DockStyle.Top; + pnlUser.Location = new Point(0, top); + pnlUser.Tag = user; + pnlUser.Height = 30; + pnlUser.Text = user.UserName; + pnlUser.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); + pnlUser.Style.Font = txtMobile.Font; + pnlUser.Style.BackColor1.Color = Color.Transparent; + pnlUser.Style.BackColor2.Color = Color.Transparent; + pnlUser.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom; + pnlUser.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; + pnlUser.Style.BorderColor.Color = Color.FromArgb(225, 225, 225); + pnlUser.Click += PnlUser_Click; - pnlUser.Controls.Add(pnlClose); + pnlRememberBox.Controls.Add(pnlUser); - top += 30; - } - } + var pnlClose = new DevComponents.DotNetBar.PanelEx(); + pnlClose.Location = new Point(146, 7); + pnlClose.Tag = user; + pnlClose.Height = 16; + pnlClose.Width = 16; + pnlClose.Text = "x"; + pnlClose.Style.Font = txtMobile.Font; + pnlClose.Style.Alignment = StringAlignment.Center; + pnlClose.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); + pnlClose.Style.BackColor1.Color = Color.Transparent; + pnlClose.Style.BackColor2.Color = Color.Transparent; + pnlClose.Style.Border = DevComponents.DotNetBar.eBorderType.None; + pnlClose.StyleMouseOver.Alignment = StringAlignment.Center; + pnlClose.StyleMouseOver.ForeColor.Color = Color.FromArgb(255, 255, 255); + pnlClose.StyleMouseOver.BackColor1.Color = Color.FromArgb(127, 127, 127); + pnlClose.StyleMouseOver.BackColor2.Color = Color.FromArgb(127, 127, 127); + pnlClose.Cursor = Cursors.Hand; + pnlClose.Click += PnlClose_Click; - private void PnlClose_Click(object sender, EventArgs e) - { - var pnl = sender as DevComponents.DotNetBar.PanelEx; - if (pnl != null) - { - var user = pnl.Tag as UserParam; - if (user != null) - { - UserParamList.Remove(user); - SaveUserList(); - ShowUserDropDown(); - } - } - } + pnlUser.Controls.Add(pnlClose); - private void PnlUser_Click(object sender, EventArgs e) - { - var user = (sender as Panel)?.Tag as UserParam; - if (user != null) - { - BaseClass.UserName = user.UserName; - BaseClass.PassWord = user.PasswordMd5; - this.txtMobile.Text = user.UserName; - this.password.Text = "******"; - } - pnlRememberBox.Controls.Clear(); - pnlRememberBox.Visible = false; - pnlShowRemember.Text = "▼"; - } + top += 30; + } + } - private void PnlRememberBox_MouseLeave(object sender, EventArgs e) - { - } + private void PnlClose_Click(object sender, EventArgs e) + { + var pnl = sender as DevComponents.DotNetBar.PanelEx; + if (pnl != null) + { + var user = pnl.Tag as UserParam; + if (user != null) + { + UserParamList.Remove(user); + SaveUserList(); + ShowUserDropDown(); + } + } + } - public static void GetUserList() - { - //if (File.Exists("config\\user_login_config")) - { - try - { - //var txt = FileBase.ReadAllText("config\\user_login_config", System.Text.Encoding.UTF8); - //UserParamList = JsonHelper.DeserializeJsonToList(txt); + private void PnlUser_Click(object sender, EventArgs e) + { + var user = (sender as Panel)?.Tag as UserParam; + if (user != null) + { + BaseClass.UserName = user.UserName; + BaseClass.PassWord = user.PasswordMd5; + this.txtMobile.Text = user.UserName; + this.password.Text = "******"; + } + pnlRememberBox.Controls.Clear(); + pnlRememberBox.Visible = false; + pnlShowRemember.Text = "▼"; + } - UserParamList = NaiBa.Util.Helper.ConfigHelper.LoadConfig>("user_login_config"); - if (UserParamList == null) - UserParamList = new List(); - } - catch - { - UserParamList = new List(); - } - } - } + private void PnlRememberBox_MouseLeave(object sender, EventArgs e) + { + } - public static void SaveUserList() - { - //if (!Directory.Exists("config")) - //{ - // Directory.CreateDirectory("config"); - //} - //try - //{ - // var txt = JsonHelper.SerializeObject(UserParamList); - // FileBase.WriteAllText("config\\user_login_config", txt, System.Text.Encoding.UTF8); - //} - //catch - //{ - //} + public static void GetUserList() + { + //if (File.Exists("config\\user_login_config")) + { + try + { + //var txt = FileBase.ReadAllText("config\\user_login_config", System.Text.Encoding.UTF8); + //UserParamList = JsonHelper.DeserializeJsonToList(txt); - NaiBa.Util.Helper.ConfigHelper.SaveConfig("user_login_config", UserParamList); - } + UserParamList = NaiBa.Util.Helper.ConfigHelper.LoadConfig>("user_login_config"); + if (UserParamList == null) + UserParamList = new List(); + } + catch + { + UserParamList = new List(); + } + } + } - private void lblBuy_Click(object sender, EventArgs e) - { - GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); - } + public static void SaveUserList() + { + //if (!Directory.Exists("config")) + //{ + // Directory.CreateDirectory("config"); + //} + //try + //{ + // var txt = JsonHelper.SerializeObject(UserParamList); + // FileBase.WriteAllText("config\\user_login_config", txt, System.Text.Encoding.UTF8); + //} + //catch + //{ + //} - private void lblContactSupport_Click(object sender, EventArgs e) - { - SupportQrCodeForm qrForm = new SupportQrCodeForm(); - qrForm.ShowDialog(); - } - } + NaiBa.Util.Helper.ConfigHelper.SaveConfig("user_login_config", UserParamList); + } - public class UserParam - { - public string UserName { get; set; } - public string PasswordMd5 { get; set; } - } + private void lblBuy_Click(object sender, EventArgs e) + { + GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); + } + + private void lblContactSupport_Click(object sender, EventArgs e) + { + SupportQrCodeForm qrForm = new SupportQrCodeForm(); + qrForm.ShowDialog(); + } + } + + public class UserParam + { + public string UserName { get; set; } + public string PasswordMd5 { get; set; } + } } diff --git a/Work/Services/CloudGoodsService.cs b/Work/Services/CloudGoodsService.cs index 7bf30a5..0c64d64 100644 --- a/Work/Services/CloudGoodsService.cs +++ b/Work/Services/CloudGoodsService.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using Work.Store.Api; using System.Threading.Tasks; using Work.Api.Wx; @@ -17,35 +17,13 @@ namespace Work.Services /// public class CloudGoodsService { - private static string ServerUrl = "http://103.90.155.78:9001"; - static CloudGoodsService() - { - if (EnvironmentHelper.IsDevelopment) - { - ServerUrl = "http://localhost:14312"; - } - } public static async Task> GetGoods(GoodsSearchRequest request) { - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/SearchGoods", request); - - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.GetGoods(request); } public static string GetGoodsDetail(long goodsId) { - var html = GetHtml.PostJson(ServerUrl + $"/api/CloudGoods/GetGoodsRawData?goodsId={goodsId}", ""); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return StoreApiClient.Instance.CloudGoods.GetGoodsDetail(goodsId); } /// @@ -338,17 +316,7 @@ namespace Work.Services throw new ArgumentException("违规商品列表不能包含空项。", nameof(items)); } - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/ReportViolations", items, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret == null) - { - throw new Exception("批量上报违规商品返回为空。"); - } - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data ?? throw new Exception("批量上报违规商品返回数据为空。"); + return await StoreApiClient.Instance.CloudGoods.ReportViolations(items); } /// @@ -361,17 +329,7 @@ namespace Work.Services throw new ArgumentNullException(nameof(goodsIds)); } - var html = GetHtml.PostJson(ServerUrl + "/api/CloudGoods/GetViolationStatus", goodsIds, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret == null) - { - throw new Exception("批量查询商品违规状态返回为空。"); - } - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data ?? throw new Exception("批量查询商品违规状态返回数据为空。"); + return StoreApiClient.Instance.CloudGoods.GetViolationStatus(goodsIds); } /// 逐原因记录店铺动作。日志请求失败不能改变剔除决定或中断后续下架。 @@ -424,23 +382,13 @@ namespace Work.Services { if (requests == null || requests.Count == 0 || requests.Any(item => item == null)) throw new ArgumentException("请提供有效的违规日志。", nameof(requests)); - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/RecordViolationLog", requests, timeOut: 5); - var ret = JsonConvert.DeserializeObject(html) ?? throw new Exception("记录违规日志返回为空。"); - if (ret.Code != 200) - throw new Exception(ret.Msg); + await StoreApiClient.Instance.CloudGoods.RecordViolationLogs(requests); } /// 读取日志筛选和显示使用的违规原因字典。 public static async Task> GetViolationReasons() { - using (var web = new System.Net.WebClient { Encoding = Encoding.UTF8 }) - { - var html = await web.DownloadStringTaskAsync(ServerUrl + "/api/CloudGoods/GetViolationReasons"); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret?.Code != 200 || ret.Data == null) - throw new Exception(ret?.Msg ?? "查询违规类型返回为空。"); - return ret.Data; - } + return await StoreApiClient.Instance.CloudGoods.GetViolationReasons(); } public class ViolationReason @@ -457,12 +405,7 @@ namespace Work.Services if (shopIds == null || shopIds.Length == 0 || shopIds.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException("请提供有效的店铺编号。", nameof(shopIds)); var request = new GetViolationLogsRequest { ShopIds = shopIds, GoodsId = goodsId, BeforeId = beforeId, Size = size }; - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/GetViolationLogs", request, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html) - ?? throw new Exception("查询违规日志返回为空。"); - if (ret.Code != 200) - throw new Exception(ret.Msg); - return ret.Data ?? throw new Exception("查询违规日志返回数据为空。"); + return await StoreApiClient.Instance.CloudGoods.GetViolationLogs(request); } #region user @@ -475,14 +418,7 @@ namespace Work.Services public static async Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) { request.UserName = GetUserName(); - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/SearchFavorites", request); - - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.SearchFavorites(request); } /// @@ -494,18 +430,7 @@ namespace Work.Services public static async Task Favorite(List goodsIds) { - var html = await GetHtml.PostJsonAsync(ServerUrl + $"/api/CloudGoods/AddFavorite", new - { - UserName = GetUserName(), - GoodsIds = goodsIds, - }); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.Favorite(GetUserName(), goodsIds); } /// /// 取消收藏 @@ -516,18 +441,7 @@ namespace Work.Services public static async Task RemoveFavorites(List goodsIds) { - var html = await GetHtml.PostJsonAsync(ServerUrl + $"/api/CloudGoods/RemoveFavorites", new - { - UserName = GetUserName(), - GoodsIds = goodsIds, - }); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.RemoveFavorites(GetUserName(), goodsIds); } #endregion diff --git a/Work/Services/ImageCacheService.cs b/Work/Services/ImageCacheService.cs index 2acb731..9475205 100644 --- a/Work/Services/ImageCacheService.cs +++ b/Work/Services/ImageCacheService.cs @@ -1,14 +1,11 @@ using AllBase; -using AllBase.Helper; -using AllBase.Model.Result; using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Work.Store.Api; namespace Work.Services; @@ -17,17 +14,6 @@ namespace Work.Services; /// public class ImageCacheService { - private static string ServerUrl = "http://103.90.155.78:9001"; - static ImageCacheService() - { - if (EnvironmentHelper.IsDevelopment) - { - ServerUrl = "http://localhost:14312"; - } - } - - - /// /// 获取图片缓存 /// @@ -37,10 +23,6 @@ public class ImageCacheService Dictionary wxImgMapDic = new Dictionary(StringComparer.OrdinalIgnoreCase); try { - //if (EnvironmentHelper.IsProduction) - //{ - // return wxImgMapDic; - //} List imageUrls = new List(); imageUrls.AddRange(product.detail.galleries.Where(g => g.type == 1).Take(9).Select(a => a.url)); var temp_detailGalleries = product.detail.galleries.Where(g => g.type == 2).ToList(); @@ -58,16 +40,11 @@ public class ImageCacheService List imageUrlMd5s = imageUrls.Select(a => BaseClass.GetMd5(a)).ToList(); - var url = ServerUrl + "/api/ImageCache/GetImageCache";//获取缓存 - var html = GetHtml.PostJson(url, new { md5s = imageUrlMd5s }); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code == 200) + var caches = StoreApiClient.Instance.ImageCache.GetImageCaches(imageUrlMd5s); + foreach (var item in caches) { - foreach (var item in ret.Data) - { - item.IsCache = true; - wxImgMapDic[item.Md5] = item; - } + item.IsCache = true; + wxImgMapDic[item.Md5] = item; } } catch(Exception ex) @@ -86,38 +63,9 @@ public class ImageCacheService { try { - //if (EnvironmentHelper.IsProduction) - //{ - // return; - //} - var data = ""; - data = JsonHelper.SerializeObject(images.Where(a => a.IsCache == false).Select(a => - new - { - md5 = a.Md5, - Platform = platform, - PlatformUrl = a.PlatformUrl - } - )); - - var url = ServerUrl + "/api/ImageCache/AddOrUpdate"; - var html = GetHtml.PostJson(url, data); - - - var ret = JsonConvert.DeserializeObject>(html); - List failed = new List(); - if (ret.Code == 500) - { - if (ret.Data is JArray jArray) - { - failed = jArray.ToObject>() ?? new List(); - } - else if (ret.Data is JValue jValue && jValue.Value is string str) - { - failed.Add(str); - } - } - foreach (ImageCache item in images) + var pending = images.Where(image => !image.IsCache).ToList(); + var failed = StoreApiClient.Instance.ImageCache.SaveImageCaches((int)platform, pending); + foreach (var item in pending) { item.IsCache = !failed.Contains(item.Md5, StringComparer.OrdinalIgnoreCase); } diff --git a/Work/Store.Api/ApiException.cs b/Work/Store.Api/ApiException.cs new file mode 100644 index 0000000..1243c8d --- /dev/null +++ b/Work/Store.Api/ApiException.cs @@ -0,0 +1,17 @@ +using CsharpHttpHelper; +using Newtonsoft.Json.Linq; + +namespace Work.Store.Api; + +/// 保留业务错误数据,供批量接口识别部分失败项。 +public sealed class ApiException : HttpException +{ + public int? Code { get; } + public JToken ResponseData { get; } + + public ApiException(string message, int? code, JToken responseData) : base(message) + { + Code = code; + ResponseData = responseData; + } +} diff --git a/Work/Store.Api/ApiTransport.cs b/Work/Store.Api/ApiTransport.cs new file mode 100644 index 0000000..6b7220e --- /dev/null +++ b/Work/Store.Api/ApiTransport.cs @@ -0,0 +1,146 @@ +using CsharpHttpHelper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.RegularExpressions; + +namespace Work.Store.Api; + +/// 管理 API 的统一 HTTP 出口,负责 JSON、Bearer 认证和错误处理。 +public sealed class ApiTransport +{ + private readonly string _baseUrl; + private string _token; + private Func _login; + internal object SessionGate { get; } = new object(); + + internal void SetSession(string token, Func login) + { + lock (SessionGate) + { + _token = token; + _login = login; + } + } + + public ApiTransport(string baseUrl) + { + if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri) || + (uri.Scheme != "http" && uri.Scheme != "https") || + uri.UserInfo.Length != 0 || uri.Query.Length != 0 || uri.Fragment.Length != 0) + throw new ArgumentException("必须提供无凭据、查询参数和片段的 HTTP(S) 根地址。", nameof(baseUrl)); + + _baseUrl = uri.AbsoluteUri.TrimEnd('/') + "/"; + } + + /// 仅存于内存;手动赋值会清除自动登录凭据,设为 null 退出本地会话。 + public string Token + { + get { lock (SessionGate) return _token; } + set + { + if (value != null && (string.IsNullOrWhiteSpace(value) || value.IndexOfAny(new[] { '\r', '\n' }) >= 0)) + throw new ArgumentException("Token 不能为空白或包含换行。", nameof(value)); + SetSession(value, null); + } + } + + /// 同步请求;无 data 时返回 default(T),界面调用需放在后台线程。 + public T Send(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) + { + if (string.IsNullOrWhiteSpace(route) || + !Regex.IsMatch(route, @"\A[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)*\z")) + throw new ArgumentException("接口必须使用相对路由,例如 Auth/Login。", nameof(route)); + if (body != null) Validator.ValidateObject(body, new ValidationContext(body), true); + if (timeoutMilliseconds <= 0) throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds)); + string token = authenticated ? Token : null; + if (authenticated && token == null) + throw new HttpException("请先登录。") { IsOnline = false }; + + var url = new StringBuilder(_baseUrl).Append(route); + if (query != null) + { + bool first = true; + foreach (var pair in query) + { + if (pair.Value == null) continue; + url.Append(first ? '?' : '&').Append(Uri.EscapeDataString(pair.Key)) + .Append('=').Append(Uri.EscapeDataString(pair.Value)); + first = false; + } + } + var item = new HttpItem + { + URL = url.ToString(), + Method = method, + ContentType = "application/json; charset=utf-8", + Accept = "application/json", + PostDataType = PostDataType.Byte, + PostdataByte = body == null ? null : Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(body)), + Encoding = Encoding.UTF8, + Timeout = timeoutMilliseconds, + ReadWriteTimeout = timeoutMilliseconds, + Allowautoredirect = false + }; + for (int attempt = 0; ; attempt++) + { + if (token != null) item.Header["Authorization"] = "Bearer " + token; + var response = HttpHelper.Request(item); + int status = (int)response.StatusCode; + if (status == 0) throw new HttpException("无法连接服务器或请求超时。"); + + JObject result = null; + JsonException parseError = null; + try { result = JObject.Parse(response.Html ?? ""); } + catch (JsonException ex) { parseError = ex; } + + var code = result?["code"]; + if (status == 401 || (code?.Type == JTokenType.Integer && code.ToString() == "401")) + { + if (authenticated) + { + lock (SessionGate) + { + // 并发旧请求复用已更新的 Token;同一请求最多重新发送一次。 + if (_token == token) + { + _token = null; + if (attempt == 0 && _login != null) + _token = _login(); + } + if (attempt == 0 && _token != null) + { + token = _token; + continue; + } + } + } + throw new HttpException(result?["msg"]?.ToString() ?? "登录已失效,请重新登录。") { IsOnline = false }; + } + if (status < 200 || status >= 300) + throw new HttpException($"请求失败(HTTP {status}):{result?["msg"]}"); + if (result == null) + throw new HttpException("服务器响应不是有效的 JSON 对象。", parseError); + // 先检查业务码,避免错误响应的 data 结构掩盖服务端错误。 + if (code?.Type != JTokenType.Integer || code.ToString() != "200") + throw new ApiException("请求失败:" + (result["msg"]?.ToString() ?? "响应缺少成功状态码。"), + code?.Type == JTokenType.Integer && int.TryParse(code.ToString(), out var businessCode) ? businessCode : (int?)null, + result["data"]); + if (result["data"] == null || result["data"].Type == JTokenType.Null) return default; + try { return result["data"].ToObject(); } + catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); } + } + } + + public T GetRequired(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) where T : class + => Send(route, method, body, query, authenticated, timeoutMilliseconds) + ?? throw new HttpException("服务器响应缺少 data。"); +} + diff --git a/Work/Store.Api/AuthApi.cs b/Work/Store.Api/AuthApi.cs new file mode 100644 index 0000000..dc890d6 --- /dev/null +++ b/Work/Store.Api/AuthApi.cs @@ -0,0 +1,75 @@ +using CsharpHttpHelper; +using System; +using System.ComponentModel.DataAnnotations; + + +namespace Work.Store.Api; + +public sealed class LoginRequest +{ + [Required] + public string UserName { get; set; } + + /// UTF-8 密码的 32 位大写 MD5,由调用方计算。 + [Required] + public string Password { get; set; } + + [StringLength(100)] + public string DeviceId { get; set; } + + [StringLength(100)] + public string DeviceName { get; set; } +} + +public sealed class LoginResult +{ + public string TokenType { get; set; } = "Bearer"; + public string Token { get; set; } + public uint ExpiresInMinutes { get; set; } +} + +public sealed class AuthApi +{ + private readonly ApiTransport _transport; + private readonly string _deviceId; + + public AuthApi(string baseUrl) : this(new ApiTransport(baseUrl)) { } + + public AuthApi(ApiTransport transport, string deviceId = null) + { + _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + _deviceId = deviceId; + } + + /// 同步登录,凭据快照仅存内存,供 401 时自动重登;界面调用需放在后台线程。 + public LoginResult Login(LoginRequest request) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + var credentials = new LoginRequest + { + UserName = request.UserName, + Password = request.Password, + DeviceId = _deviceId ?? request.DeviceId, + DeviceName = request.DeviceName + }; + lock (_transport.SessionGate) + { + var result = LoginCore(credentials); + _transport.SetSession(result.Token, () => LoginCore(credentials).Token); + return result; + } + } + + private LoginResult LoginCore(LoginRequest request) + { + var result = _transport.GetRequired("Auth/Login", MethodType.Post, request, authenticated: false); + if (result == null || string.IsNullOrWhiteSpace(result.Token) || + result.Token.IndexOfAny(new[] { '\r', '\n' }) >= 0 || + !string.Equals(result.TokenType, "Bearer", StringComparison.OrdinalIgnoreCase)) + throw new HttpException("登录响应缺少有效的 Bearer Token。"); + + return result; + } +} + + diff --git a/Work/Store.Api/CloudGoodsApi.cs b/Work/Store.Api/CloudGoodsApi.cs new file mode 100644 index 0000000..76c9baf --- /dev/null +++ b/Work/Store.Api/CloudGoodsApi.cs @@ -0,0 +1,55 @@ +using AllBase.Model.Result; +using CsharpHttpHelper; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using Work.Services; + +namespace Work.Store.Api; + +/// 云商品接口,沿用服务端不带版本号的路由。 +public sealed class CloudGoodsApi +{ + private readonly ApiTransport _transport; + + public CloudGoodsApi(ApiTransport transport) + => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + + public Task> GetGoods(GoodsSearchRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchGoods", MethodType.Post, request)); + + public string GetGoodsDetail(long goodsId) + => _transport.GetRequired("CloudGoods/GetGoodsRawData", MethodType.Post, + query: new Dictionary { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) }); + + public Task> ReportViolations(List items) + => Task.Run(() => _transport.GetRequired>("CloudGoods/ReportViolations", MethodType.Post, items, + timeoutMilliseconds: 5000)); + + public List GetViolationStatus(List goodsIds) + => _transport.GetRequired>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds, + timeoutMilliseconds: 5000); + + public Task RecordViolationLogs(IReadOnlyList requests) + => Task.Run(() => _transport.Send("CloudGoods/RecordViolationLog", MethodType.Post, requests, + timeoutMilliseconds: 5000)); + + public Task> GetViolationReasons() + => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationReasons", MethodType.Get)); + + public Task> GetViolationLogs(GetViolationLogsRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationLogs", MethodType.Post, request, + timeoutMilliseconds: 5000)); + + public Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchFavorites", MethodType.Post, request)); + + public Task Favorite(string userName, List goodsIds) + => Task.Run(() => _transport.Send("CloudGoods/AddFavorite", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds })); + + public Task RemoveFavorites(string userName, List goodsIds) + => Task.Run(() => _transport.Send("CloudGoods/RemoveFavorites", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds })); +} diff --git a/Work/Store.Api/ImageCacheApi.cs b/Work/Store.Api/ImageCacheApi.cs new file mode 100644 index 0000000..2bfb036 --- /dev/null +++ b/Work/Store.Api/ImageCacheApi.cs @@ -0,0 +1,41 @@ +using CsharpHttpHelper; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Work.Services; + +namespace Work.Store.Api; + +/// 图片缓存接口,返回批量保存失败的 MD5 列表。 +public sealed class ImageCacheApi +{ + private readonly ApiTransport _transport; + + public ImageCacheApi(ApiTransport transport) + => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + + public List GetImageCaches(List md5s) + => _transport.GetRequired>("ImageCache/GetImageCache", MethodType.Post, new { md5s }); + + public List SaveImageCaches(int platform, IReadOnlyList images) + { + if (images == null) throw new ArgumentNullException(nameof(images)); + if (images.Count == 0) return new List(); + try + { + _transport.Send("ImageCache/AddOrUpdate", MethodType.Post, + images.Select(image => new { md5 = image.Md5, Platform = platform, PlatformUrl = image.PlatformUrl }).ToArray()); + return new List(); + } + catch (ApiException ex) when (ex.Code == 500) + { + // 兼容旧服务的部分失败响应;没有明确失败项时保持整体失败。 + if (ex.ResponseData is JArray array && array.Count > 0 && array.All(item => item.Type == JTokenType.String)) + return array.ToObject>(); + if (ex.ResponseData?.Type == JTokenType.String && !string.IsNullOrWhiteSpace((string)ex.ResponseData)) + return new List { (string)ex.ResponseData }; + throw; + } + } +} diff --git a/Work/Store.Api/StoreApiClient.cs b/Work/Store.Api/StoreApiClient.cs new file mode 100644 index 0000000..82c2d99 --- /dev/null +++ b/Work/Store.Api/StoreApiClient.cs @@ -0,0 +1,50 @@ +using Microsoft.Win32; +using AllBase.Helper; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Work.Store.Api; + +/// 全局共用一个用户会话与请求出口,窗体通过 Instance 访问。 +public sealed class StoreApiClient +{ + private static readonly string BaseUrl = EnvironmentHelper.IsDevelopment + ? "http://localhost:14312/api/" + : "http://103.90.155.78:9001/api/"; + + public static StoreApiClient Instance { get; } = new StoreApiClient(); + + public ApiTransport Transport { get; } + public AuthApi Auth { get; } + public CloudGoodsApi CloudGoods { get; } + public ImageCacheApi ImageCache { get; } + /// 当前 Windows 安装的 SHA-256 设备指纹,登录时作为 DeviceId 发送。 + public string DeviceId { get; } + + private StoreApiClient() + { + DeviceId = CreateDeviceId(); + Transport = new ApiTransport(BaseUrl); + Auth = new AuthApi(Transport, DeviceId); + CloudGoods = new CloudGoodsApi(Transport); + ImageCache = new ImageCacheApi(Transport); + } + + private static string CreateDeviceId() + { + using (var machine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) + using (var key = machine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography")) + { + if (!Guid.TryParse(key?.GetValue("MachineGuid") as string, out var machineGuid) || machineGuid == Guid.Empty) + throw new InvalidOperationException("无法读取有效的 Windows MachineGuid,不能生成设备指纹。"); + + using (var sha256 = SHA256.Create()) + return BitConverter.ToString(sha256.ComputeHash( + Encoding.UTF8.GetBytes("Store.Api:" + machineGuid.ToString("D")))).Replace("-", ""); + } + } + + /// 清除内存中的 Token 和自动登录凭据,不调用服务端退出接口。 + public void ClearSession() => Transport.Token = null; +} diff --git a/Work/Work.csproj b/Work/Work.csproj index 0daef55..9fea9ce 100644 --- a/Work/Work.csproj +++ b/Work/Work.csproj @@ -51,6 +51,7 @@ ..\DllReference\NaiBa.Util.Winform.dll + ..\DllReference\System.Data.SQLite.DLL False