大量的代码格式化
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 38s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s

This commit is contained in:
SunCheng
2026-01-18 22:25:59 +08:00
parent 9611ff2088
commit 435efbcb90
18 changed files with 200 additions and 226 deletions

View File

@@ -43,12 +43,12 @@ public class ConfigService(IConfigRepository configRepository) : IConfigService
var config = await configRepository.GetByKeyAsync(key);
var type = typeof(T) switch
{
Type t when t == typeof(bool) => ConfigType.Boolean,
Type t when t == typeof(int)
|| t == typeof(double)
|| t == typeof(float)
|| t == typeof(decimal) => ConfigType.Number,
Type t when t == typeof(string) => ConfigType.String,
{ } t when t == typeof(bool) => ConfigType.Boolean,
{ } t when t == typeof(int)
|| t == typeof(double)
|| t == typeof(float)
|| t == typeof(decimal) => ConfigType.Number,
{ } t when t == typeof(string) => ConfigType.String,
_ => ConfigType.Json
};
var valueStr = type switch

View File

@@ -1,4 +1,4 @@
using Service.EmailParseServices;
using Service.EmailServices.EmailParse;
namespace Service.EmailServices;

View File

@@ -1,4 +1,4 @@
namespace Service.EmailParseServices;
namespace Service.EmailServices.EmailParse;
public class EmailParseForm95555(
ILogger<EmailParseForm95555> logger,
@@ -26,7 +26,7 @@ public class EmailParseForm95555(
return true;
}
public override async Task<(
public override Task<(
string card,
string reason,
decimal amount,
@@ -51,7 +51,7 @@ public class EmailParseForm95555(
if (matches.Count <= 0)
{
logger.LogWarning("未能从招商银行邮件内容中解析出交易信息");
return [];
return Task.FromResult<(string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime? occurredAt)[]>([]);
}
var results = new List<(
@@ -85,7 +85,7 @@ public class EmailParseForm95555(
results.Add((card, reason, amount, balance, type, occurredAt));
}
}
return results.ToArray();
return Task.FromResult(results.ToArray());
}
private DateTime? ParseOccurredAt(string value)

View File

@@ -1,6 +1,6 @@
using HtmlAgilityPack;
namespace Service.EmailParseServices;
namespace Service.EmailServices.EmailParse;
public class EmailParseFormCcsvc(
ILogger<EmailParseFormCcsvc> logger,
@@ -44,11 +44,6 @@ public class EmailParseFormCcsvc(
// 1. Get Date
var dateNode = doc.DocumentNode.SelectSingleNode("//font[contains(text(), '您的消费明细如下')]");
if (dateNode == null)
{
logger.LogWarning("Date node not found");
return [];
}
var dateText = dateNode.InnerText.Trim();
// "2025/12/21&nbsp;您的消费明细如下:"
@@ -63,104 +58,87 @@ public class EmailParseFormCcsvc(
decimal balance = 0;
// Find "可用额度" label
var limitLabelNode = doc.DocumentNode.SelectSingleNode("//font[contains(text(), '可用额度')]");
if (limitLabelNode != null)
{
// Go up to TR
var tr = limitLabelNode.Ancestors("tr").FirstOrDefault();
if (tr != null)
{
var prevTr = tr.PreviousSibling;
while (prevTr != null && prevTr.Name != "tr") prevTr = prevTr.PreviousSibling;
while (prevTr.Name != "tr") prevTr = prevTr.PreviousSibling;
if (prevTr != null)
{
var balanceNode = prevTr.SelectSingleNode(".//font[contains(text(), '¥')]");
if (balanceNode != null)
{
var balanceStr = balanceNode.InnerText.Replace("¥", "").Replace(",", "").Trim();
decimal.TryParse(balanceStr, out balance);
}
}
var balanceNode = prevTr.SelectSingleNode(".//font[contains(text(), '¥')]");
var balanceStr = balanceNode.InnerText.Replace("¥", "").Replace(",", "").Trim();
decimal.TryParse(balanceStr, out balance);
}
}
// 3. Get Transactions
var transactionNodes = doc.DocumentNode.SelectNodes("//span[@id='fixBand4']");
if (transactionNodes != null)
foreach (var node in transactionNodes)
{
foreach (var node in transactionNodes)
try
{
try
// Time
var timeNode = node.SelectSingleNode(".//span[@id='fixBand5']//font");
var timeText = timeNode.InnerText.Trim(); // "10:13:43"
DateTime? occurredAt = date;
if (!string.IsNullOrEmpty(timeText) && DateTime.TryParse($"{date:yyyy-MM-dd} {timeText}", out var dt))
{
// Time
var timeNode = node.SelectSingleNode(".//span[@id='fixBand5']//font");
var timeText = timeNode?.InnerText.Trim(); // "10:13:43"
DateTime? occurredAt = date;
if (!string.IsNullOrEmpty(timeText) && DateTime.TryParse($"{date:yyyy-MM-dd} {timeText}", out var dt))
{
occurredAt = dt;
}
// Info Block
var infoNode = node.SelectSingleNode(".//span[@id='fixBand12']");
if (infoNode == null) continue;
// Amount
var amountNode = infoNode.SelectSingleNode(".//font[contains(text(), 'CNY')]");
var amountText = amountNode?.InnerText.Replace("CNY", "").Replace("&nbsp;", "").Trim();
if (!decimal.TryParse(amountText, out var amount))
{
continue;
}
// Description
var descNode = infoNode.SelectSingleNode(".//tr[2]//font");
var descText = descNode?.InnerText ?? "";
// Replace &nbsp; and non-breaking space (\u00A0) with normal space
descText = descText.Replace("&nbsp;", " ");
descText = HtmlEntity.DeEntitize(descText).Replace((char)160, ' ').Trim();
// Parse Description: "尾号4390 消费 财付通-luckincoffee瑞幸咖啡"
var parts = descText.Split([' '], StringSplitOptions.RemoveEmptyEntries);
string card = "";
string reason = descText;
TransactionType type = TransactionType.Expense;
if (parts.Length > 0 && parts[0].StartsWith("尾号"))
{
card = parts[0].Replace("尾号", "");
}
if (parts.Length > 1)
{
var typeStr = parts[1];
type = DetermineTransactionType(typeStr, reason, amount);
}
if (parts.Length > 2)
{
reason = string.Join(" ", parts.Skip(2));
}
// 招商信用卡特殊,消费金额为正数,退款为负数
if(amount > 0)
{
type = TransactionType.Expense;
}
else
{
type = TransactionType.Income;
amount = Math.Abs(amount);
}
result.Add((card, reason, amount, balance, type, occurredAt));
occurredAt = dt;
}
catch (Exception ex)
// Info Block
var infoNode = node.SelectSingleNode(".//span[@id='fixBand12']");
// Amount
var amountNode = infoNode.SelectSingleNode(".//font[contains(text(), 'CNY')]");
var amountText = amountNode.InnerText.Replace("CNY", "").Replace("&nbsp;", "").Trim();
if (!decimal.TryParse(amountText, out var amount))
{
logger.LogError(ex, "Error parsing transaction node");
continue;
}
// Description
var descNode = infoNode.SelectSingleNode(".//tr[2]//font");
var descText = descNode.InnerText;
// Replace &nbsp; and non-breaking space (\u00A0) with normal space
descText = descText.Replace("&nbsp;", " ");
descText = HtmlEntity.DeEntitize(descText).Replace((char)160, ' ').Trim();
// Parse Description: "尾号4390 消费 财付通-luckincoffee瑞幸咖啡"
var parts = descText.Split([' '], StringSplitOptions.RemoveEmptyEntries);
string card = "";
string reason = descText;
TransactionType type;
if (parts.Length > 0 && parts[0].StartsWith("尾号"))
{
card = parts[0].Replace("尾号", "");
}
if (parts.Length > 2)
{
reason = string.Join(" ", parts.Skip(2));
}
// 招商信用卡特殊,消费金额为正数,退款为负数
if(amount > 0)
{
type = TransactionType.Expense;
}
else
{
type = TransactionType.Income;
amount = Math.Abs(amount);
}
result.Add((card, reason, amount, balance, type, occurredAt));
}
catch (Exception ex)
{
logger.LogError(ex, "Error parsing transaction node");
}
}

View File

@@ -1,4 +1,4 @@
namespace Service.EmailParseServices;
namespace Service.EmailServices.EmailParse;
public interface IEmailParseServices
{

View File

@@ -182,7 +182,7 @@ public class EmailSyncService(
var unreadMessages = await emailFetchService.FetchUnreadMessagesAsync();
logger.LogInformation("邮箱 {Email} 获取到 {MessageCount} 封未读邮件", email, unreadMessages.Count);
foreach (var (message, uid) in unreadMessages)
foreach (var (message, _) in unreadMessages)
{
try
{

View File

@@ -127,7 +127,7 @@ public class EmailSyncJob(
var unreadMessages = await emailFetchService.FetchUnreadMessagesAsync();
logger.LogInformation("邮箱 {Email} 获取到 {MessageCount} 封未读邮件", email, unreadMessages.Count);
foreach (var (message, uid) in unreadMessages)
foreach (var (message, _) in unreadMessages)
{
try
{

View File

@@ -1,5 +1,4 @@
using System.Net;
using WebPush;
using WebPush;
using PushSubscription = Entity.PushSubscription;
namespace Service;

View File

@@ -158,10 +158,8 @@ public class OpenAiService(
var json = JsonSerializer.Serialize(payload);
using var content = new StringContent(json, Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = content
};
using var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = content;
using var resp = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (!resp.IsSuccessStatusCode)
@@ -232,10 +230,8 @@ public class OpenAiService(
using var content = new StringContent(json, Encoding.UTF8, "application/json");
// 使用 SendAsync 来支持 HttpCompletionOption
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = content
};
using var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = content;
using var resp = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (!resp.IsSuccessStatusCode)