Files
29WxOrder/29WxOrderWpf/ViewModels/TaobaoLoginViewModel.cs
T

711 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 拼多多买手移动版登录窗口 视图模型(WPF 版 TaobaoLoginForm)
/// </summary>
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<Process> cefSubProcesses = new List<Process>();
public static TaobaoLoginViewModel TaobaoLoginInstance { get; set; } = null;
/// <summary>浏览器创建完成后触发(视图在此把浏览器加入可视树)</summary>
public event Action<ChromiumWebBrowser> BrowserCreated;
/// <summary>请求关闭窗口</summary>
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<ShopCompany> lstShopCompany = new List<ShopCompany>();
private string currentUrl = "";
private volatile bool _isWindowLoaded = false;
#region 界面绑定属性
/// <summary>用户名</summary>
public string UserName
{
get { return this.GetValue(x => x.UserName); }
set { this.SetValue(x => x.UserName, value); }
}
/// <summary>密码</summary>
public string PassWord
{
get { return this.GetValue(x => x.PassWord); }
set { this.SetValue(x => x.PassWord, value); }
}
/// <summary>地址栏</summary>
public string Address
{
get { return this.GetValue(x => x.Address); }
set { this.SetValue(x => x.Address, value); }
}
/// <summary>登录成功后关闭窗口</summary>
public bool IsCloseAfterLogin
{
get { return this.GetValue(x => x.IsCloseAfterLogin); }
set { this.SetValue(x => x.IsCloseAfterLogin, value); }
}
/// <summary>登录模式</summary>
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);
}
/// <summary>
/// 窗口加载完成时调用
/// </summary>
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();
});
}
/// <summary>
/// 窗口关闭时调用,停止后台任务
/// </summary>
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<Process> processes = null)
{
// 有可能释放错误导致程序崩溃,废弃
return;
try
{
if (processes == null)
processes = new List<Process>();
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}");
}
}
/// <summary>
/// 自动登录店铺后台
/// </summary>
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<Cookie> cookieLst = new List<Cookie>();
try
{
if (buyer != null)
{
cookieLst = JsonHelper.DeserializeJsonToList<CefSharp.Cookie>(buyer?.cookie);
if (cookieLst == null)
cookieLst = new List<Cookie>();
}
}
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<Cookie> cookieList = WPFCEFBrowser.GetCookieList(shopCookieHead, CefWebBrowser.GetCookieManager(null));
List<Cookie> 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<BuyerLoginInfo> 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<BuyerLoginInfo> loginInfoLst = JsonHelper.DeserializeJsonToList<BuyerLoginInfo>(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;
}
/// <summary>
/// 设置淘宝店铺掉线
/// </summary>
/// <param name="buyer"></param>
/// <param name="fromTool"></param>
/// <returns></returns>
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;
}
}
}