增加热更功能

This commit is contained in:
扫地僧 2026-05-07 23:25:10 +08:00
parent 4a5dd2418b
commit 235021cf90
166 changed files with 2845 additions and 218 deletions

6
.gitignore vendored
View File

@ -2,4 +2,8 @@
dujiaoka-master/ dujiaoka-master/
Vmianqian/ Vmianqian/
.vs .vs
.idea .idea
bin_proxycheck/
bin/Debug/*
bin/Output/*
bin/Release/*

View File

@ -159,6 +159,29 @@ public sealed class AlipayMonitorOptions
/// 轮询时默认回查最近多少天的账单。 /// 轮询时默认回查最近多少天的账单。
/// </summary> /// </summary>
public int QueryDays { get; set; } = 1; public int QueryDays { get; set; } = 1;
/// <summary>
/// 可选:支付宝账号。
/// 仅用于 WebView2 登录页自动填充,不参与后台 HttpClient 直登。
/// </summary>
public string Account { get; set; } = string.Empty;
/// <summary>
/// 可选:支付宝密码。
/// 仅用于 WebView2 登录页自动填充,不参与后台 HttpClient 直登。
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 是否启用自动填充账号密码。
/// 启用后,程序会在 WebView2 登录页尝试:
/// 1. 切换到密码登录
/// 2. 自动填入账号
/// 3. 自动填入密码
/// 4. 自动点击登录按钮
/// 若遇到滑块、短信、人机校验,仍需用户手动处理。
/// </summary>
public bool EnableAutoFill { get; set; }
} }
/// <summary> /// <summary>
@ -1450,7 +1473,7 @@ public sealed class AlipayLoginForm : Form
private string _detectedCtoken = string.Empty; private string _detectedCtoken = string.Empty;
private bool _loginEventRaised; private bool _loginEventRaised;
private bool _navigatedToBillPage; private bool _navigatedToBillPage;
private bool _waitingSecurityVerify; private bool _autoFillAttempted;
/// <summary> /// <summary>
/// 当检测到登录成功并提取到 Cookie 后触发。 /// 当检测到登录成功并提取到 Cookie 后触发。
@ -1506,7 +1529,9 @@ public sealed class AlipayLoginForm : Form
_webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded; _webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
_webView.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested; _webView.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
_statusLabel.Text = "请使用支付宝扫码登录。"; _statusLabel.Text = _options.EnableAutoFill
? "正在打开支付宝登录页,将自动尝试账号密码登录;若出现验证,请手动完成。"
: "请使用支付宝扫码登录。";
_webView.CoreWebView2.Navigate(_options.LoginUrl); _webView.CoreWebView2.Navigate(_options.LoginUrl);
} }
catch (Exception ex) catch (Exception ex)
@ -1581,7 +1606,6 @@ public sealed class AlipayLoginForm : Form
if (IsSecurityVerifyUrl(currentUrl)) if (IsSecurityVerifyUrl(currentUrl))
{ {
_waitingSecurityVerify = true;
_statusLabel.Text = "支付宝触发安全校验,请在当前页面完成验证,完成后程序会自动继续。"; _statusLabel.Text = "支付宝触发安全校验,请在当前页面完成验证,完成后程序会自动继续。";
StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs
{ {
@ -1593,7 +1617,6 @@ public sealed class AlipayLoginForm : Form
if (IsBillReadyUrl(currentUrl)) if (IsBillReadyUrl(currentUrl))
{ {
_waitingSecurityVerify = false;
await TryExtractCookiesAndRaiseAsync(currentUrl); await TryExtractCookiesAndRaiseAsync(currentUrl);
return; return;
} }
@ -1641,12 +1664,7 @@ public sealed class AlipayLoginForm : Form
"""; """;
var result = await core.ExecuteScriptAsync(script); var result = await core.ExecuteScriptAsync(script);
if (string.IsNullOrWhiteSpace(result)) var json = ExtractWebViewJsonPayload(result);
{
return;
}
var json = JsonSerializer.Deserialize<string>(result);
if (string.IsNullOrWhiteSpace(json)) if (string.IsNullOrWhiteSpace(json))
{ {
return; return;
@ -1661,14 +1679,14 @@ public sealed class AlipayLoginForm : Form
if (IsSecurityVerifyUrl(currentUrl)) if (IsSecurityVerifyUrl(currentUrl))
{ {
_waitingSecurityVerify = true;
_statusLabel.Text = "支付宝触发安全校验,请在当前页面完成验证,完成后程序会自动继续。"; _statusLabel.Text = "支付宝触发安全校验,请在当前页面完成验证,完成后程序会自动继续。";
return; return;
} }
await TryAutoFillLoginAsync(currentUrl);
if (IsBillReadyUrl(currentUrl)) if (IsBillReadyUrl(currentUrl))
{ {
_waitingSecurityVerify = false;
await TryExtractCookiesAndRaiseAsync(currentUrl); await TryExtractCookiesAndRaiseAsync(currentUrl);
return; return;
} }
@ -1692,6 +1710,415 @@ public sealed class AlipayLoginForm : Form
} }
} }
/// <summary>
/// 在 WebView2 登录页尝试自动填充账号密码并点击登录。
/// 注意:
/// 1. 支付宝登录页 DOM 可能随版本变化,本方法采用“多选择器 + 页面文本”策略尽量兼容;
/// 2. 若页面出现滑块、短信、人机验证,本方法不会强行绕过,只会提示用户手动接管;
/// 3. 为避免重复点击,本方法整个登录流程只自动尝试一次。
/// </summary>
private async Task TryAutoFillLoginAsync(string currentUrl)
{
if (_autoFillAttempted ||
!_options.EnableAutoFill ||
_webView.CoreWebView2 == null ||
IsDisposed)
{
return;
}
if (string.IsNullOrWhiteSpace(_options.Account) || string.IsNullOrWhiteSpace(_options.Password))
{
return;
}
if (IsBillReadyUrl(currentUrl) || IsSecurityVerifyUrl(currentUrl))
{
return;
}
try
{
var submitted = false;
var partialFilled = false;
var switchedToPasswordLogin = false;
for (var attempt = 1; attempt <= 10; attempt++)
{
if (_webView.CoreWebView2 == null || IsDisposed)
{
return;
}
var script = $$"""
(async () => {
const result = {
switched: false,
accountFilled: false,
passwordFilled: false,
clicked: false,
hasAccountInput: false,
hasPasswordInput: false,
pageText: document.body ? document.body.innerText : "",
title: document.title || "",
href: location.href
};
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function setNativeValue(element, value) {
if (!element) return false;
const prototype = Object.getPrototypeOf(element);
const descriptor = prototype
? Object.getOwnPropertyDescriptor(prototype, 'value')
: null;
if (descriptor && descriptor.set) {
descriptor.set.call(element, value);
} else {
element.value = value;
}
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
element.dispatchEvent(new Event('blur', { bubbles: true }));
return true;
}
function isVisible(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0' &&
!!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
function normalizeText(element) {
return (element?.innerText || element?.textContent || element?.value || '')
.replace(/\s+/g, '')
.trim();
}
function triggerMouseClick(element) {
if (!element || !isVisible(element)) return false;
try { element.scrollIntoView({ block: 'center', inline: 'center' }); } catch {}
try { element.focus(); } catch {}
for (const eventName of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
try {
element.dispatchEvent(new MouseEvent(eventName, {
bubbles: true,
cancelable: true,
composed: true,
view: window
}));
} catch {}
}
try { element.click(); } catch {}
return true;
}
function clickElementOrAncestors(element, maxDepth = 4) {
let current = element;
let depth = 0;
while (current && depth <= maxDepth) {
if (triggerMouseClick(current)) {
return true;
}
current = current.parentElement;
depth++;
}
return false;
}
function findPasswordLoginTrigger() {
const keywords = [
'',
'',
'',
'',
'',
''
];
const selectors = [
'[data-status="show_login"]',
'[data-role*="password"]',
'[data-role*="login"]',
'[class*="password"]',
'[class*="login"]',
'[class*="tab"]',
'[class*="switch"]',
'[id*="password"]',
'[id*="login"]',
'[id*="tab"]',
'a',
'button',
'div',
'span',
'li'
];
for (const selector of selectors) {
const elements = [...document.querySelectorAll(selector)];
const found = elements.find(el => {
if (!isVisible(el)) return false;
const text = normalizeText(el);
return keywords.some(keyword => text.includes(keyword));
});
if (found) {
return found;
}
}
return null;
}
function findFirstVisible(selectors) {
for (const selector of selectors) {
const list = [...document.querySelectorAll(selector)];
const found = list.find(isVisible);
if (found) return found;
}
return null;
}
function findLoginButton() {
const selectors = [
'button[type="submit"]',
'input[type="submit"]',
'button',
'input[type="button"]',
'a',
'div'
];
const keywords = ['', '', '', ''];
for (const selector of selectors) {
const elements = [...document.querySelectorAll(selector)];
const found = elements.find(el => {
if (!isVisible(el)) return false;
const text = normalizeText(el);
if (!keywords.some(keyword => text.includes(keyword))) return false;
const rect = el.getBoundingClientRect();
return rect.width >= 40 && rect.height >= 24;
});
if (found) {
return found;
}
}
return null;
}
const passwordTrigger = findPasswordLoginTrigger();
if (passwordTrigger) {
result.switched = clickElementOrAncestors(passwordTrigger, 5);
await sleep(350);
}
const accountSelectors = [
'input[name="loginId"]',
'input[name="account"]',
'input[name="username"]',
'input[name="userName"]',
'input[name="user-id"]',
'input[id*="loginId"]',
'input[id*="account"]',
'input[id*="user"]',
'input[placeholder*="账号"]',
'input[placeholder*="手机号"]',
'input[placeholder*="邮箱"]',
'input[autocomplete="username"]',
'input[type="text"]',
'input:not([type])'
];
const passwordSelectors = [
'input[name="password"]',
'input[id*="password"]',
'input[placeholder*="密码"]',
'input[type="password"]',
'input[autocomplete="current-password"]'
];
const accountInput = findFirstVisible(accountSelectors);
const passwordInput = findFirstVisible(passwordSelectors);
result.hasAccountInput = !!accountInput;
result.hasPasswordInput = !!passwordInput;
if (accountInput) {
accountInput.focus();
result.accountFilled = setNativeValue(accountInput, {{JsonSerializer.Serialize(_options.Account)}});
try { accountInput.blur(); } catch {}
}
if (passwordInput) {
passwordInput.focus();
result.passwordFilled = setNativeValue(passwordInput, {{JsonSerializer.Serialize(_options.Password)}});
try { passwordInput.blur(); } catch {}
}
if (result.accountFilled && result.passwordFilled) {
await sleep(300);
}
let loginButton = findLoginButton();
if (loginButton && result.accountFilled && result.passwordFilled) {
result.clicked = clickElementOrAncestors(loginButton, 4);
}
if (!result.clicked && passwordInput && result.accountFilled && result.passwordFilled) {
try {
passwordInput.focus();
passwordInput.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Enter', code: 'Enter' }));
passwordInput.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, key: 'Enter', code: 'Enter' }));
passwordInput.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: 'Enter', code: 'Enter' }));
} catch {}
}
if (!result.clicked && result.accountFilled && result.passwordFilled) {
loginButton = findLoginButton();
const form = passwordInput?.form || accountInput?.form || loginButton?.closest?.('form') || null;
if (form) {
try {
if (typeof form.requestSubmit === 'function') {
form.requestSubmit();
} else {
form.submit();
}
result.clicked = true;
} catch {}
}
}
result.href = location.href;
result.pageText = document.body ? document.body.innerText : "";
return JSON.stringify(result);
})();
""";
var raw = await _webView.CoreWebView2.ExecuteScriptAsync(script);
var json = ExtractWebViewJsonPayload(raw);
if (!string.IsNullOrWhiteSpace(json))
{
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var switched = root.TryGetProperty("switched", out var switchedProp) && switchedProp.GetBoolean();
var accountFilled = root.TryGetProperty("accountFilled", out var accountProp) && accountProp.GetBoolean();
var passwordFilled = root.TryGetProperty("passwordFilled", out var passwordProp) && passwordProp.GetBoolean();
var clicked = root.TryGetProperty("clicked", out var clickedProp) && clickedProp.GetBoolean();
var hasAccountInput = root.TryGetProperty("hasAccountInput", out var hasAccountProp) && hasAccountProp.GetBoolean();
var hasPasswordInput = root.TryGetProperty("hasPasswordInput", out var hasPasswordProp) && hasPasswordProp.GetBoolean();
switchedToPasswordLogin |= switched;
partialFilled |= accountFilled || passwordFilled;
if (clicked && accountFilled && passwordFilled)
{
submitted = true;
}
if ((switched || hasAccountInput || hasPasswordInput) && attempt < 10)
{
_statusLabel.Text = $"已识别支付宝登录页,正在自动切换账密登录并填充信息(第 {attempt}/10 次尝试)……";
}
}
if (submitted)
{
await Task.Delay(1200);
break;
}
await Task.Delay(800);
}
_autoFillAttempted = true;
if (submitted)
{
_statusLabel.Text = "已自动点击账密登录、填充账号密码并提交,若出现验证请手动完成。";
StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs
{
StatusCode = "AutoFillSubmitted",
Message = "支付宝登录页已自动切换账密登录、填充账号密码并提交登录。"
});
}
else if (partialFilled || switchedToPasswordLogin)
{
_statusLabel.Text = "已尝试切换账密登录并部分填充,请确认页面后手动继续。";
StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs
{
StatusCode = "AutoFillPartial",
Message = "支付宝登录页已尝试切换账密登录并部分自动填充,请人工确认并继续。"
});
}
else
{
_statusLabel.Text = "当前页面未识别到账密登录表单,请手动切换并登录。";
StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs
{
StatusCode = "AutoFillSkipped",
Message = "当前支付宝页面未识别到密码登录表单,已切换为人工登录模式。"
});
}
}
catch (Exception ex)
{
StatusChanged?.Invoke(this, new AlipayStatusChangedEventArgs
{
StatusCode = "Warn",
Message = $"自动填充支付宝登录信息失败,已切换为人工登录:{ex.Message}",
Exception = ex
});
}
}
private static string ExtractWebViewJsonPayload(string? scriptResult)
{
if (string.IsNullOrWhiteSpace(scriptResult))
{
return string.Empty;
}
try
{
using var doc = JsonDocument.Parse(scriptResult);
return doc.RootElement.ValueKind switch
{
JsonValueKind.String => doc.RootElement.GetString() ?? string.Empty,
JsonValueKind.Object => doc.RootElement.GetRawText(),
JsonValueKind.Array => doc.RootElement.GetRawText(),
JsonValueKind.True => "true",
JsonValueKind.False => "false",
JsonValueKind.Number => doc.RootElement.ToString(),
_ => string.Empty
};
}
catch
{
return scriptResult.Trim();
}
}
private async Task<bool> TryNavigateToBillPageBeforeExtractAsync(string currentUrl) private async Task<bool> TryNavigateToBillPageBeforeExtractAsync(string currentUrl)
{ {
if (_webView.CoreWebView2 == null || _navigatedToBillPage) if (_webView.CoreWebView2 == null || _navigatedToBillPage)

283
Form1.Designer.cs generated
View File

@ -40,6 +40,7 @@ namespace Vmianqian
homeSummaryCard = new System.Windows.Forms.Panel(); homeSummaryCard = new System.Windows.Forms.Panel();
lblSummaryTitle = new AntdUI.Label(); lblSummaryTitle = new AntdUI.Label();
lblSummaryDesc = new AntdUI.Label(); lblSummaryDesc = new AntdUI.Label();
btnCheckUpdate = new AntdUI.Button();
homeConfigCard = new System.Windows.Forms.Panel(); homeConfigCard = new System.Windows.Forms.Panel();
btnSaveConfig = new AntdUI.Button(); btnSaveConfig = new AntdUI.Button();
btnHeartbeatCheck = new AntdUI.Button(); btnHeartbeatCheck = new AntdUI.Button();
@ -50,7 +51,11 @@ namespace Vmianqian
lblApiKeyTitle = new AntdUI.Label(); lblApiKeyTitle = new AntdUI.Label();
txtApiKey = new Input(); txtApiKey = new Input();
homeMemberCard = new System.Windows.Forms.Panel(); homeMemberCard = new System.Windows.Forms.Panel();
lblMemberPlaceholder = new AntdUI.Label(); btnActivate = new AntdUI.Button();
txtActivationCode = new Input();
lblActivationCodeTitle = new AntdUI.Label();
btnCopyDeviceCode = new AntdUI.Button();
label3 = new AntdUI.Label();
homeLogCard = new System.Windows.Forms.Panel(); homeLogCard = new System.Windows.Forms.Panel();
btnClearLog = new AntdUI.Button(); btnClearLog = new AntdUI.Button();
txtLog = new TextBox(); txtLog = new TextBox();
@ -107,6 +112,14 @@ namespace Vmianqian
txtNotifyEmail = new Input(); txtNotifyEmail = new Input();
lblEmailAuthCodeTitle = new AntdUI.Label(); lblEmailAuthCodeTitle = new AntdUI.Label();
txtEmailAuthCode = new Input(); txtEmailAuthCode = new Input();
settingsAlipayCard = new System.Windows.Forms.Panel();
btnAlipayAccountSave = new AntdUI.Button();
chkAlipayAutoFill = new Switch();
lblAlipayAutoFillTitle = new AntdUI.Label();
lblAlipayAccountTitle = new AntdUI.Label();
txtAlipayAccount = new Input();
lblAlipayPasswordTitle = new AntdUI.Label();
txtAlipayPassword = new Input();
titlebar = new PageHeader(); titlebar = new PageHeader();
lblTopNotice = new AntdUI.Label(); lblTopNotice = new AntdUI.Label();
lblAlipayStatusValue = new AntdUI.Label(); lblAlipayStatusValue = new AntdUI.Label();
@ -116,10 +129,10 @@ namespace Vmianqian
buttonCollapse = new AntdUI.Button(); buttonCollapse = new AntdUI.Button();
menu = new AntdUI.Menu(); menu = new AntdUI.Menu();
contentHost = new System.Windows.Forms.Panel(); contentHost = new System.Windows.Forms.Panel();
pageAlipay = new System.Windows.Forms.Panel();
pageWechat = new System.Windows.Forms.Panel();
pageHome = new System.Windows.Forms.Panel(); pageHome = new System.Windows.Forms.Panel();
pageSettings = new System.Windows.Forms.Panel(); pageSettings = new System.Windows.Forms.Panel();
pageAlipay = new System.Windows.Forms.Panel();
pageWechat = new System.Windows.Forms.Panel();
homeSummaryCard.SuspendLayout(); homeSummaryCard.SuspendLayout();
homeConfigCard.SuspendLayout(); homeConfigCard.SuspendLayout();
homeMemberCard.SuspendLayout(); homeMemberCard.SuspendLayout();
@ -134,13 +147,14 @@ namespace Vmianqian
alipayLogCard.SuspendLayout(); alipayLogCard.SuspendLayout();
((System.ComponentModel.ISupportInitialize)gridAlipayLogs).BeginInit(); ((System.ComponentModel.ISupportInitialize)gridAlipayLogs).BeginInit();
settingsEmailCard.SuspendLayout(); settingsEmailCard.SuspendLayout();
settingsAlipayCard.SuspendLayout();
titlebar.SuspendLayout(); titlebar.SuspendLayout();
bottomBar.SuspendLayout(); bottomBar.SuspendLayout();
contentHost.SuspendLayout(); contentHost.SuspendLayout();
pageAlipay.SuspendLayout();
pageWechat.SuspendLayout();
pageHome.SuspendLayout(); pageHome.SuspendLayout();
pageSettings.SuspendLayout(); pageSettings.SuspendLayout();
pageAlipay.SuspendLayout();
pageWechat.SuspendLayout();
SuspendLayout(); SuspendLayout();
// //
// homeSummaryCard // homeSummaryCard
@ -148,6 +162,7 @@ namespace Vmianqian
homeSummaryCard.BackColor = Color.White; homeSummaryCard.BackColor = Color.White;
homeSummaryCard.Controls.Add(lblSummaryTitle); homeSummaryCard.Controls.Add(lblSummaryTitle);
homeSummaryCard.Controls.Add(lblSummaryDesc); homeSummaryCard.Controls.Add(lblSummaryDesc);
homeSummaryCard.Controls.Add(btnCheckUpdate);
homeSummaryCard.Location = new Point(20, 23); homeSummaryCard.Location = new Point(20, 23);
homeSummaryCard.Name = "homeSummaryCard"; homeSummaryCard.Name = "homeSummaryCard";
homeSummaryCard.Size = new Size(960, 100); homeSummaryCard.Size = new Size(960, 100);
@ -171,7 +186,20 @@ namespace Vmianqian
lblSummaryDesc.Name = "lblSummaryDesc"; lblSummaryDesc.Name = "lblSummaryDesc";
lblSummaryDesc.Size = new Size(899, 32); lblSummaryDesc.Size = new Size(899, 32);
lblSummaryDesc.TabIndex = 1; lblSummaryDesc.TabIndex = 1;
lblSummaryDesc.Text = "当前阶段先打通“PC端本地监听 -> V免签服务端回调 -> 心跳检测”链路,整体布局按 AntdUI Demo 风格复刻。"; lblSummaryDesc.Text = "这是v0.0.2版本的软件";
//
// btnCheckUpdate
//
btnCheckUpdate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnCheckUpdate.BorderWidth = 2F;
btnCheckUpdate.Ghost = true;
btnCheckUpdate.Location = new Point(844, 24);
btnCheckUpdate.Name = "btnCheckUpdate";
btnCheckUpdate.Size = new Size(92, 36);
btnCheckUpdate.TabIndex = 2;
btnCheckUpdate.Text = "检查更新";
btnCheckUpdate.Type = TTypeMini.Primary;
btnCheckUpdate.Click += btnCheckUpdate_Click;
// //
// homeConfigCard // homeConfigCard
// //
@ -265,21 +293,64 @@ namespace Vmianqian
// homeMemberCard // homeMemberCard
// //
homeMemberCard.BackColor = Color.White; homeMemberCard.BackColor = Color.White;
homeMemberCard.Controls.Add(lblMemberPlaceholder); homeMemberCard.Controls.Add(btnActivate);
homeMemberCard.Controls.Add(txtActivationCode);
homeMemberCard.Controls.Add(lblActivationCodeTitle);
homeMemberCard.Controls.Add(btnCopyDeviceCode);
homeMemberCard.Controls.Add(label3);
homeMemberCard.Location = new Point(510, 141); homeMemberCard.Location = new Point(510, 141);
homeMemberCard.Name = "homeMemberCard"; homeMemberCard.Name = "homeMemberCard";
homeMemberCard.Size = new Size(470, 280); homeMemberCard.Size = new Size(470, 280);
homeMemberCard.TabIndex = 1; homeMemberCard.TabIndex = 1;
homeMemberCard.Tag = "home-member"; homeMemberCard.Tag = "home-member";
// //
// lblMemberPlaceholder // btnActivate
// //
lblMemberPlaceholder.ForeColor = Color.DimGray; btnActivate.Location = new Point(358, 64);
lblMemberPlaceholder.Location = new Point(24, 20); btnActivate.Name = "btnActivate";
lblMemberPlaceholder.Name = "lblMemberPlaceholder"; btnActivate.Size = new Size(92, 40);
lblMemberPlaceholder.Size = new Size(429, 52); btnActivate.TabIndex = 0;
lblMemberPlaceholder.TabIndex = 0; btnActivate.Text = "激活";
lblMemberPlaceholder.Text = "预留区域:后续用于会员登录、设备绑定、解绑与状态展示。"; btnActivate.Type = TTypeMini.Primary;
btnActivate.Click += btnActivate_Click;
//
// txtActivationCode
//
txtActivationCode.Location = new Point(74, 64);
txtActivationCode.Name = "txtActivationCode";
txtActivationCode.PlaceholderText = "请输入激活码";
txtActivationCode.Size = new Size(276, 40);
txtActivationCode.TabIndex = 1;
//
// lblActivationCodeTitle
//
lblActivationCodeTitle.AutoSizeMode = TAutoSize.Auto;
lblActivationCodeTitle.Location = new Point(21, 76);
lblActivationCodeTitle.Name = "lblActivationCodeTitle";
lblActivationCodeTitle.Size = new Size(36, 16);
lblActivationCodeTitle.TabIndex = 2;
lblActivationCodeTitle.Text = "激活码";
//
// btnCopyDeviceCode
//
btnCopyDeviceCode.BorderWidth = 2F;
btnCopyDeviceCode.Ghost = true;
btnCopyDeviceCode.Location = new Point(358, 18);
btnCopyDeviceCode.Name = "btnCopyDeviceCode";
btnCopyDeviceCode.Size = new Size(92, 36);
btnCopyDeviceCode.TabIndex = 3;
btnCopyDeviceCode.Text = "复制";
btnCopyDeviceCode.Type = TTypeMini.Primary;
btnCopyDeviceCode.Click += btnCopyDeviceCode_Click;
//
// label3
//
label3.ForeColor = Color.FromArgb(38, 38, 38);
label3.Location = new Point(21, 20);
label3.Name = "label3";
label3.Size = new Size(329, 36);
label3.TabIndex = 4;
label3.Text = "设备号:读取中...";
// //
// homeLogCard // homeLogCard
// //
@ -872,6 +943,83 @@ namespace Vmianqian
txtEmailAuthCode.Size = new Size(250, 55); txtEmailAuthCode.Size = new Size(250, 55);
txtEmailAuthCode.TabIndex = 11; txtEmailAuthCode.TabIndex = 11;
// //
// settingsAlipayCard
//
settingsAlipayCard.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
settingsAlipayCard.BackColor = Color.White;
settingsAlipayCard.Controls.Add(btnAlipayAccountSave);
settingsAlipayCard.Controls.Add(chkAlipayAutoFill);
settingsAlipayCard.Controls.Add(lblAlipayAutoFillTitle);
settingsAlipayCard.Controls.Add(lblAlipayAccountTitle);
settingsAlipayCard.Controls.Add(txtAlipayAccount);
settingsAlipayCard.Controls.Add(lblAlipayPasswordTitle);
settingsAlipayCard.Controls.Add(txtAlipayPassword);
settingsAlipayCard.Location = new Point(624, 23);
settingsAlipayCard.Name = "settingsAlipayCard";
settingsAlipayCard.Size = new Size(361, 395);
settingsAlipayCard.TabIndex = 0;
settingsAlipayCard.Tag = "settings-listen";
//
// btnAlipayAccountSave
//
btnAlipayAccountSave.Location = new Point(24, 325);
btnAlipayAccountSave.Name = "btnAlipayAccountSave";
btnAlipayAccountSave.Size = new Size(120, 42);
btnAlipayAccountSave.TabIndex = 0;
btnAlipayAccountSave.Text = "保存支付宝配置";
btnAlipayAccountSave.Type = TTypeMini.Primary;
btnAlipayAccountSave.Click += btnAlipayAccountSave_Click;
//
// chkAlipayAutoFill
//
chkAlipayAutoFill.Location = new Point(24, 274);
chkAlipayAutoFill.Name = "chkAlipayAutoFill";
chkAlipayAutoFill.Size = new Size(62, 32);
chkAlipayAutoFill.TabIndex = 1;
//
// lblAlipayAutoFillTitle
//
lblAlipayAutoFillTitle.AutoSizeMode = TAutoSize.Auto;
lblAlipayAutoFillTitle.Location = new Point(95, 280);
lblAlipayAutoFillTitle.Name = "lblAlipayAutoFillTitle";
lblAlipayAutoFillTitle.Size = new Size(144, 16);
lblAlipayAutoFillTitle.TabIndex = 2;
lblAlipayAutoFillTitle.Text = "启用账号密码自动填充登录";
//
// lblAlipayAccountTitle
//
lblAlipayAccountTitle.AutoSizeMode = TAutoSize.Auto;
lblAlipayAccountTitle.Location = new Point(24, 21);
lblAlipayAccountTitle.Name = "lblAlipayAccountTitle";
lblAlipayAccountTitle.Size = new Size(60, 16);
lblAlipayAccountTitle.TabIndex = 3;
lblAlipayAccountTitle.Text = "支付宝账号";
//
// txtAlipayAccount
//
txtAlipayAccount.Location = new Point(24, 49);
txtAlipayAccount.Name = "txtAlipayAccount";
txtAlipayAccount.PlaceholderText = "请输入支付宝登录账号";
txtAlipayAccount.Size = new Size(250, 55);
txtAlipayAccount.TabIndex = 4;
//
// lblAlipayPasswordTitle
//
lblAlipayPasswordTitle.AutoSizeMode = TAutoSize.Auto;
lblAlipayPasswordTitle.Location = new Point(24, 116);
lblAlipayPasswordTitle.Name = "lblAlipayPasswordTitle";
lblAlipayPasswordTitle.Size = new Size(60, 16);
lblAlipayPasswordTitle.TabIndex = 5;
lblAlipayPasswordTitle.Text = "支付宝密码";
//
// txtAlipayPassword
//
txtAlipayPassword.Location = new Point(24, 144);
txtAlipayPassword.Name = "txtAlipayPassword";
txtAlipayPassword.PlaceholderText = "请输入支付宝登录密码";
txtAlipayPassword.Size = new Size(250, 55);
txtAlipayPassword.TabIndex = 6;
//
// titlebar // titlebar
// //
titlebar.Controls.Add(lblTopNotice); titlebar.Controls.Add(lblTopNotice);
@ -879,6 +1027,7 @@ namespace Vmianqian
titlebar.Controls.Add(lblWechatStatusValue); titlebar.Controls.Add(lblWechatStatusValue);
titlebar.DividerShow = true; titlebar.DividerShow = true;
titlebar.Dock = DockStyle.Top; titlebar.Dock = DockStyle.Top;
titlebar.Icon = Properties.Resources.logo;
titlebar.Location = new Point(0, 0); titlebar.Location = new Point(0, 0);
titlebar.Name = "titlebar"; titlebar.Name = "titlebar";
titlebar.ShowButton = true; titlebar.ShowButton = true;
@ -977,16 +1126,44 @@ namespace Vmianqian
// contentHost // contentHost
// //
contentHost.BackColor = Color.White; contentHost.BackColor = Color.White;
contentHost.Controls.Add(pageAlipay);
contentHost.Controls.Add(pageWechat);
contentHost.Controls.Add(pageHome); contentHost.Controls.Add(pageHome);
contentHost.Controls.Add(pageSettings); contentHost.Controls.Add(pageSettings);
contentHost.Controls.Add(pageAlipay);
contentHost.Controls.Add(pageWechat);
contentHost.Dock = DockStyle.Fill; contentHost.Dock = DockStyle.Fill;
contentHost.Location = new Point(192, 44); contentHost.Location = new Point(192, 44);
contentHost.Name = "contentHost"; contentHost.Name = "contentHost";
contentHost.Size = new Size(1008, 916); contentHost.Size = new Size(1008, 916);
contentHost.TabIndex = 0; contentHost.TabIndex = 0;
// //
// pageHome
//
pageHome.AutoScroll = true;
pageHome.BackColor = Color.FromArgb(245, 247, 250);
pageHome.Controls.Add(homeLogCard);
pageHome.Controls.Add(homeSummaryCard);
pageHome.Controls.Add(homeMemberCard);
pageHome.Controls.Add(homeConfigCard);
pageHome.Dock = DockStyle.Fill;
pageHome.Location = new Point(0, 0);
pageHome.Name = "pageHome";
pageHome.Padding = new Padding(20);
pageHome.Size = new Size(1008, 916);
pageHome.TabIndex = 3;
//
// pageSettings
//
pageSettings.AutoScroll = true;
pageSettings.BackColor = Color.FromArgb(245, 247, 250);
pageSettings.Controls.Add(settingsAlipayCard);
pageSettings.Controls.Add(settingsEmailCard);
pageSettings.Dock = DockStyle.Fill;
pageSettings.Location = new Point(0, 0);
pageSettings.Name = "pageSettings";
pageSettings.Padding = new Padding(20);
pageSettings.Size = new Size(1008, 916);
pageSettings.TabIndex = 0;
//
// pageAlipay // pageAlipay
// //
pageAlipay.AutoScroll = true; pageAlipay.AutoScroll = true;
@ -1012,33 +1189,6 @@ namespace Vmianqian
pageWechat.Size = new Size(1008, 916); pageWechat.Size = new Size(1008, 916);
pageWechat.TabIndex = 2; pageWechat.TabIndex = 2;
// //
// pageHome
//
pageHome.AutoScroll = true;
pageHome.BackColor = Color.FromArgb(245, 247, 250);
pageHome.Controls.Add(homeLogCard);
pageHome.Controls.Add(homeSummaryCard);
pageHome.Controls.Add(homeMemberCard);
pageHome.Controls.Add(homeConfigCard);
pageHome.Dock = DockStyle.Fill;
pageHome.Location = new Point(0, 0);
pageHome.Name = "pageHome";
pageHome.Padding = new Padding(20);
pageHome.Size = new Size(1008, 916);
pageHome.TabIndex = 3;
//
// pageSettings
//
pageSettings.AutoScroll = true;
pageSettings.BackColor = Color.FromArgb(245, 247, 250);
pageSettings.Controls.Add(settingsEmailCard);
pageSettings.Dock = DockStyle.Fill;
pageSettings.Location = new Point(0, 0);
pageSettings.Name = "pageSettings";
pageSettings.Padding = new Padding(20);
pageSettings.Size = new Size(1008, 916);
pageSettings.TabIndex = 0;
//
// Form1 // Form1
// //
AutoScaleDimensions = new SizeF(7F, 17F); AutoScaleDimensions = new SizeF(7F, 17F);
@ -1061,6 +1211,7 @@ namespace Vmianqian
homeConfigCard.ResumeLayout(false); homeConfigCard.ResumeLayout(false);
homeConfigCard.PerformLayout(); homeConfigCard.PerformLayout();
homeMemberCard.ResumeLayout(false); homeMemberCard.ResumeLayout(false);
homeMemberCard.PerformLayout();
homeLogCard.ResumeLayout(false); homeLogCard.ResumeLayout(false);
homeLogCard.PerformLayout(); homeLogCard.PerformLayout();
wechatHookCard.ResumeLayout(false); wechatHookCard.ResumeLayout(false);
@ -1076,17 +1227,20 @@ namespace Vmianqian
((System.ComponentModel.ISupportInitialize)gridAlipayLogs).EndInit(); ((System.ComponentModel.ISupportInitialize)gridAlipayLogs).EndInit();
settingsEmailCard.ResumeLayout(false); settingsEmailCard.ResumeLayout(false);
settingsEmailCard.PerformLayout(); settingsEmailCard.PerformLayout();
settingsAlipayCard.ResumeLayout(false);
settingsAlipayCard.PerformLayout();
titlebar.ResumeLayout(false); titlebar.ResumeLayout(false);
bottomBar.ResumeLayout(false); bottomBar.ResumeLayout(false);
contentHost.ResumeLayout(false); contentHost.ResumeLayout(false);
pageAlipay.ResumeLayout(false);
pageWechat.ResumeLayout(false);
pageHome.ResumeLayout(false); pageHome.ResumeLayout(false);
pageSettings.ResumeLayout(false); pageSettings.ResumeLayout(false);
pageAlipay.ResumeLayout(false);
pageWechat.ResumeLayout(false);
ResumeLayout(false); ResumeLayout(false);
} }
private WinPanel homeSummaryCard = null!; private WinPanel homeSummaryCard = null!;
private AntdUI.Button btnCheckUpdate = null!;
private WinPanel homeConfigCard = null!; private WinPanel homeConfigCard = null!;
private WinPanel homeMemberCard = null!; private WinPanel homeMemberCard = null!;
private WinPanel homeLogCard = null!; private WinPanel homeLogCard = null!;
@ -1103,20 +1257,33 @@ namespace Vmianqian
private NumericUpDown numAlipayInterval = null!; private NumericUpDown numAlipayInterval = null!;
private AntdUI.Label lblAlipayDesc = null!; private AntdUI.Label lblAlipayDesc = null!;
private WinPanel alipayLogCard = null!; private WinPanel alipayLogCard = null!;
private WinPanel settingsAlipayCard = null!;
private AntdUI.Button btnAlipayAccountSave = null!;
private Switch chkAlipayAutoFill = null!;
private AntdUI.Label lblAlipayAutoFillTitle = null!;
private AntdUI.Label lblAlipayAccountTitle = null!;
private Input txtAlipayAccount = null!;
private AntdUI.Label lblAlipayPasswordTitle = null!;
private Input txtAlipayPassword = null!;
private WinPanel settingsEmailCard = null!; private WinPanel settingsEmailCard = null!;
private AntdUI.Label label1 = null!; private AntdUI.Label label1 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; private AntdUI.Button btnActivate = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn2; private Input txtActivationCode = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn3; private AntdUI.Label lblActivationCodeTitle = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn4; private AntdUI.Button btnCopyDeviceCode = null!;
private DataGridViewTextBoxColumn ; private AntdUI.Label label3 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn5; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn1 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn6; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn2 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn8; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn3 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn9; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn4 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn7; private DataGridViewTextBoxColumn = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn10; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn5 = null!;
private DataGridViewTextBoxColumn ; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn6 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn11; private DataGridViewTextBoxColumn dataGridViewTextBoxColumn8 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn9 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn7 = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn10 = null!;
private DataGridViewTextBoxColumn = null!;
private DataGridViewTextBoxColumn dataGridViewTextBoxColumn11 = null!;
} }
} }

820
Form1.cs
View File

@ -5,8 +5,11 @@ using MimeKit;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Management;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -38,8 +41,13 @@ namespace Vmianqian
private readonly string _pendingOrdersFilePath = Path.Combine(AppContext.BaseDirectory, "pending-orders.client.json"); private readonly string _pendingOrdersFilePath = Path.Combine(AppContext.BaseDirectory, "pending-orders.client.json");
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true, PropertyNameCaseInsensitive = true }; private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true, PropertyNameCaseInsensitive = true };
private readonly HttpClient _httpClient = new(); private readonly HttpClient _httpClient = new();
private readonly object _pendingOrdersFileLock = new();
private bool _isUpdating;
private readonly System.Windows.Forms.Timer _runtimeTimer = new(); private readonly System.Windows.Forms.Timer _runtimeTimer = new();
private readonly System.Windows.Forms.Timer _heartbeatTimer = new(); private readonly System.Windows.Forms.Timer _heartbeatTimer = new();
private readonly string _deviceCode;
private const string UpgradeCheckApiBaseUrl = "https://api.yunzer.cn";
private const string UpgradeProductCode = "Vmianqian";
private DateTime _appStartTime = DateTime.Now; private DateTime _appStartTime = DateTime.Now;
private HttpListener? _httpListener; private HttpListener? _httpListener;
@ -63,6 +71,8 @@ namespace Vmianqian
private bool _heartbeatRequestInProgress; private bool _heartbeatRequestInProgress;
private int _wechatProtocolAuthFailCount; private int _wechatProtocolAuthFailCount;
private Vmianqian.AlipayMonitor? _alipayMonitor; private Vmianqian.AlipayMonitor? _alipayMonitor;
private Vmianqian.AlipayLoginForm? _alipayLoginForm;
private bool _alipayAutoRefreshingCookie;
private AntdUI.Label? _lblWechatSidHint; private AntdUI.Label? _lblWechatSidHint;
private AntdUI.PageHeader titlebar = null!; private AntdUI.PageHeader titlebar = null!;
@ -103,7 +113,6 @@ namespace Vmianqian
private AntdUI.Label lblWechatSidTitle = null!; private AntdUI.Label lblWechatSidTitle = null!;
private AntdUI.Label lblWechatFrequencyTitle = null!; private AntdUI.Label lblWechatFrequencyTitle = null!;
private AntdUI.Label lblHeartbeatDesc = null!; private AntdUI.Label lblHeartbeatDesc = null!;
private AntdUI.Label lblMemberPlaceholder = null!;
private AntdUI.Switch chkHeartbeatEnabled = null!; private AntdUI.Switch chkHeartbeatEnabled = null!;
private AntdUI.Button btnHeartbeatCheck = null!; private AntdUI.Button btnHeartbeatCheck = null!;
private AntdUI.Button btnClearLog = null!; private AntdUI.Button btnClearLog = null!;
@ -129,9 +138,12 @@ namespace Vmianqian
public Form1() public Form1()
{ {
_deviceCode = BuildDeviceCode();
InitializeComponent(); InitializeComponent();
UpdateTitlebarVersion();
InitializeDesignerLayout(); InitializeDesignerLayout();
InitializeWechatUi(); InitializeWechatUi();
UpdateDeviceCodeLabel();
// InitializeAlipayUi(); // InitializeAlipayUi();
ReloadMenuItems(); ReloadMenuItems();
SelectPage("home"); SelectPage("home");
@ -250,6 +262,12 @@ namespace Vmianqian
{ {
summaryCard.SuspendLayout(); summaryCard.SuspendLayout();
lblSummaryTitle.Location = new Point(28, 20); lblSummaryTitle.Location = new Point(28, 20);
if (btnCheckUpdate != null)
{
btnCheckUpdate.Size = new Size(92, 36);
btnCheckUpdate.Location = new Point(Math.Max(28, summaryCard.ClientSize.Width - btnCheckUpdate.Width - 24), 20);
btnCheckUpdate.BringToFront();
}
var summaryDescTop = lblSummaryTitle.Bottom + 8; var summaryDescTop = lblSummaryTitle.Bottom + 8;
var summaryDescWidth = Math.Max(220, pageAvailableWidth - 56); var summaryDescWidth = Math.Max(220, pageAvailableWidth - 56);
var summaryDescPreferred = TextRenderer.MeasureText( var summaryDescPreferred = TextRenderer.MeasureText(
@ -278,7 +296,7 @@ namespace Vmianqian
var leftWidth = rowWidth / 2; var leftWidth = rowWidth / 2;
var rightWidth = rowWidth - leftWidth; var rightWidth = rowWidth - leftWidth;
var configHeight = 390; var configHeight = 390;
var memberHeight = 128; var memberHeight = 170;
configCard.Bounds = new Rectangle(margin, top, leftWidth, configHeight); configCard.Bounds = new Rectangle(margin, top, leftWidth, configHeight);
memberCard.Bounds = new Rectangle(margin + leftWidth + gap, top, rightWidth, memberHeight); memberCard.Bounds = new Rectangle(margin + leftWidth + gap, top, rightWidth, memberHeight);
@ -305,7 +323,24 @@ namespace Vmianqian
const int heartbeatTop = 310; const int heartbeatTop = 310;
chkHeartbeatEnabled.Location = new Point(contentLeft, heartbeatTop - 4); chkHeartbeatEnabled.Location = new Point(contentLeft, heartbeatTop - 4);
lblHeartbeatDesc.Location = new Point(chkHeartbeatEnabled.Right + 4, heartbeatTop + 1); lblHeartbeatDesc.Location = new Point(chkHeartbeatEnabled.Right + 4, heartbeatTop + 1);
lblMemberPlaceholder.Size = new Size(Math.Max(220, memberCard.ClientSize.Width - 48), 52);
const int memberLeft = 21;
const int memberRight = 20;
const int memberTop = 20;
const int memberButtonGap = 8;
var memberContentWidth = Math.Max(220, memberCard.ClientSize.Width - memberLeft - memberRight);
var memberActionWidth = 92;
var memberInputWidth = Math.Max(140, memberContentWidth - memberActionWidth - memberButtonGap);
label3.Location = new Point(memberLeft, memberTop);
label3.Size = new Size(memberInputWidth, 36);
btnCopyDeviceCode.Location = new Point(memberCard.ClientSize.Width - memberRight - memberActionWidth, 18);
btnCopyDeviceCode.Size = new Size(memberActionWidth, 36);
lblActivationCodeTitle.Location = new Point(memberLeft, 62);
txtActivationCode.Location = new Point(memberLeft, 84);
txtActivationCode.Size = new Size(memberInputWidth, 40);
btnActivate.Location = new Point(memberCard.ClientSize.Width - memberRight - memberActionWidth, 84);
btnActivate.Size = new Size(memberActionWidth, 40);
var rowBottom = Math.Max(configCard.Bottom, memberCard.Bottom); var rowBottom = Math.Max(configCard.Bottom, memberCard.Bottom);
var logCardTop = rowBottom + margin; var logCardTop = rowBottom + margin;
@ -607,6 +642,7 @@ namespace Vmianqian
ApplyHeartbeatSetting(); ApplyHeartbeatSetting();
Log("程序已启动。"); Log("程序已启动。");
_ = CheckForUpdatesAsync(silentWhenUpToDate: true, showNoUpdateMessage: false);
} }
private void Form1_FormClosing(object? sender, FormClosingEventArgs e) private void Form1_FormClosing(object? sender, FormClosingEventArgs e)
@ -618,6 +654,20 @@ namespace Vmianqian
StopWechatSidCapture(); StopWechatSidCapture();
_alipayMonitor?.Stop(); _alipayMonitor?.Stop();
_alipayMonitor?.Dispose(); _alipayMonitor?.Dispose();
if (_alipayLoginForm != null)
{
try
{
_alipayLoginForm.Close();
_alipayLoginForm.Dispose();
}
catch
{
}
_alipayLoginForm = null;
}
_runtimeTimer.Stop(); _runtimeTimer.Stop();
_runtimeTimer.Dispose(); _runtimeTimer.Dispose();
_heartbeatTimer.Dispose(); _heartbeatTimer.Dispose();
@ -738,27 +788,74 @@ namespace Vmianqian
private void LoadPendingOrders() private void LoadPendingOrders()
{ {
if (!File.Exists(_pendingOrdersFilePath)) lock (_pendingOrdersFileLock)
{ {
_pendingOrders = new List<PendingOrderRecord>(); if (!File.Exists(_pendingOrdersFilePath))
return; {
} _pendingOrders = new List<PendingOrderRecord>();
return;
}
try try
{ {
var json = File.ReadAllText(_pendingOrdersFilePath, Encoding.UTF8); using var stream = new FileStream(_pendingOrdersFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_pendingOrders = JsonSerializer.Deserialize<List<PendingOrderRecord>>(json, _jsonOptions) ?? new List<PendingOrderRecord>(); using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
} var json = reader.ReadToEnd();
catch _pendingOrders = JsonSerializer.Deserialize<List<PendingOrderRecord>>(json, _jsonOptions) ?? new List<PendingOrderRecord>();
{ }
_pendingOrders = new List<PendingOrderRecord>(); catch
{
_pendingOrders = new List<PendingOrderRecord>();
}
} }
} }
private void SavePendingOrders() private void SavePendingOrders()
{ {
var json = JsonSerializer.Serialize(_pendingOrders, _jsonOptions); lock (_pendingOrdersFileLock)
File.WriteAllText(_pendingOrdersFilePath, json, Encoding.UTF8); {
var json = JsonSerializer.Serialize(_pendingOrders, _jsonOptions);
var tempFilePath = _pendingOrdersFilePath + ".tmp";
Exception? lastError = null;
for (var attempt = 1; attempt <= 5; attempt++)
{
try
{
File.WriteAllText(tempFilePath, json, Encoding.UTF8);
if (File.Exists(_pendingOrdersFilePath))
{
File.Copy(tempFilePath, _pendingOrdersFilePath, overwrite: true);
File.Delete(tempFilePath);
}
else
{
File.Move(tempFilePath, _pendingOrdersFilePath);
}
return;
}
catch (Exception ex)
{
lastError = ex;
try
{
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
}
}
catch
{
}
Thread.Sleep(80 * attempt);
}
}
throw new IOException($"保存待支付订单文件失败:{lastError?.Message}", lastError);
}
} }
private void BindConfigToUi() private void BindConfigToUi()
@ -770,6 +867,9 @@ namespace Vmianqian
txtSmtpPort.Text = _config.SmtpPort.ToString(); txtSmtpPort.Text = _config.SmtpPort.ToString();
txtNotifyEmail.Text = _config.NotifyEmail; txtNotifyEmail.Text = _config.NotifyEmail;
txtEmailAuthCode.Text = _config.EmailAuthCode; txtEmailAuthCode.Text = _config.EmailAuthCode;
txtAlipayAccount.Text = _config.AlipayAccount;
txtAlipayPassword.Text = _config.AlipayPassword;
chkAlipayAutoFill.Checked = _config.AlipayEnableAutoFill;
txtWechatPath.Text = _config.WechatPath; txtWechatPath.Text = _config.WechatPath;
txtWechatId.Text = _config.WechatSid; txtWechatId.Text = _config.WechatSid;
var savedAlipayUrl = string.IsNullOrWhiteSpace(_config.AlipayBillApiUrl) var savedAlipayUrl = string.IsNullOrWhiteSpace(_config.AlipayBillApiUrl)
@ -796,6 +896,9 @@ namespace Vmianqian
_config.SmtpPort = ParseSmtpPort(txtSmtpPort.Text); _config.SmtpPort = ParseSmtpPort(txtSmtpPort.Text);
_config.NotifyEmail = txtNotifyEmail.Text.Trim(); _config.NotifyEmail = txtNotifyEmail.Text.Trim();
_config.EmailAuthCode = txtEmailAuthCode.Text.Trim(); _config.EmailAuthCode = txtEmailAuthCode.Text.Trim();
_config.AlipayAccount = txtAlipayAccount.Text.Trim();
_config.AlipayPassword = txtAlipayPassword.Text;
_config.AlipayEnableAutoFill = chkAlipayAutoFill.Checked;
_config.WechatPath = txtWechatPath.Text.Trim(); _config.WechatPath = txtWechatPath.Text.Trim();
_config.WechatSid = txtWechatId.Text.Trim(); _config.WechatSid = txtWechatId.Text.Trim();
_config.AlipayPath = txtAliPath.Text.Trim(); _config.AlipayPath = txtAliPath.Text.Trim();
@ -2101,6 +2204,22 @@ namespace Vmianqian
} }
} }
private void btnAlipayAccountSave_Click(object? sender, EventArgs e)
{
try
{
SaveUiToConfig();
SaveConfig();
Log("支付宝账号登录配置已保存到本地缓存文件。");
MessageBox.Show("支付宝配置已保存。", "保存成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
Log($"保存支付宝配置失败:{ex.Message}");
MessageBox.Show(ex.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnAlipayLogin_Click(object? sender, EventArgs e) private void btnAlipayLogin_Click(object? sender, EventArgs e)
{ {
try try
@ -2119,43 +2238,7 @@ namespace Vmianqian
SaveUiToConfig(); SaveUiToConfig();
SaveConfig(); SaveConfig();
OpenAlipayLoginWindow(autoRefresh: false);
using var loginForm = new Vmianqian.AlipayLoginForm(BuildAlipayOptions());
loginForm.LoginSucceeded += (_, args) =>
{
EnsureAlipayMonitorCreated();
_alipayMonitor!.SetCookies(args.CookieContainer);
_alipayMonitor.SetCtoken(args.CToken);
if (!string.IsNullOrWhiteSpace(args.CToken))
{
var pureApiUrl = txtAliPath.Text.Trim();
if (Uri.TryCreate(pureApiUrl, UriKind.Absolute, out var apiUri))
{
pureApiUrl = $"{apiUri.Scheme}://{apiUri.Host}{apiUri.AbsolutePath}";
}
txtAliPath.Text = pureApiUrl;
}
Log($"支付宝登录成功Cookie 已提取ctoken={args.CToken},当前地址:{args.CurrentUrl}");
lblAlipayStatusValue.Text = "支付宝: 已登录";
lblAlipayStatusValue.ForeColor = Color.DarkOrange;
// 登录成功后自动开始监听,不再需要用户手动再点一次。
_alipayMonitor.Start();
btnAlipayLogin.Text = "停止监听";
btnAlipayLogin.Type = TTypeMini.Error;
loginForm.BeginInvoke(() => loginForm.Close());
};
loginForm.StatusChanged += (_, args) =>
{
Log($"支付宝登录窗口:{args.Message}");
};
loginForm.ShowDialog(this);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -2215,7 +2298,10 @@ namespace Vmianqian
JsonpCallback = "callback", JsonpCallback = "callback",
MinPollSeconds = 15, MinPollSeconds = 15,
MaxPollSeconds = Math.Max(15, Math.Min(35, (int)numAlipayInterval.Value)), MaxPollSeconds = Math.Max(15, Math.Min(35, (int)numAlipayInterval.Value)),
PageSize = 10 PageSize = 10,
Account = txtAlipayAccount.Text.Trim(),
Password = txtAlipayPassword.Text,
EnableAutoFill = chkAlipayAutoFill.Checked
}; };
} }
@ -2295,21 +2381,154 @@ namespace Vmianqian
lblAlipayStatusValue.ForeColor = Color.DarkOrange; lblAlipayStatusValue.ForeColor = Color.DarkOrange;
break; break;
case "CookieExpired": case "CookieExpired":
lblAlipayStatusValue.Text = "支付宝: Cookie失效"; lblAlipayStatusValue.Text = "支付宝: 自动续登中";
lblAlipayStatusValue.ForeColor = Color.Red; lblAlipayStatusValue.ForeColor = Color.DarkOrange;
btnAlipayLogin.Text = "扫码登录"; btnAlipayLogin.Text = "自动续登中";
btnAlipayLogin.Type = TTypeMini.Primary; btnAlipayLogin.Type = TTypeMini.Warn;
MessageBox.Show("支付宝 Cookie 失效,请重新扫码登录。", "支付宝监听提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); BeginAlipayAutoRefreshCookie();
break; break;
case "Stopped": case "Stopped":
lblAlipayStatusValue.Text = "支付宝: 离线"; if (_alipayAutoRefreshingCookie)
lblAlipayStatusValue.ForeColor = Color.Red; {
btnAlipayLogin.Text = "扫码登录"; lblAlipayStatusValue.Text = "支付宝: 自动续登中";
btnAlipayLogin.Type = TTypeMini.Primary; lblAlipayStatusValue.ForeColor = Color.DarkOrange;
btnAlipayLogin.Text = "自动续登中";
btnAlipayLogin.Type = TTypeMini.Warn;
}
else
{
lblAlipayStatusValue.Text = "支付宝: 离线";
lblAlipayStatusValue.ForeColor = Color.Red;
btnAlipayLogin.Text = "扫码登录";
btnAlipayLogin.Type = TTypeMini.Primary;
}
break; break;
} }
} }
private void BeginAlipayAutoRefreshCookie()
{
if (_alipayAutoRefreshingCookie)
{
return;
}
_alipayAutoRefreshingCookie = true;
try
{
SaveUiToConfig();
SaveConfig();
Log("支付宝 Cookie 已失效,正在自动打开登录窗口续 Cookie……");
OpenAlipayLoginWindow(autoRefresh: true);
}
catch (Exception ex)
{
_alipayAutoRefreshingCookie = false;
Log($"支付宝自动续 Cookie 失败:{ex.Message}");
lblAlipayStatusValue.Text = "支付宝: 续登失败";
lblAlipayStatusValue.ForeColor = Color.Red;
btnAlipayLogin.Text = "扫码登录";
btnAlipayLogin.Type = TTypeMini.Primary;
}
}
private void OpenAlipayLoginWindow(bool autoRefresh)
{
if (_alipayLoginForm != null && !_alipayLoginForm.IsDisposed)
{
try
{
_alipayLoginForm.BringToFront();
_alipayLoginForm.Focus();
}
catch
{
}
return;
}
var loginForm = new Vmianqian.AlipayLoginForm(BuildAlipayOptions());
_alipayLoginForm = loginForm;
loginForm.FormClosed += (_, _) =>
{
if (ReferenceEquals(_alipayLoginForm, loginForm))
{
_alipayLoginForm = null;
}
if (_alipayAutoRefreshingCookie)
{
_alipayAutoRefreshingCookie = false;
if (_alipayMonitor?.IsRunning != true)
{
lblAlipayStatusValue.Text = "支付宝: 离线";
lblAlipayStatusValue.ForeColor = Color.Red;
btnAlipayLogin.Text = "扫码登录";
btnAlipayLogin.Type = TTypeMini.Primary;
}
}
};
loginForm.LoginSucceeded += (_, args) =>
{
EnsureAlipayMonitorCreated();
_alipayMonitor!.SetCookies(args.CookieContainer);
_alipayMonitor.SetCtoken(args.CToken);
if (!string.IsNullOrWhiteSpace(args.CToken))
{
var pureApiUrl = txtAliPath.Text.Trim();
if (Uri.TryCreate(pureApiUrl, UriKind.Absolute, out var apiUri))
{
pureApiUrl = $"{apiUri.Scheme}://{apiUri.Host}{apiUri.AbsolutePath}";
}
txtAliPath.Text = pureApiUrl;
}
Log(autoRefresh
? $"支付宝自动续 Cookie 成功ctoken={args.CToken},当前地址:{args.CurrentUrl}"
: $"支付宝登录成功Cookie 已提取ctoken={args.CToken},当前地址:{args.CurrentUrl}");
lblAlipayStatusValue.Text = "支付宝: 已登录";
lblAlipayStatusValue.ForeColor = Color.DarkOrange;
_alipayAutoRefreshingCookie = false;
// 登录成功后自动开始监听,不再需要用户手动再点一次。
_alipayMonitor.Start();
btnAlipayLogin.Text = "停止监听";
btnAlipayLogin.Type = TTypeMini.Error;
try
{
loginForm.BeginInvoke(() => loginForm.Close());
}
catch
{
}
};
loginForm.StatusChanged += (_, args) =>
{
Log($"支付宝登录窗口:{args.Message}");
};
if (autoRefresh)
{
loginForm.Show(this);
}
else
{
loginForm.ShowDialog(this);
}
}
private async Task StartListenerAsync() private async Task StartListenerAsync()
{ {
StopListener(); StopListener();
@ -3090,6 +3309,451 @@ namespace Vmianqian
}; };
} }
/// <summary>
/// 将设备号显示到首页会员卡区域。
/// </summary>
private void UpdateDeviceCodeLabel()
{
if (label3 == null)
{
return;
}
label3.Text = $"设备号:{_deviceCode}";
}
/// <summary>
/// 复制设备号按钮点击事件。
/// 便于用户一键复制后发送给他人。
/// </summary>
private void btnCopyDeviceCode_Click(object? sender, EventArgs e)
{
try
{
if (string.IsNullOrWhiteSpace(_deviceCode))
{
MessageBox.Show("当前设备号为空,无法复制。", "复制失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
Clipboard.SetText(_deviceCode);
Log($"设备号已复制:{_deviceCode}");
MessageBox.Show("设备号已复制到剪贴板。", "复制成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
Log($"复制设备号失败:{ex.Message}");
MessageBox.Show($"复制失败:{ex.Message}", "复制失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnActivate_Click(object? sender, EventArgs e)
{
try
{
var activationCode = txtActivationCode?.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(activationCode))
{
MessageBox.Show("请输入激活码。", "激活提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
Log($"收到激活请求,激活码:{activationCode}");
MessageBox.Show("激活按钮已添加,后续可在此接入真实激活接口。", "激活", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
Log($"激活失败:{ex.Message}");
MessageBox.Show($"激活失败:{ex.Message}", "激活失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void btnCheckUpdate_Click(object? sender, EventArgs e)
{
await CheckForUpdatesAsync(silentWhenUpToDate: false, showNoUpdateMessage: true);
}
private async Task CheckForUpdatesAsync(bool silentWhenUpToDate, bool showNoUpdateMessage)
{
if (_isUpdating)
{
if (!silentWhenUpToDate)
{
MessageBox.Show("更新任务正在执行中,请稍候。", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return;
}
try
{
var currentVersion = GetCurrentVersion();
var requestUrl = BuildUpgradeCheckUrl(UpgradeProductCode, currentVersion);
var response = await _httpClient.GetAsync(requestUrl);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Log($"检查更新失败HTTP {(int)response.StatusCode} {response.StatusCode}");
if (!silentWhenUpToDate)
{
MessageBox.Show($"检查更新失败HTTP {(int)response.StatusCode} {response.StatusCode}", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
return;
}
var result = JsonSerializer.Deserialize<SoftwareUpgradeCheckResponse>(body, _jsonOptions);
if (result?.Data == null)
{
Log("检查更新失败:服务端响应无法解析。");
if (!silentWhenUpToDate)
{
MessageBox.Show("检查更新失败:服务端响应无法解析。", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
return;
}
var remoteVersion = (result.Data.LatestVersion ?? string.Empty).Trim();
var upToDate = result.Data.UpToDate;
if (!upToDate && IsRemoteVersionNewer(currentVersion, remoteVersion))
{
Log($"发现新版本:当前={currentVersion},最新={remoteVersion}");
var notes = string.IsNullOrWhiteSpace(result.Data.ReleaseNotes) ? "暂无更新说明" : result.Data.ReleaseNotes.Trim();
var downloadUrl = (result.Data.DownloadUrl ?? string.Empty).Trim();
var forceText = result.Data.ForceUpdate ? "是" : "否";
var promptMessage =
$"发现新版本:{remoteVersion}{Environment.NewLine}" +
$"当前版本:{currentVersion}{Environment.NewLine}" +
$"是否强更:{forceText}{Environment.NewLine}{Environment.NewLine}" +
$"更新说明:{Environment.NewLine}{notes}{Environment.NewLine}{Environment.NewLine}" +
(result.Data.ForceUpdate
? "当前版本为强制更新,点击“确定”后将立即开始增量更新。"
: "点击“是”开始增量更新,程序会在下载完成后自动退出并覆盖更新。");
var dialogResult = MessageBox.Show(
promptMessage,
"发现新版本",
result.Data.ForceUpdate ? MessageBoxButtons.OK : MessageBoxButtons.YesNo,
MessageBoxIcon.Information);
if ((result.Data.ForceUpdate && dialogResult == DialogResult.OK) ||
(!result.Data.ForceUpdate && dialogResult == DialogResult.Yes))
{
await StartIncrementalUpdateAsync(remoteVersion, downloadUrl, result.Data.ForceUpdate);
}
return;
}
Log($"已是最新版本:{currentVersion}");
if (showNoUpdateMessage)
{
MessageBox.Show($"当前已是最新版本:{currentVersion}", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
Log($"检查更新异常:{ex.Message}");
if (!silentWhenUpToDate)
{
MessageBox.Show($"检查更新失败:{ex.Message}", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private async Task StartIncrementalUpdateAsync(string remoteVersion, string downloadUrl, bool forceUpdate)
{
if (_isUpdating)
{
return;
}
if (string.IsNullOrWhiteSpace(downloadUrl))
{
throw new InvalidOperationException("服务端未提供更新包下载地址。");
}
_isUpdating = true;
if (btnCheckUpdate != null)
{
btnCheckUpdate.Loading = true;
btnCheckUpdate.Enabled = false;
btnCheckUpdate.Text = "更新中";
}
try
{
var tempRoot = Path.Combine(Path.GetTempPath(), "VmianqianUpdate", remoteVersion + "_" + DateTime.Now.ToString("yyyyMMddHHmmss"));
var packageFile = Path.Combine(tempRoot, "update-package.zip");
var extractRoot = Path.Combine(tempRoot, "package");
Directory.CreateDirectory(tempRoot);
Log($"开始下载增量更新包:{downloadUrl}");
await DownloadFileAsync(downloadUrl, packageFile);
Log($"增量更新包下载完成:{packageFile}");
try
{
ZipFile.ExtractToDirectory(packageFile, extractRoot, true);
}
catch (InvalidDataException)
{
throw new InvalidOperationException("更新包不是有效的 zip 压缩包,无法执行增量更新。");
}
var payloadRoot = ResolveUpdatePayloadRoot(extractRoot);
Log($"增量更新包已解压:{payloadRoot}");
var launcherPath = Application.ExecutablePath;
var currentProcessId = Environment.ProcessId;
var scriptPath = Path.Combine(tempRoot, "apply-update.cmd");
var updateLogPath = Path.Combine(tempRoot, "apply-update.log");
var appDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var deleteListPath = Path.Combine(payloadRoot, "delete-files.txt");
var deleteCommands = new StringBuilder();
if (File.Exists(deleteListPath))
{
foreach (var line in File.ReadAllLines(deleteListPath, Encoding.UTF8))
{
var relativePath = line.Trim();
if (string.IsNullOrWhiteSpace(relativePath))
{
continue;
}
if (relativePath.StartsWith("#", StringComparison.Ordinal))
{
continue;
}
var normalizedRelative = relativePath.Replace('/', '\\').TrimStart('\\');
var fullDeletePath = Path.Combine(appDirectory, normalizedRelative);
deleteCommands.AppendLine($"if exist \"{fullDeletePath}\" del /f /q \"{fullDeletePath}\" >> \"%LOG%\" 2>&1");
}
}
var script = $@"@echo off
chcp 65001 >nul
setlocal enableextensions
set ""APPDIR={appDirectory}""
set ""SRCDIR={payloadRoot}""
set ""EXE={launcherPath}""
set ""PID={currentProcessId}""
set ""LOG={updateLogPath}""
echo [%%date%% %%time%%] updater started > ""%LOG%""
echo APPDIR=%APPDIR% >> ""%LOG%""
echo SRCDIR=%SRCDIR% >> ""%LOG%""
echo EXE=%EXE% >> ""%LOG%""
echo PID=%PID% >> ""%LOG%""
taskkill /PID %PID% /T /F >> ""%LOG%"" 2>&1
:waitloop
tasklist /FI ""PID eq %PID%"" | find ""%PID%"" >nul
if not errorlevel 1 (
timeout /t 1 /nobreak >nul
goto waitloop
)
echo [%%date%% %%time%%] process stopped, start copy >> ""%LOG%""
robocopy ""%SRCDIR%"" ""%APPDIR%"" /E /R:3 /W:1 /NFL /NDL /NJH /NJS /NP >> ""%LOG%"" 2>&1
set ""RC=%ERRORLEVEL%""
echo robocopy exit code=%RC% >> ""%LOG%""
if %RC% GEQ 8 goto copyfailed
{deleteCommands}
echo [%%date%% %%time%%] copy success, start app >> ""%LOG%""
start """" ""%EXE%""
exit /b 0
:copyfailed
echo [%%date%% %%time%%] copy failed, robocopy exit code=%RC% >> ""%LOG%""
start notepad.exe ""%LOG%""
exit /b %RC%
";
File.WriteAllText(scriptPath, script, new UTF8Encoding(false));
Log($"增量更新准备完成,目标版本:{remoteVersion}");
var confirmText = forceUpdate
? "更新包已下载完成,当前版本要求强制更新。点击确定后程序将退出并自动完成覆盖更新。"
: "更新包已下载完成。点击确定后程序将退出并自动完成覆盖更新。";
MessageBox.Show(confirmText, "开始更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
Process.Start(new ProcessStartInfo
{
FileName = scriptPath,
UseShellExecute = true,
WorkingDirectory = tempRoot,
WindowStyle = ProcessWindowStyle.Normal
});
BeginInvoke(new Action(Close));
}
catch
{
_isUpdating = false;
if (btnCheckUpdate != null)
{
btnCheckUpdate.Loading = false;
btnCheckUpdate.Enabled = true;
btnCheckUpdate.Text = "检查更新";
}
throw;
}
}
private async Task DownloadFileAsync(string requestUrl, string targetFilePath)
{
using var response = await _httpClient.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)!);
await using var httpStream = await response.Content.ReadAsStreamAsync();
await using var fileStream = new FileStream(targetFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpStream.CopyToAsync(fileStream);
}
private static string ResolveUpdatePayloadRoot(string extractRoot)
{
var incrementalRoot = Path.Combine(extractRoot, "incremental");
if (Directory.Exists(incrementalRoot))
{
return incrementalRoot;
}
var appRoot = Path.Combine(extractRoot, "app");
if (Directory.Exists(appRoot))
{
return appRoot;
}
return extractRoot;
}
private void UpdateTitlebarVersion()
{
if (titlebar == null)
{
return;
}
titlebar.SubText = $"V{GetCurrentVersion()}";
titlebar.Refresh();
}
private static string GetCurrentVersion()
{
var informationalVersion = Assembly
.GetExecutingAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
var version = string.IsNullOrWhiteSpace(informationalVersion)
? Application.ProductVersion?.Trim()
: informationalVersion.Trim();
if (string.IsNullOrWhiteSpace(version))
{
version = "0.0.0";
}
var plusIndex = version.IndexOf('+');
if (plusIndex >= 0)
{
version = version[..plusIndex];
}
return version.Trim().TrimStart('V', 'v');
}
private static string BuildUpgradeCheckUrl(string code, string version)
{
return $"{UpgradeCheckApiBaseUrl}/api/softwareupgrade/check?code={Uri.EscapeDataString(code)}&version={Uri.EscapeDataString(string.IsNullOrWhiteSpace(version) ? "0.0.0" : version)}";
}
private static bool IsRemoteVersionNewer(string currentVersion, string remoteVersion)
{
if (string.IsNullOrWhiteSpace(remoteVersion))
{
return false;
}
if (Version.TryParse(NormalizeVersion(remoteVersion), out var remote) &&
Version.TryParse(NormalizeVersion(currentVersion), out var current))
{
return remote > current;
}
return !string.Equals(currentVersion?.Trim(), remoteVersion.Trim(), StringComparison.OrdinalIgnoreCase);
}
private static string NormalizeVersion(string? version)
{
var value = string.IsNullOrWhiteSpace(version) ? "0.0.0" : version.Trim();
var parts = value.Split('.', StringSplitOptions.RemoveEmptyEntries).ToList();
while (parts.Count < 3)
{
parts.Add("0");
}
return string.Join(".", parts.Take(4));
}
/// <summary>
/// 生成当前设备的唯一标识。
/// 优先拼接主板序列号、CPU ProcessorId、磁盘序列号和机器名再做 SHA256 摘要。
/// 若某些硬件信息读取失败,则自动降级为可获取到的信息组合。
/// </summary>
private static string BuildDeviceCode()
{
var parts = new List<string>
{
GetWmiProperty("Win32_BaseBoard", "SerialNumber"),
GetWmiProperty("Win32_Processor", "ProcessorId"),
GetWmiProperty("Win32_DiskDrive", "SerialNumber"),
Environment.MachineName
};
var raw = string.Join("|", parts.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()));
if (string.IsNullOrWhiteSpace(raw))
{
raw = $"{Environment.MachineName}|{Environment.UserName}|{Environment.OSVersion.VersionString}";
}
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
var hex = Convert.ToHexString(bytes);
return hex[..24];
}
/// <summary>
/// 读取指定 WMI 类的首个属性值。
/// </summary>
private static string GetWmiProperty(string className, string propertyName)
{
try
{
using var searcher = new ManagementObjectSearcher($"SELECT {propertyName} FROM {className}");
foreach (var item in searcher.Get())
{
var value = item[propertyName]?.ToString();
if (!string.IsNullOrWhiteSpace(value))
{
return value.Trim();
}
}
}
catch
{
}
return string.Empty;
}
private static (string Sid, string Version, string Source) TryExtractSidFromWechatLocalFiles() private static (string Sid, string Version, string Source) TryExtractSidFromWechatLocalFiles()
{ {
foreach (var root in GetWechatDataRoots()) foreach (var root in GetWechatDataRoots())
@ -3444,6 +4108,9 @@ namespace Vmianqian
public int SmtpPort { get; set; } = 465; public int SmtpPort { get; set; } = 465;
public string NotifyEmail { get; set; } = string.Empty; public string NotifyEmail { get; set; } = string.Empty;
public string EmailAuthCode { get; set; } = string.Empty; public string EmailAuthCode { get; set; } = string.Empty;
public string AlipayAccount { get; set; } = string.Empty;
public string AlipayPassword { get; set; } = string.Empty;
public bool AlipayEnableAutoFill { get; set; }
public string WechatPath { get; set; } = string.Empty; public string WechatPath { get; set; } = string.Empty;
public string WechatSid { get; set; } = string.Empty; public string WechatSid { get; set; } = string.Empty;
public string WechatApiVersion { get; set; } = "7.10.1"; public string WechatApiVersion { get; set; } = "7.10.1";
@ -3508,6 +4175,31 @@ namespace Vmianqian
public List<PendingOrderRegistration>? Data { get; set; } public List<PendingOrderRegistration>? Data { get; set; }
} }
public sealed class SoftwareUpgradeCheckResponse
{
public int Code { get; set; }
public string Msg { get; set; } = string.Empty;
public SoftwareUpgradeCheckData? Data { get; set; }
}
public sealed class SoftwareUpgradeCheckData
{
[JsonPropertyName("upToDate")]
public bool UpToDate { get; set; }
[JsonPropertyName("latestVersion")]
public string LatestVersion { get; set; } = string.Empty;
[JsonPropertyName("downloadUrl")]
public string DownloadUrl { get; set; } = string.Empty;
[JsonPropertyName("forceUpdate")]
public bool ForceUpdate { get; set; }
[JsonPropertyName("releaseNotes")]
public string ReleaseNotes { get; set; } = string.Empty;
}
public sealed class ServerCallbackPayload public sealed class ServerCallbackPayload
{ {
public string ApiKey { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty;

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net10.0-windows\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
</Project>

73
Properties/Resources.Designer.cs generated Normal file
View File

@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace Vmianqian.Properties {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Vmianqian.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </summary>
internal static System.Drawing.Bitmap logo {
get {
object obj = ResourceManager.GetObject("logo", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

124
Properties/Resources.resx Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="logo" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\logo.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

5
Readmd.md Normal file
View File

@ -0,0 +1,5 @@
# 自动生成升级包代码
```
powershell -ExecutionPolicy Bypass -File .\package-incremental.ps1
```

View File

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Version>0.0.3</Version>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@ -8,12 +9,14 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<GitRepositoryConfigurationScope>local</GitRepositoryConfigurationScope> <GitRepositoryConfigurationScope>local</GitRepositoryConfigurationScope>
<ApplicationIcon>logo.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AntdUI" Version="2.3.10" /> <PackageReference Include="AntdUI" Version="2.3.10" />
<PackageReference Include="MailKit" Version="4.16.0" /> <PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageReference Include="System.Management" Version="9.0.0" />
<PackageReference Include="Titanium.Web.Proxy" Version="3.2.0" /> <PackageReference Include="Titanium.Web.Proxy" Version="3.2.0" />
</ItemGroup> </ItemGroup>
@ -25,4 +28,23 @@
<None Remove="antdui-demo\**\*" /> <None Remove="antdui-demo\**\*" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="logo.ico" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>E:\Demo\C\Vmianqian\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Form1.cs"> <Compile Update="Form1.cs">
<SubType>Form</SubType> <SubType>Form</SubType>

View File

@ -6,11 +6,12 @@
"compilationOptions": {}, "compilationOptions": {},
"targets": { "targets": {
".NETCoreApp,Version=v10.0": { ".NETCoreApp,Version=v10.0": {
"Vmianqian/1.0.0": { "Vmianqian/0.0.2": {
"dependencies": { "dependencies": {
"AntdUI": "2.3.10", "AntdUI": "2.3.10",
"MailKit": "4.16.0", "MailKit": "4.16.0",
"Microsoft.Web.WebView2": "1.0.2903.40", "Microsoft.Web.WebView2": "1.0.2903.40",
"System.Management": "9.0.0",
"Titanium.Web.Proxy": "3.2.0", "Titanium.Web.Proxy": "3.2.0",
"Microsoft.Web.WebView2.Core": "1.0.2903.40", "Microsoft.Web.WebView2.Core": "1.0.2903.40",
"Microsoft.Web.WebView2.WinForms": "1.0.2903.40", "Microsoft.Web.WebView2.WinForms": "1.0.2903.40",
@ -93,6 +94,22 @@
} }
} }
}, },
"System.Management/9.0.0": {
"runtime": {
"lib/net9.0/System.Management.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
},
"runtimeTargets": {
"runtimes/win/lib/net9.0/System.Management.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
}
},
"Titanium.Web.Proxy/3.2.0": { "Titanium.Web.Proxy/3.2.0": {
"dependencies": { "dependencies": {
"BrotliSharpLib": "0.3.3", "BrotliSharpLib": "0.3.3",
@ -132,7 +149,7 @@
} }
}, },
"libraries": { "libraries": {
"Vmianqian/1.0.0": { "Vmianqian/0.0.2": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
@ -186,6 +203,13 @@
"path": "portable.bouncycastle/1.8.8", "path": "portable.bouncycastle/1.8.8",
"hashPath": "portable.bouncycastle.1.8.8.nupkg.sha512" "hashPath": "portable.bouncycastle.1.8.8.nupkg.sha512"
}, },
"System.Management/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bVh4xAMI5grY5GZoklKcMBLirhC8Lqzp63Ft3zXJacwGAlLyFdF4k0qz4pnKIlO6HyL2Z4zqmHm9UkzEo6FFsA==",
"path": "system.management/9.0.0",
"hashPath": "system.management.9.0.0.nupkg.sha512"
},
"Titanium.Web.Proxy/3.2.0": { "Titanium.Web.Proxy/3.2.0": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,

View File

@ -8,5 +8,5 @@
"top_topics_and_observing_domains": [ ] "top_topics_and_observing_domains": [ ]
} ], } ],
"hex_encoded_hmac_key": "3D20341D26496BECEC5CACADB813B2813BF9F4CB81F7946788CD04BBF5F9787C", "hex_encoded_hmac_key": "3D20341D26496BECEC5CACADB813B2813BF9F4CB81F7946788CD04BBF5F9787C",
"next_scheduled_calculation_time": "13423151610108124" "next_scheduled_calculation_time": "13423151610108165"
} }

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.890 a9f0 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/MANIFEST-000001 2026/05/07-20:24:27.760 1648 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/MANIFEST-000001
2026/05/07-00:59:30.890 a9f0 Recovering log #3 2026/05/07-20:24:27.761 1648 Recovering log #3
2026/05/07-00:59:30.890 a9f0 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/000003.log 2026/05/07-20:24:27.761 1648 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:08.006 5d14 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/MANIFEST-000001 2026/05/07-20:03:13.009 ef30 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/MANIFEST-000001
2026/05/07-00:34:08.007 5d14 Recovering log #3 2026/05/07-20:03:13.009 ef30 Recovering log #3
2026/05/07-00:34:08.007 5d14 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/000003.log 2026/05/07-20:03:13.010 ef30 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Extension State/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.851 50dc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001 2026/05/07-20:24:27.720 c6fc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2026/05/07-00:59:30.857 50dc Recovering log #8 2026/05/07-20:24:27.728 c6fc Recovering log #8
2026/05/07-00:59:30.859 50dc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000008.log 2026/05/07-20:24:27.731 c6fc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000008.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:07.975 f674 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001 2026/05/07-20:03:12.956 f7ec Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2026/05/07-00:34:07.981 f674 Recovering log #8 2026/05/07-20:03:12.964 f7ec Recovering log #8
2026/05/07-00:34:07.990 f674 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000008.log 2026/05/07-20:03:12.967 f7ec Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000008.log

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:31.188 50dc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001 2026/05/07-20:24:28.037 c6fc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2026/05/07-00:59:31.188 50dc Recovering log #3 2026/05/07-20:24:28.038 c6fc Recovering log #3
2026/05/07-00:59:31.190 50dc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/000003.log 2026/05/07-20:24:28.040 c6fc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:08.312 f674 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001 2026/05/07-20:03:13.318 f7ec Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2026/05/07-00:34:08.312 f674 Recovering log #3 2026/05/07-20:03:13.319 f7ec Recovering log #3
2026/05/07-00:34:08.315 f674 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/000003.log 2026/05/07-20:03:13.321 f7ec Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.785 ecb0 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001 2026/05/07-20:24:27.648 a5cc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2026/05/07-00:59:30.789 ecb0 Recovering log #3 2026/05/07-20:24:27.651 a5cc Recovering log #3
2026/05/07-00:59:30.789 ecb0 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/000003.log 2026/05/07-20:24:27.652 a5cc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:07.905 ece0 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001 2026/05/07-20:03:12.876 c9c4 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2026/05/07-00:34:07.909 ece0 Recovering log #3 2026/05/07-20:03:12.882 c9c4 Recovering log #3
2026/05/07-00:34:07.909 ece0 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/000003.log 2026/05/07-20:03:12.882 c9c4 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Site Characteristics Database/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.784 d424 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/MANIFEST-000001 2026/05/07-20:24:27.647 73b4 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/MANIFEST-000001
2026/05/07-00:59:30.789 d424 Recovering log #3 2026/05/07-20:24:27.651 73b4 Recovering log #3
2026/05/07-00:59:30.789 d424 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/000003.log 2026/05/07-20:24:27.651 73b4 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:07.904 6f20 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/MANIFEST-000001 2026/05/07-20:03:12.876 b7cc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/MANIFEST-000001
2026/05/07-00:34:07.909 6f20 Recovering log #3 2026/05/07-20:03:12.882 b7cc Recovering log #3
2026/05/07-00:34:07.909 6f20 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/000003.log 2026/05/07-20:03:12.882 b7cc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\Sync Data\LevelDB/000003.log

View File

@ -26,3 +26,8 @@
2026-05-06 16:31:24.926: [INFO] OnDoneLoading sync enabled: 0 2026-05-06 16:31:24.926: [INFO] OnDoneLoading sync enabled: 0
2026-05-06 16:34:07.923: [INFO] OnDoneLoading sync enabled: 0 2026-05-06 16:34:07.923: [INFO] OnDoneLoading sync enabled: 0
2026-05-06 16:59:30.809: [INFO] OnDoneLoading sync enabled: 0 2026-05-06 16:59:30.809: [INFO] OnDoneLoading sync enabled: 0
2026-05-07 11:33:26.664: [INFO] OnDoneLoading sync enabled: 0
2026-05-07 11:51:55.297: [INFO] OnDoneLoading sync enabled: 0
2026-05-07 11:55:20.733: [INFO] OnDoneLoading sync enabled: 0
2026-05-07 11:55:55.674: [INFO] OnDoneLoading sync enabled: 0
2026-05-07 11:56:13.319: [INFO] OnDoneLoading sync enabled: 0

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.817 f6dc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/MANIFEST-000001 2026/05/07-20:24:27.673 1648 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/MANIFEST-000001
2026/05/07-00:59:30.817 f6dc Recovering log #3 2026/05/07-20:24:27.673 1648 Recovering log #3
2026/05/07-00:59:30.817 f6dc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/000003.log 2026/05/07-20:24:27.674 1648 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:07.931 f2c0 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/MANIFEST-000001 2026/05/07-20:03:12.908 b7cc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/MANIFEST-000001
2026/05/07-00:34:07.931 f2c0 Recovering log #3 2026/05/07-20:03:12.908 b7cc Recovering log #3
2026/05/07-00:34:07.932 f2c0 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/000003.log 2026/05/07-20:03:12.909 b7cc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:59:30.813 f6dc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/MANIFEST-000001 2026/05/07-20:24:27.669 1648 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/MANIFEST-000001
2026/05/07-00:59:30.813 f6dc Recovering log #3 2026/05/07-20:24:27.670 1648 Recovering log #3
2026/05/07-00:59:30.813 f6dc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/000003.log 2026/05/07-20:24:27.670 1648 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/000003.log

View File

@ -1,3 +1,3 @@
2026/05/07-00:34:07.927 f2c0 Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/MANIFEST-000001 2026/05/07-20:03:12.903 b7cc Reusing MANIFEST E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/MANIFEST-000001
2026/05/07-00:34:07.927 f2c0 Recovering log #3 2026/05/07-20:03:12.904 b7cc Recovering log #3
2026/05/07-00:34:07.927 f2c0 Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/000003.log 2026/05/07-20:03:12.904 b7cc Reusing old log E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Vmianqian.exe.WebView2\EBWebView\Default\shared_proto_db\metadata/000003.log

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,9 @@
"SmtpPort": 465, "SmtpPort": 465,
"NotifyEmail": "1066960883@qq.com", "NotifyEmail": "1066960883@qq.com",
"EmailAuthCode": "TPPMKSMvCadyzu3m", "EmailAuthCode": "TPPMKSMvCadyzu3m",
"AlipayAccount": "19895983967",
"AlipayPassword": "Lzq920103!",
"AlipayEnableAutoFill": true,
"WechatPath": "C:\\Softwares\\Tencent\\Weixin\\Weixin.exe", "WechatPath": "C:\\Softwares\\Tencent\\Weixin\\Weixin.exe",
"WechatSid": "AAHhwahUM3etjBpBQ-qeuaAZ9RF_NLJRg-ljztn_3qLcCQ", "WechatSid": "AAHhwahUM3etjBpBQ-qeuaAZ9RF_NLJRg-ljztn_3qLcCQ",
"WechatApiVersion": "7.10.2", "WechatApiVersion": "7.10.2",

View File

@ -1,16 +1 @@
[ []
{
"OrderId": "202605070105294312",
"PayId": "2026050701002863867",
"Param": "MBSI70BBVRCGHU0K",
"PayType": 2,
"Price": 0.1,
"ReallyPrice": 0.1,
"TimeOut": 5,
"State": 1,
"Date": 1778086829,
"RegisteredAt": "2026-05-07T01:00:50.9448888+08:00",
"CompletedAt": "2026-05-07T01:00:51.3503482+08:00",
"TradeNo": "2026050722001409341411855749"
}
]

70
bin/Inno编译文件.iss Normal file
View File

@ -0,0 +1,70 @@
; --------------------------------------------------------
; V免签PC监听程序 - Inno Setup 完整脚本
; 编译环境:.NET 10.0 Windows
; 打包模式:生成解决方案 (Release)
; --------------------------------------------------------
#define MyAppName "V免签PC监听程序"
#define MyAppVersion "0.0.1"
#define MyAppPublisher "扫地僧"
#define MyAppURL "https://www.yunzer.cn/"
#define MyAppExeName "Vmianqian.exe"
#define MyAppAssocName MyAppName + "文件"
#define MyAppAssocExt ".exe"
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
[Setup]
; AppId 保持不变,确保版本覆盖正常
AppId={{D8540A89-49BC-4843-9CE7-A96496DFB2DB}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
; 默认安装路径
DefaultDirName={autopf}\{#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}
; --- 核心设置:监听程序必须以管理员权限运行 ---
PrivilegesRequired=admin
; 架构支持
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
ChangesAssociations=yes
DisableProgramGroupPage=yes
; 输出设置
OutputBaseFilename=V免签监听程序_安装包
; 请确保此 .ico 文件路径正确
SetupIconFile=E:\Demo\C\Vmianqian\logo.ico
SolidCompression=yes
WizardStyle=modern dynamic
[Languages]
Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
; --- 核心修正:直接指向 Release 生成目录,并打包目录下所有文件 ---
; recursesubdirs 确保打包子目录(如 runtimes 目录,这对 .NET 程序至关重要)
Source: "E:\Demo\C\Vmianqian\bin\Release\net10.0-windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Registry]
; 注册表关联
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
[Icons]
; 显式指定图标文件为主程序本身,解决快捷方式图标空白问题
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; IconFilename: "{app}\{#MyAppExeName}"
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

22
build-upgrade-package.bat Normal file
View File

@ -0,0 +1,22 @@
@echo off
setlocal
cd /d "%~dp0"
echo.
echo ========================================
echo Build upgrade package
echo ========================================
echo.
powershell -ExecutionPolicy Bypass -File ".\package-incremental.ps1"
echo.
if %errorlevel% neq 0 (
echo Package build failed.
) else (
echo Package build completed.
)
echo.
pause

BIN
logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -12,11 +12,11 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Vmianqian")] [assembly: System.Reflection.AssemblyCompanyAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("0.0.3.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6fb40a7689b5db8902ca50394f29517e5bdc4108")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("0.0.3+4a5dd2418b610b8c830d439dca19d71d132d86c7")]
[assembly: System.Reflection.AssemblyProductAttribute("Vmianqian")] [assembly: System.Reflection.AssemblyProductAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyTitleAttribute("Vmianqian")] [assembly: System.Reflection.AssemblyTitleAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("0.0.3.0")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")] [assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")] [assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]

View File

@ -1 +1 @@
5d9c659649f82a6db3033ce25ce5f2cd30abeb96072a886181b61b685af63a8c 3d204494fbae9c9a1df67d0bd1dba87fdd9a76e6167861891cb9d29bd6d54027

View File

@ -1 +1 @@
a200812809a6c12e0e5a805bae74d188c7782b566b9e1e93bf9af5062674a65d 24dcc0ae9e7bdc30cfe6b63b0849f0d4b1f637666c5ea893cbac6a9564f6e43d

View File

@ -77,3 +77,6 @@ E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.Wpf.dll
E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.Core.xml E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.Core.xml
E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.WinForms.xml E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.WinForms.xml
E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.Wpf.xml E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\Microsoft.Web.WebView2.Wpf.xml
E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\System.Management.dll
E:\Demo\C\Vmianqian\bin\Debug\net10.0-windows\runtimes\win\lib\net9.0\System.Management.dll
E:\Demo\C\Vmianqian\obj\Debug\net10.0-windows\Vmianqian.Properties.Resources.resources

View File

@ -79,6 +79,22 @@
} }
} }
}, },
"System.Management/9.0.0": {
"runtime": {
"lib/net9.0/System.Management.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
},
"runtimeTargets": {
"runtimes/win/lib/net9.0/System.Management.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
}
},
"Titanium.Web.Proxy/3.2.0": { "Titanium.Web.Proxy/3.2.0": {
"dependencies": { "dependencies": {
"BrotliSharpLib": "0.3.3", "BrotliSharpLib": "0.3.3",
@ -143,6 +159,13 @@
"path": "portable.bouncycastle/1.8.8", "path": "portable.bouncycastle/1.8.8",
"hashPath": "portable.bouncycastle.1.8.8.nupkg.sha512" "hashPath": "portable.bouncycastle.1.8.8.nupkg.sha512"
}, },
"System.Management/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bVh4xAMI5grY5GZoklKcMBLirhC8Lqzp63Ft3zXJacwGAlLyFdF4k0qz4pnKIlO6HyL2Z4zqmHm9UkzEo6FFsA==",
"path": "system.management/9.0.0",
"hashPath": "system.management.9.0.0.nupkg.sha512"
},
"Titanium.Web.Proxy/3.2.0": { "Titanium.Web.Proxy/3.2.0": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,

Some files were not shown because too many files have changed in this diff Show More