using _29WxOrderWpf.CefHelper; using _29WxOrderWpf.Commands; using AllBase; using AllBase.Helper; using AllBase.Model.DBModel; using AllBase.Model.MvvmBaseClasses; using CefSharp; using CefSharp.Handler; using CefSharp.Wpf; using NaiBa.Util.Extension; using Org.BouncyCastle.Asn1.Ocsp; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Security.Policy; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using TemuKJD.ToolForms; using Work; using Work._29Tools.BLL; using Work._29Tools.CefTools; namespace _29WxOrderWpf.ViewModels { /// /// 拼多多买手移动版登录窗口 视图模型(WPF 版 TaobaoLoginForm) /// public class TaobaoLoginViewModel : PropertyNotifyObject { // 拼多多买手退货退款售后地址 // https://mobile.yangkeduo.com/complaint.html?order_sn=260718-101481213440980&after_sales_reason=0&after_sales_type=2&after_sales_code=1002 List cefSubProcesses = new List(); public static TaobaoLoginViewModel TaobaoLoginInstance { get; set; } = null; /// 浏览器创建完成后触发(视图在此把浏览器加入可视树) public event Action BrowserCreated; /// 请求关闭窗口 public event Action RequestClose; public int CloseAfterSecond { get; set; } private EnumOpenType _openType = EnumOpenType.manage; public EnumOpenType OpenType { get { return _openType; } set { _openType = value; OnPropertyChanged(nameof(OpenType)); OnPropertyChanged(nameof(IsLoginMode)); } } public string InitUrl { get; set; } = ""; private string shopCookieHead = "taobao.com"; private string buyerLoginUrl = "https://login.taobao.com/havanaone/login/login.htm"; private string buyerMainUrl = "https://i.taobao.com/my_itaobao"; //"https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm?"; public ChromiumWebBrowser CefWebBrowser { get; private set; } private BuyerLoginInfo buyer = null; private List lstShopCompany = new List(); private string currentUrl = ""; private volatile bool _isWindowLoaded = false; #region 界面绑定属性 /// 用户名 public string UserName { get { return this.GetValue(x => x.UserName); } set { this.SetValue(x => x.UserName, value); } } /// 密码 public string PassWord { get { return this.GetValue(x => x.PassWord); } set { this.SetValue(x => x.PassWord, value); } } /// 地址栏 public string Address { get { return this.GetValue(x => x.Address); } set { this.SetValue(x => x.Address, value); } } /// 登录成功后关闭窗口 public bool IsCloseAfterLogin { get { return this.GetValue(x => x.IsCloseAfterLogin); } set { this.SetValue(x => x.IsCloseAfterLogin, value); } } /// 登录模式 public bool IsLoginMode { get { return this.GetValue(x => x.IsLoginMode); } set { this.SetValue(x => x.IsLoginMode, value); } } #endregion #region 命令 public ICommand ReloadCommand { get; } public ICommand LoginAgainCommand { get; } public ICommand StopCommand { get; } public ICommand CopyUserNameCommand { get; } public ICommand CopyPasswordCommand { get; } #endregion public TaobaoLoginViewModel(BuyerLoginInfo buyerInfo) { TaobaoLoginInstance = this; buyer = buyerInfo; UserName = buyerInfo?.buyer_name; PassWord = ""; //DataOpenSSL.DecryptString(buyerInfo.Password); IsCloseAfterLogin = true; IsLoginMode = OpenType == EnumOpenType.login; ReloadCommand = new DelegateCommand(Reload); LoginAgainCommand = new DelegateCommand(LoginAgain); StopCommand = new DelegateCommand(StopButton); CopyUserNameCommand = new DelegateCommand(CopyUserName); CopyPasswordCommand = new DelegateCommand(CopyPassword); } /// /// 窗口加载完成时调用 /// public void Start() { IsLoginMode = OpenType == EnumOpenType.login; _isWindowLoaded = true; if (CloseAfterSecond > 0) { Task.Run(() => { Thread.Sleep(1000 * CloseAfterSecond); if (_isWindowLoaded) { Application.Current?.Dispatcher.Invoke(new Action(() => { if (_isWindowLoaded) RequestClose?.Invoke(); })); } }); } Task.Run(() => { AutoLogin(); }); } /// /// 窗口关闭时调用,停止后台任务 /// public void Stop() { _isWindowLoaded = false; // 1. 解除 JS 绑定 CefWebBrowser?.JavascriptObjectRepository?.UnRegisterAll(); CefWebBrowser?.CloseDevTools(); // 2. 优雅关闭浏览器 CefWebBrowser?.GetBrowser()?.CloseBrowser(false); // 3. 清理残留子进程(按路径判断,避免误杀) CleanupBrowserSubprocesses(this.cefSubProcesses); // 4. 释放控件 CefWebBrowser?.Dispose(); CefWebBrowser = null; } public int GetRenderProcessId() { int parentPid = Process.GetCurrentProcess().Id; // 查找所有名为 CefSharp.BrowserSubprocess 且父进程为当前应用的进程 var subprocesses = Process.GetProcessesByName("CefSharp.BrowserSubprocess") .Where(p => { try { return p.SessionId == Process.GetCurrentProcess().SessionId && GetParentProcessId(p.Id) == parentPid; } catch { return false; } }); // 返回第一个匹配的 Render 进程 ID,实际可能有多个 return subprocesses.FirstOrDefault()?.Id ?? -1; } // 辅助方法:获取父进程 ID (需引用 System.Management 或使用 P/Invoke) private static int GetParentProcessId(int processId) { using (var query = new System.Management.ManagementObjectSearcher( $"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {processId}")) { using (var results = query.Get()) { foreach (System.Management.ManagementObject obj in results) { return Convert.ToInt32(obj["ParentProcessId"]); } } } return -1; } public static void CleanupBrowserSubprocesses(List processes = null) { // 有可能释放错误导致程序崩溃,废弃 return; try { if (processes == null) processes = new List(); string appBasePath = AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\', '/'); Process[] subprocesses = Process.GetProcessesByName("CefSharp.BrowserSubprocess"); foreach (Process process in subprocesses) { bool shouldKill = false; try { // 安全地获取进程模块信息,进程可能在我们检查时已退出 if (process != null && !process.HasExited) { string moduleFileName = process.MainModule?.FileName; if (!string.IsNullOrEmpty(moduleFileName)) { // 判断进程是否从当前应用目录启动 string processDir = System.IO.Path.GetDirectoryName(moduleFileName); if (!string.IsNullOrEmpty(processDir) && (processDir.Equals(appBasePath, StringComparison.OrdinalIgnoreCase) || appBasePath.Contains(processDir) // 处理子目录情况 || moduleFileName.Contains(appBasePath))) { if (!processes.Any(p => p.Handle.ParseToInt() == process.Handle.ParseToInt())) shouldKill = true; } } } } catch (InvalidOperationException) { // 进程可能在检查过程中已退出,忽略 continue; } catch (Win32Exception ex) when (ex.NativeErrorCode == 5) // 访问被拒绝 { // 可能是权限问题或其他应用的进程,跳过 continue; } if (shouldKill) { try { process.Kill(); process.WaitForExit(500); // 等待最多500毫秒,确保进程终止 } catch (Exception killEx) { // 记录日志,但通常进程已死 //System.Diagnostics.Debug.WriteLine($"终止进程时出错: {killEx.Message}"); } } } } catch (Exception ex) { // 这里最好不要用MessageBox,尤其是在关闭流程中,可能引发线程问题 // 建议记录到日志文件 //System.Diagnostics.Debug.WriteLine($"清理子进程时发生异常: {ex}"); } } /// /// 自动登录店铺后台 /// private async void AutoLogin() { if (OpenType != EnumOpenType.login && buyer == null) return; _ = Application.Current.Dispatcher.InvokeAsync(async () => { if (CefWebBrowser == null) { this.cefSubProcesses = Process.GetProcessesByName("CefSharp.BrowserSubprocess").ToList(); if (!string.IsNullOrEmpty(InitUrl)) CefWebBrowser = WPFCEFBrowser.CreateChromiumWebBrowser(InitUrl, false); else CefWebBrowser = WPFCEFBrowser.CreateChromiumWebBrowser(buyerMainUrl, false); LocalResourceRequestHandler resourceRequestHandler = new LocalResourceRequestHandler(); resourceRequestHandler.ResourceLoadCompleteEvent += RequestHandler_ResourceLoadComplete; resourceRequestHandler.BeforeResourceLoadEvent += RequestHandler_BeforeResourceLoad; CefWebBrowser.RequestHandler = new LocalRequestHandler(resourceRequestHandler); var lifeSapnHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow); CefWebBrowser.LifeSpanHandler = lifeSapnHandler; //CefWebBrowser.KeyboardHandler = new KeyboardHandler(); CefWebBrowser.KeyboardHandler = new CefKeyboardHandler(); CefWebBrowser.FrameLoadEnd += CefWebBrowser_FrameLoadEnd; CefWebBrowser.AddressChanged += CefWebBrowser_AddressChanged; CefWebBrowser.LoadError += CefWebBrowser_LoadError; // 通知视图把浏览器加入可视树(必须在等待浏览器初始化之前完成) BrowserCreated?.Invoke(CefWebBrowser); List cookieLst = new List(); try { if (buyer != null) { cookieLst = JsonHelper.DeserializeJsonToList(buyer?.cookie); if (cookieLst == null) cookieLst = new List(); } } catch { } // cef138必须init后方可设置或删除cookie while (!CefWebBrowser.IsBrowserInitialized || !CefWebBrowser.GetBrowser().MainFrame.IsValid) await Task.Delay(20); //await WPFCEFBrowser.DeleteCookieAsync(CefWebBrowser.GetCookieManager(null)); //cef 138 如果没有设置RequestContext 可能为空,直接getCookieManager //await WPFCEFBrowser.SetCookieAsync(JsonHelper.SerializeObject(cookieLst), CefWebBrowser.GetCookieManager(null)); if (OpenType != EnumOpenType.login) foreach (var cook in cookieLst) { await WPFCEFBrowser.SetCookieAsync(cook.Domain.Trim('.'), cook.Name + "=" + cook.Value + ";", CefWebBrowser.GetCookieManager(null)); //t.Wait(5000); } //根据登录的具体店铺设置local storage if (!string.IsNullOrEmpty(buyer?.buyer_id)) { //var storageJsCode = "window.localStorage.setItem('" + "mall-info-id" + "', '" + shop.ShopId + "');\r\n"; //storageJsCode += "window.localStorage.setItem('" + "agentseller-mall-info-id" + "', '" + shop.ShopId + "');\r\n"; //while (!CefWebBrowser.IsBrowserInitialized || !CefWebBrowser.GetBrowser().MainFrame.IsValid) // Thread.Sleep(20); //CefWebBrowser.GetBrowser().MainFrame.ExecuteJavaScriptAsync(storageJsCode); } if (!string.IsNullOrEmpty(InitUrl)) CefWebBrowser.Load(InitUrl); else CefWebBrowser.Load(buyerMainUrl); //不是登录的话就此返回 if (OpenType != EnumOpenType.login) { return; } for (int i = 0; i < 50; i++) { if (CefWebBrowser.IsLoading) await Task.Delay(100); } await Task.Delay(Randomx.GetId(1500, 2000)); if (!_isWindowLoaded) return; while (!CefWebBrowser.Address?.StartsWith(buyerLoginUrl) ?? false) { await Task.Delay(1000); if (!_isWindowLoaded) return; } } } ); } private void CefWebBrowser_LoadError(object sender, CefSharp.LoadErrorEventArgs e) { } private void CefWebBrowser_AddressChanged(object sender, DependencyPropertyChangedEventArgs e) { if (Application.Current?.Dispatcher != null) { Application.Current.Dispatcher.Invoke(new Action(() => { currentUrl = e.NewValue.ParseToString(); Address = e.NewValue.ParseToString(); })); } } private void CefWebBrowser_FrameLoadEnd(object sender, CefSharp.FrameLoadEndEventArgs e) { //js绑定C#对象 html页面内声明绑定的C#对象 StringBuilder sb = new StringBuilder(); sb.Append("(function(){"); sb.Append("CefSharp.BindObjectAsync('shopObj');"); sb.Append("})();"); CefWebBrowser.GetBrowser().MainFrame.EvaluateScriptAsync(sb.ToString()); } private bool RequestHandler_BeforeResourceLoad(object sender, string url, string postData, IRequest request) { return false; } private async void RequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request) { await DoResourceLoadComplete(url, responseData); } private async Task DoResourceLoadComplete(string url, string responseData) { //if (url.Contains(buyerLoginUrl) && OpenType == EnumOpenType.login) //{ // Task.Run(() => { CefWebBrowser.Load(buyerLoginUrl); }); //} var data = responseData; //if (data.Contains("karl_malone_32") || data.Contains("jtt_fan")) { //https://h5api.m.taobao.com/h5/mtop.user.getusersimple/1.0/?jsv=2.7.2&appKey=12574478&t=1789984970076&sign=6bb7d3b5cd8bd3ab32bd97c1a0df1c0f&jsonpIncPrefix=tbnavnew&api=mtop.user.getUserSimple&v=1.0&dataType=json&type=originaljson&data=%7B%7D //{"api":"mtop.user.getUserSimple","v":"1.0","ret":["SUCCESS::调用成功"],"data":{"nick":"jtt_fan","userNumId":440631048,"displayNick":"jtt_fan"}} } if (url.Contains(buyerMainUrl) || (data.Contains("\"displayNick\"") && data.Contains("\"nick\"") && data.Contains("\"userNumId\""))) { if (OpenType == EnumOpenType.login) { await Task.Run(async () => { if (data.Contains("\"ret\":[\"SUCCESS::调用成功\"]")) { var uid = BaseRegex.GetStringJson(ref data, "userNumId"); var nickname = BaseRegex.GetStringJson(ref data, "displayNick"); var username = nickname; if (!string.IsNullOrEmpty(nickname) && !string.IsNullOrEmpty(uid) && !isAddOrUpdateBuyer) { isAddOrUpdateBuyer = true; try { AddOrUpdateBuyer(uid, username, nickname); } catch { } isAddOrUpdateBuyer = false; } } }); } } } bool isAddOrUpdateBuyer = false; void AddOrUpdateBuyer(string uid, string username, string nickname) { Thread.Sleep(2000); //List cookieList = WPFCEFBrowser.GetCookieList(shopCookieHead, CefWebBrowser.GetCookieManager(null)); List cookieList = WPFCEFBrowser.GetCookieList("", CefWebBrowser.GetCookieManager(null)); var buyerUrl = BaseClass.Build29WxObjString("user/add_taobao_buyer"); if (this.buyer == null || this.buyer.buyer_id != uid) this.buyer = GlobalConfig.TaobaoBuyerLoginList.FirstOrDefault(b => b.buyer_id == uid); if (this.buyer != null) { this.buyer.status = "在线"; this.buyer.user_name = username; this.buyer.buyer_nick = nickname; this.buyer.cookie = JsonHelper.SerializeObject(cookieList); buyerUrl = BaseClass.Build29WxObjString("user/mdy_taobao_buyer"); } var buyerData = UserLogin.GetSignaturex() + "&buyer_id=" + uid + "&buyer_name=" + username + "&buyer_nick=" + nickname; var result = GetHtml.PostUtf8(buyerUrl, buyerData); if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200")) result = GetHtml.PostUtf8(buyerUrl, buyerData); buyerUrl = BaseClass.Build29WxObjString("user/mdy_taobao_buyer_cookie"); buyerData = UserLogin.GetSignaturex() + "&buyer_id=" + uid + "&status=" + "在线" + "&cookie=" + GetHtml.UrlEncodeUtf8(JsonHelper.SerializeObject(cookieList)); result = GetHtml.PostUtf8(buyerUrl, buyerData); if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200")) result = GetHtml.PostUtf8(buyerUrl, buyerData); Application.Current?.Dispatcher.Invoke(new Action(() => { try { if (IsCloseAfterLogin) RequestClose?.Invoke(); } catch (Exception ex) { } })); } private void StopButton() { } private void CopyUserName() { if (!string.IsNullOrEmpty(UserName)) Clipboard.SetDataObject(UserName); } private void CopyPassword() { if (!string.IsNullOrEmpty(PassWord)) Clipboard.SetDataObject(PassWord); } private void Reload() { if (CefWebBrowser != null) { if (Address != "about:blank") CefWebBrowser.Load(Address); else CefWebBrowser.Load(buyerMainUrl); } } private void LoginAgain() { if (this.currentUrl != buyerLoginUrl) this.CefWebBrowser?.Load(buyerLoginUrl); } #region 静态方法(检测/更新买手登录状态) public static void CheckBuyerLogin(List checkBuyers, bool isShowProgress = true) { for (int i = 0; i < checkBuyers.Count; i++) { var buyer = GlobalConfig.TaobaoBuyerLoginList.FirstOrDefault(v => v.buyer_id == checkBuyers[i].buyer_id); var buys = GlobalConfig.TaobaoBuyerLoginList.Where(v => v.buyer_id == checkBuyers[i].buyer_id).ToList(); if (buyer == null) continue; // 先从服务端取最新cookie var url = BaseClass.Build29WxObjString("user/get_taobao_buyer"); var data = UserLogin.GetSignaturex(); data += "&buyer_ids=" + buyer.buyer_id; data += "&page_size=1"; var result = GetHtml.PostUtf8(url, data); if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200")) result = GetHtml.PostUtf8(url, data); if (!result.Contains("\"code\":200")) continue; List loginInfoLst = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(result)["data"].ParseToString()); var serverBuyer = loginInfoLst.FirstOrDefault(); if (buyer != null && serverBuyer != null) { buyer.cookie = serverBuyer.cookie; foreach (var buy in buys) { buy.cookie = serverBuyer.cookie; } } if (buyer != null) { return; var loginResult = CheckBuyerLogin(buyer); if (!loginResult.Contains("\"success\":true") && !loginResult.Contains("\"login\":true") && !loginResult.Contains("\"nickname\"")) { buyer.cookie = "[]"; foreach (var buy in buys) { buy.status = "掉线"; buy.cookie = "[]"; } } else { foreach (var buy in buys) { buy.status = "在线"; } } UpdateServerBuyerCookies(buyer); // 店铺cookie和状态写入服务端 } } } public static string CheckBuyerLogin(BuyerLoginInfo buyer, string refere = "https://mobile.yangkeduo.com/goods.html") { string url = "https://mobile.yangkeduo.com/proxy/api/api/apollo/v3/user/me?pdduid=" + buyer.buyer_id; return HttpResponseYkdBLL.YangkeduoGetResponse(buyer, url, refere); } public static string UpdateServerBuyerCookies(BuyerLoginInfo buyer, string fromTool = null) { var url = BaseClass.Build29WxObjString("user/mdy_taobao_buyer_cookie"); var data = UserLogin.GetSignaturex() + "&status=" + buyer.status + "&cookie=" + GetHtml.UrlEncodeUtf8(buyer.cookie) + "&buyer_id=" + buyer.buyer_id; var result = GetHtml.PostUtf8(url, data); if (!result.Contains("\"code\":200")) result = GetHtml.PostUtf8(url, data); return result; } /// /// 设置淘宝店铺掉线 /// /// /// /// public static string UpdateServerBuyerDisconnect(BuyerLoginInfo buyer, string fromTool = null) { //buyer.status = "掉线"; //buyer.cookie = "[]"; //var url = BaseClass.Build29WxObjString("user/mdy_taobao_buyer_cookie"); //var data = UserLogin.GetSignaturex() // + "&status=" + "掉线" // + "&cookie=" + "[]" // + "&buyer_id=" + buyer.buyer_id; //var result = GetHtml.PostUtf8(url, data); //if (!result.Contains("\"code\":200")) // result = GetHtml.PostUtf8(url, data); //return result; return ""; } #endregion } internal class CefKeyboardHandler : IKeyboardHandler { public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) { if (type == KeyType.KeyUp && Enum.IsDefined(typeof(System.Windows.Forms.Keys), windowsKeyCode)) { var key = (System.Windows.Forms.Keys)windowsKeyCode; switch (key) { case System.Windows.Forms.Keys.F12: browser.ShowDevTools(); break; case System.Windows.Forms.Keys.F5: if (modifiers == CefEventFlags.ControlDown) { //MessageBox.Show("ctrl+f5"); browser.Reload(true); //强制忽略缓存 } else { //MessageBox.Show("f5"); browser.Reload(); } break; } } return false; } public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) { return false; } } }