456 lines
13 KiB
Markdown
456 lines
13 KiB
Markdown
|
|
# TransactionRecordRepository 查询语句文档
|
|||
|
|
|
|||
|
|
本文档整理了所有与账单(TransactionRecord)相关的查询语句,包括仓储层、服务层中的SQL查询。
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
|
|||
|
|
1. [TransactionRecordRepository 查询方法](#transactionrecordrepository-查询方法)
|
|||
|
|
2. [其他仓储中的账单查询](#其他仓储中的账单查询)
|
|||
|
|
3. [服务层中的SQL查询](#服务层中的sql查询)
|
|||
|
|
4. [总结](#总结)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## TransactionRecordRepository 查询方法
|
|||
|
|
|
|||
|
|
### 1. 基础查询
|
|||
|
|
|
|||
|
|
#### 1.1 根据邮件ID和交易时间检查是否存在
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:94-99
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.EmailMessageId == emailMessageId && t.OccurredAt == occurredAt)
|
|||
|
|
.FirstAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 根据导入编号检查是否存在
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:101-106
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.ImportNo == importNo && t.ImportFrom == importFrom)
|
|||
|
|
.FirstAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 核心查询构建器
|
|||
|
|
|
|||
|
|
#### 2.1 BuildQuery() 私有方法 - 统一查询构建
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:53-92
|
|||
|
|
private ISelect<TransactionRecord> BuildQuery(
|
|||
|
|
int? year = null,
|
|||
|
|
int? month = null,
|
|||
|
|
DateTime? startDate = null,
|
|||
|
|
DateTime? endDate = null,
|
|||
|
|
TransactionType? type = null,
|
|||
|
|
string[]? classifies = null,
|
|||
|
|
string? searchKeyword = null,
|
|||
|
|
string? reason = null)
|
|||
|
|
{
|
|||
|
|
var query = FreeSql.Select<TransactionRecord>();
|
|||
|
|
|
|||
|
|
// 搜索关键词条件(Reason/Classify/Card/ImportFrom)
|
|||
|
|
query = query.WhereIf(!string.IsNullOrWhiteSpace(searchKeyword),
|
|||
|
|
t => t.Reason.Contains(searchKeyword!) ||
|
|||
|
|
t.Classify.Contains(searchKeyword!) ||
|
|||
|
|
t.Card.Contains(searchKeyword!) ||
|
|||
|
|
t.ImportFrom.Contains(searchKeyword!))
|
|||
|
|
.WhereIf(!string.IsNullOrWhiteSpace(reason),
|
|||
|
|
t => t.Reason == reason);
|
|||
|
|
|
|||
|
|
// 按分类筛选(处理"未分类"特殊情况)
|
|||
|
|
if (classifies is { Length: > 0 })
|
|||
|
|
{
|
|||
|
|
var filterClassifies = classifies.Select(c => c == "未分类" ? string.Empty : c).ToList();
|
|||
|
|
query = query.Where(t => filterClassifies.Contains(t.Classify));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按交易类型筛选
|
|||
|
|
query = query.WhereIf(type.HasValue, t => t.Type == type!.Value);
|
|||
|
|
|
|||
|
|
// 按年月筛选
|
|||
|
|
if (year.HasValue && month.HasValue)
|
|||
|
|
{
|
|||
|
|
var dateStart = new DateTime(year.Value, month.Value, 1);
|
|||
|
|
var dateEnd = dateStart.AddMonths(1);
|
|||
|
|
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按日期范围筛选
|
|||
|
|
query = query.WhereIf(startDate.HasValue, t => t.OccurredAt >= startDate!.Value)
|
|||
|
|
.WhereIf(endDate.HasValue, t => t.OccurredAt <= endDate!.Value);
|
|||
|
|
|
|||
|
|
return query;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. 分页查询与统计
|
|||
|
|
|
|||
|
|
#### 3.1 分页获取交易记录列表
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:108-137
|
|||
|
|
var query = BuildQuery(year, month, startDate, endDate, type, classifies, searchKeyword, reason);
|
|||
|
|
|
|||
|
|
// 排序:按金额或按时间
|
|||
|
|
if (sortByAmount)
|
|||
|
|
{
|
|||
|
|
return await query
|
|||
|
|
.OrderByDescending(t => t.Amount)
|
|||
|
|
.OrderByDescending(t => t.Id)
|
|||
|
|
.Page(pageIndex, pageSize)
|
|||
|
|
.ToListAsync();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await query
|
|||
|
|
.OrderByDescending(t => t.OccurredAt)
|
|||
|
|
.OrderByDescending(t => t.Id)
|
|||
|
|
.Page(pageIndex, pageSize)
|
|||
|
|
.ToListAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 获取总数(与分页查询条件相同)
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:139-151
|
|||
|
|
var query = BuildQuery(year, month, startDate, endDate, type, classifies, searchKeyword, reason);
|
|||
|
|
return await query.CountAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3 获取所有不同的交易分类
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:153-159
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => !string.IsNullOrEmpty(t.Classify))
|
|||
|
|
.Distinct()
|
|||
|
|
.ToListAsync(t => t.Classify);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. 按邮件相关查询
|
|||
|
|
|
|||
|
|
#### 4.1 获取指定邮件的交易记录列表
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:161-167
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.EmailMessageId == emailMessageId)
|
|||
|
|
.OrderBy(t => t.OccurredAt)
|
|||
|
|
.ToListAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.2 获取指定邮件的交易记录数量
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:169-174
|
|||
|
|
return (int)await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.EmailMessageId == emailMessageId)
|
|||
|
|
.CountAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. 未分类账单查询
|
|||
|
|
|
|||
|
|
#### 5.1 获取未分类的账单列表
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:176-183
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => string.IsNullOrEmpty(t.Classify))
|
|||
|
|
.OrderByDescending(t => t.OccurredAt)
|
|||
|
|
.Page(1, pageSize)
|
|||
|
|
.ToListAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 6. 智能分类相关查询
|
|||
|
|
|
|||
|
|
#### 6.1 根据关键词查询已分类的账单
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:185-204
|
|||
|
|
if (keywords.Count == 0)
|
|||
|
|
{
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var query = FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.Classify != "");
|
|||
|
|
|
|||
|
|
// 构建OR条件:Reason包含任意一个关键词
|
|||
|
|
if (keywords.Count > 0)
|
|||
|
|
{
|
|||
|
|
query = query.Where(t => keywords.Any(keyword => t.Reason.Contains(keyword)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await query
|
|||
|
|
.OrderByDescending(t => t.OccurredAt)
|
|||
|
|
.Limit(limit)
|
|||
|
|
.ToListAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7. 待确认分类查询
|
|||
|
|
|
|||
|
|
#### 7.1 获取待确认分类的账单列表
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:206-212
|
|||
|
|
return await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "")
|
|||
|
|
.OrderByDescending(t => t.OccurredAt)
|
|||
|
|
.ToListAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 8. 批量更新操作
|
|||
|
|
|
|||
|
|
#### 8.1 按摘要批量更新交易记录的分类
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:214-221
|
|||
|
|
return await FreeSql.Update<TransactionRecord>()
|
|||
|
|
.Set(t => t.Type, type)
|
|||
|
|
.Set(t => t.Classify, classify)
|
|||
|
|
.Where(t => t.Reason == reason)
|
|||
|
|
.ExecuteAffrowsAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 8.2 更新分类名称
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:223-229
|
|||
|
|
return await FreeSql.Update<TransactionRecord>()
|
|||
|
|
.Set(a => a.Classify, newName)
|
|||
|
|
.Where(a => a.Classify == oldName && a.Type == type)
|
|||
|
|
.ExecuteAffrowsAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 8.3 确认待确认的分类
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionRecordRepository.cs:231-241
|
|||
|
|
return await FreeSql.Update<TransactionRecord>()
|
|||
|
|
.Set(t => t.Classify == t.UnconfirmedClassify)
|
|||
|
|
.Set(t => t.Type == (t.UnconfirmedType ?? t.Type))
|
|||
|
|
.Set(t => t.UnconfirmedClassify, null)
|
|||
|
|
.Set(t => t.UnconfirmedType, null)
|
|||
|
|
.Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "")
|
|||
|
|
.Where(t => ids.Contains(t.Id))
|
|||
|
|
.ExecuteAffrowsAsync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 其他仓储中的账单查询
|
|||
|
|
|
|||
|
|
### BudgetRepository
|
|||
|
|
|
|||
|
|
#### 1. 获取预算当前金额
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: BudgetRepository.cs:12-33
|
|||
|
|
var query = FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(t => t.OccurredAt >= startDate && t.OccurredAt <= endDate);
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(budget.SelectedCategories))
|
|||
|
|
{
|
|||
|
|
var categoryList = budget.SelectedCategories.Split(',');
|
|||
|
|
query = query.Where(t => categoryList.Contains(t.Classify));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (budget.Category == BudgetCategory.Expense)
|
|||
|
|
{
|
|||
|
|
query = query.Where(t => t.Type == TransactionType.Expense);
|
|||
|
|
}
|
|||
|
|
else if (budget.Category == BudgetCategory.Income)
|
|||
|
|
{
|
|||
|
|
query = query.Where(t => t.Type == TransactionType.Income);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await query.SumAsync(t => t.Amount);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### TransactionCategoryRepository
|
|||
|
|
|
|||
|
|
#### 1. 检查分类是否被使用
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: TransactionCategoryRepository.cs:53-63
|
|||
|
|
var count = await FreeSql.Select<TransactionRecord>()
|
|||
|
|
.Where(r => r.Classify == category.Name && r.Type == category.Type)
|
|||
|
|
.CountAsync();
|
|||
|
|
|
|||
|
|
return count > 0;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 服务层中的SQL查询
|
|||
|
|
|
|||
|
|
### SmartHandleService
|
|||
|
|
|
|||
|
|
#### 1. 智能分析账单 - 执行AI生成的SQL
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: SmartHandleService.cs:351
|
|||
|
|
queryResults = await transactionRepository.ExecuteDynamicSqlAsync(sqlText);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**: 此方法接收AI生成的SQL语句并执行,SQL内容由AI根据用户问题动态生成,例如:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) AS TransactionCount,
|
|||
|
|
SUM(ABS(Amount)) AS TotalAmount,
|
|||
|
|
Type,
|
|||
|
|
Classify
|
|||
|
|
FROM TransactionRecord
|
|||
|
|
WHERE OccurredAt >= '2025-01-01'
|
|||
|
|
AND OccurredAt < '2026-01-01'
|
|||
|
|
GROUP BY Type, Classify
|
|||
|
|
ORDER BY TotalAmount DESC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### BudgetService
|
|||
|
|
|
|||
|
|
#### 1. 获取归档摘要 - 年度交易统计
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: BudgetService.cs:239-252
|
|||
|
|
var yearTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync(
|
|||
|
|
$"""
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) AS TransactionCount,
|
|||
|
|
SUM(ABS(Amount)) AS TotalAmount,
|
|||
|
|
Type,
|
|||
|
|
Classify
|
|||
|
|
FROM TransactionRecord
|
|||
|
|
WHERE OccurredAt >= '{year}-01-01'
|
|||
|
|
AND OccurredAt < '{year + 1}-01-01'
|
|||
|
|
GROUP BY Type, Classify
|
|||
|
|
ORDER BY TotalAmount DESC
|
|||
|
|
"""
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 获取归档摘要 - 月度交易统计
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: BudgetService.cs:254-267
|
|||
|
|
var monthYear = new DateTime(year, month, 1).AddMonths(1);
|
|||
|
|
var monthTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync(
|
|||
|
|
$"""
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) AS TransactionCount,
|
|||
|
|
SUM(ABS(Amount)) AS TotalAmount,
|
|||
|
|
Type,
|
|||
|
|
Classify
|
|||
|
|
FROM TransactionRecord
|
|||
|
|
WHERE OccurredAt >= '{year}-{month:00}-01'
|
|||
|
|
AND OccurredAt < '{monthYear:yyyy-MM-dd}'
|
|||
|
|
GROUP BY Type, Classify
|
|||
|
|
ORDER BY TotalAmount DESC
|
|||
|
|
"""
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### BudgetSavingsService
|
|||
|
|
|
|||
|
|
#### 1. 获取按分类分组的交易金额(用于存款预算计算)
|
|||
|
|
```csharp
|
|||
|
|
/// 位置: BudgetSavingsService.cs:62-65
|
|||
|
|
var transactionClassify = await transactionsRepository.GetAmountGroupByClassifyAsync(
|
|||
|
|
new DateTime(year, month, 1),
|
|||
|
|
new DateTime(year, month, 1).AddMonths(1)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
### 查询方法分类
|
|||
|
|
|
|||
|
|
| 分类 | 方法数 | 说明 |
|
|||
|
|
|------|--------|------|
|
|||
|
|
| 基础查询 | 2 | 检查记录是否存在(去重) |
|
|||
|
|
| 核心构建器 | 1 | BuildQuery() 私有方法,统一查询逻辑 |
|
|||
|
|
| 分页查询 | 2 | 分页列表 + 总数统计 |
|
|||
|
|
| 分类查询 | 1 | 获取所有不同分类 |
|
|||
|
|
| 邮件相关 | 2 | 按邮件ID查询列表和数量 |
|
|||
|
|
| 未分类查询 | 1 | 获取未分类账单列表 |
|
|||
|
|
| 智能分类 | 1 | 关键词匹配查询 |
|
|||
|
|
| 待确认分类 | 1 | 获取待确认账单列表 |
|
|||
|
|
| 批量更新 | 3 | 批量更新分类和确认操作 |
|
|||
|
|
| 其他仓储查询 | 2 | 预算/分类仓储中的账单查询 |
|
|||
|
|
| 服务层SQL | 3 | AI生成SQL + 归档统计 |
|
|||
|
|
|
|||
|
|
### 关键发现
|
|||
|
|
|
|||
|
|
1. **简化的架构**:新实现移除了复杂的统计方法,专注于核心的CRUD操作和查询功能。
|
|||
|
|
|
|||
|
|
2. **统一的查询构建**:`BuildQuery()` 私有方法(第53-92行)被 `QueryAsync()` 和 `CountAsync()` 共享使用,确保查询逻辑一致性。
|
|||
|
|
|
|||
|
|
3. **去重检查**:`ExistsByEmailMessageIdAsync()` 和 `ExistsByImportNoAsync()` 用于防止重复导入。
|
|||
|
|
|
|||
|
|
4. **灵活的查询条件**:支持按年月、日期范围、交易类型、分类、关键词等多维度筛选。
|
|||
|
|
|
|||
|
|
5. **批量操作优化**:提供批量更新分类、确认待确认记录等高效操作。
|
|||
|
|
|
|||
|
|
6. **服务层SQL保持不变**:AI生成SQL和归档统计等高级查询功能仍然通过 `ExecuteDynamicSqlAsync()` 实现。
|
|||
|
|
|
|||
|
|
### SQL查询模式
|
|||
|
|
|
|||
|
|
所有SQL查询都遵循以下模式:
|
|||
|
|
```sql
|
|||
|
|
SELECT [字段] FROM TransactionRecord
|
|||
|
|
WHERE [条件]
|
|||
|
|
ORDER BY [排序字段]
|
|||
|
|
LIMIT [限制数量]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
常用查询条件:
|
|||
|
|
- `EmailMessageId == ? AND OccurredAt == ?` - 精确匹配去重
|
|||
|
|
- `ImportNo == ? AND ImportFrom == ?` - 导入记录去重
|
|||
|
|
- `Classify != ""` - 已分类记录
|
|||
|
|
- `Classify == "" OR Classify IS NULL` - 未分类记录
|
|||
|
|
- `UnconfirmedClassify != ""` - 待确认记录
|
|||
|
|
- `Reason.Contains(?) OR Classify.Contains(?)` - 关键词搜索
|
|||
|
|
|
|||
|
|
### 字段说明
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| Id | bigint | 主键 |
|
|||
|
|
| Card | nvarchar | 卡号 |
|
|||
|
|
| Reason | nvarchar | 交易原因/摘要 |
|
|||
|
|
| Amount | decimal | 交易金额(支出为负数,收入为正数) |
|
|||
|
|
| OccurredAt | datetime | 交易发生时间 |
|
|||
|
|
| Type | int | 交易类型(0=支出, 1=收入, 2=不计入收支) |
|
|||
|
|
| Classify | nvarchar | 交易分类(空字符串表示未分类) |
|
|||
|
|
| EmailMessageId | bigint | 关联邮件ID |
|
|||
|
|
| ImportNo | nvarchar | 导入编号 |
|
|||
|
|
| ImportFrom | nvarchar | 导入来源 |
|
|||
|
|
| UnconfirmedClassify | nvarchar | 待确认分类 |
|
|||
|
|
| UnconfirmedType | int? | 待确认类型 |
|
|||
|
|
|
|||
|
|
### 接口方法总览
|
|||
|
|
|
|||
|
|
**ITransactionRecordRepository 接口定义(17个方法):**
|
|||
|
|
|
|||
|
|
1. `ExistsByEmailMessageIdAsync()` - 邮件去重检查
|
|||
|
|
2. `ExistsByImportNoAsync()` - 导入去重检查
|
|||
|
|
3. `QueryAsync()` - 分页查询(支持多维度筛选)
|
|||
|
|
4. `CountAsync()` - 总数统计(与QueryAsync条件相同)
|
|||
|
|
5. `GetDistinctClassifyAsync()` - 获取所有分类
|
|||
|
|
6. `GetByEmailIdAsync()` - 按邮件ID查询记录
|
|||
|
|
7. `GetCountByEmailIdAsync()` - 按邮件ID统计数量
|
|||
|
|
8. `GetUnclassifiedAsync()` - 获取未分类记录
|
|||
|
|
9. `GetClassifiedByKeywordsAsync()` - 关键词匹配查询
|
|||
|
|
10. `GetUnconfirmedRecordsAsync()` - 获取待确认记录
|
|||
|
|
11. `BatchUpdateByReasonAsync()` - 按摘要批量更新
|
|||
|
|
12. `UpdateCategoryNameAsync()` - 更新分类名称
|
|||
|
|
13. `ConfirmAllUnconfirmedAsync()` - 确认待确认记录
|
|||
|
|
|
|||
|
|
**私有辅助方法:**
|
|||
|
|
- `BuildQuery()` - 统一查询构建器(被QueryAsync和CountAsync使用)
|