From 6374f55aa1c591410e808727bc58d0a71a5e851a Mon Sep 17 00:00:00 2001 From: clz Date: Sat, 10 Jan 2026 21:18:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20/api/review-stats?= =?UTF-8?q?=20=E7=AB=AF=E7=82=B9=E5=92=8C=E4=BB=AA=E8=A1=A8=E7=9B=98?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=95=B0=E6=8D=AE=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/README.md | 487 ++++++++++++++++++++++++++++++++++++ server/handler/bills.go | 40 +++ server/router/router.go | 3 + web/src/lib/api.ts | 11 + web/src/routes/+page.svelte | 145 +++++++++-- 5 files changed, 668 insertions(+), 18 deletions(-) create mode 100644 server/README.md diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..bdc072f --- /dev/null +++ b/server/README.md @@ -0,0 +1,487 @@ +# BillAI 服务器 API 文档 + +## 概述 + +BillAI 服务器是一个基于 Go 的后端服务,用于处理账单的上传、清洗、分析和存储。提供 REST API 接口供前端调用。 + +**服务地址**: `http://localhost:8080` + +--- + +## API 接口列表 + +### 1. 健康检查 + +**端点**: `GET /health` + +**功能**: 检查服务器健康状态 + +**参数**: 无 + +**响应示例**: +```json +{ + "status": "ok", + "version": "1.0.0" +} +``` + +--- + +## 账单管理 API + +### 2. 上传账单文件 + +**端点**: `POST /api/upload` + +**功能**: 上传支付宝或微信账单文件进行清洗和分析 + +**请求方式**: `multipart/form-data` + +**请求参数**: + +| 参数名 | 类型 | 必需 | 说明 | +|-------|------|------|------| +| file | File | ✓ | 账单文件(CSV格式) | +| type | string | ✓ | 账单类型:`alipay` 或 `wechat` | +| format | string | - | 输出格式:`csv` 或 `json`(默认: csv) | +| year | number | - | 账单年份(用于数据验证) | +| month | number | - | 账单月份(用于数据验证) | + +**响应示例** (成功): +```json +{ + "result": true, + "message": "账单处理完成", + "data": { + "bill_type": "alipay", + "raw_count": 50, + "cleaned_count": 48, + "duplicate_count": 2, + "needs_review_count": 5, + "file_name": "20260110_150405_alipay_1.csv" + } +} +``` + +**响应示例** (全部重复): +```json +{ + "result": true, + "message": "文件中的 50 条记录全部已存在,无需重复导入", + "data": { + "bill_type": "alipay", + "raw_count": 0, + "cleaned_count": 0, + "duplicate_count": 50 + } +} +``` + +**响应示例** (失败): +```json +{ + "result": false, + "message": "账单类型无效,仅支持 alipay 或 wechat" +} +``` + +--- + +### 3. 获取账单列表 + +**端点**: `GET /api/bills` + +**功能**: 获取清洗后的账单数据,支持分页、筛选和排序 + +**请求参数** (Query): + +| 参数名 | 类型 | 必需 | 说明 | +|-------|------|------|------| +| page | number | - | 页码(从1开始,默认: 1) | +| page_size | number | - | 每页数量(默认: 20,最大: 100) | +| start_date | string | - | 开始日期(YYYY-MM-DD 格式) | +| end_date | string | - | 结束日期(YYYY-MM-DD 格式) | +| category | string | - | 交易分类筛选 | +| type | string | - | 账单来源:`alipay`、`wechat` 或 `manual` | +| income_expense | string | - | 收支类型:`收入` 或 `支出` | + +**响应示例**: +```json +{ + "result": true, + "data": { + "total": 150, + "page": 1, + "page_size": 20, + "pages": 8, + "total_expense": 5250.50, + "total_income": 8000.00, + "bills": [ + { + "_id": "507f1f77bcf86cd799439011", + "bill_type": "alipay", + "transaction_id": "2021123456789", + "merchant_order_no": "2021123456", + "time": "2026-01-10T10:30:00Z", + "category": "餐饮美食", + "merchant": "星巴克", + "description": "咖啡", + "income_expense": "支出", + "amount": 28.00, + "pay_method": "支付宝", + "status": "交易成功", + "remark": "员工补贴", + "review_level": "", + "created_at": "2026-01-10T10:30:00Z", + "updated_at": "2026-01-10T10:30:00Z", + "source_file": "20260110_150405_alipay_1.csv", + "upload_batch": "20260110_150405" + } + ] + } +} +``` + +**错误响应**: +```json +{ + "result": false, + "message": "数据库未连接" +} +``` + +--- + +### 4. 手动创建账单 + +**端点**: `POST /api/bills/manual` + +**功能**: 手动添加单条或批量账单,支持去重 + +**请求方式**: `application/json` + +**请求体**: +```json +{ + "bills": [ + { + "time": "2026-01-10 14:30:00", + "category": "餐饮美食", + "merchant": "测试餐厅", + "description": "午餐", + "income_expense": "支出", + "amount": 50.00, + "pay_method": "支付宝", + "status": "交易成功", + "remark": "测试账单" + } + ] +} +``` + +**请求参数说明**: + +| 参数名 | 类型 | 必需 | 说明 | +|-------|------|------|------| +| bills | Array | ✓ | 账单数组 | +| bills[].time | string | ✓ | 交易时间(格式: `YYYY-MM-DD HH:mm:ss`) | +| bills[].category | string | ✓ | 交易分类 | +| bills[].income_expense | string | ✓ | 收支类型:`收入` 或 `支出` | +| bills[].amount | number | ✓ | 金额(>0) | +| bills[].merchant | string | - | 交易对方(可选) | +| bills[].description | string | - | 商品说明 | +| bills[].pay_method | string | - | 支付方式 | +| bills[].status | string | - | 交易状态(默认: `交易成功`) | +| bills[].remark | string | - | 备注 | + +**响应示例** (成功): +```json +{ + "result": true, + "message": "创建成功", + "data": { + "success": 1, + "failed": 0, + "duplicates": 0 + } +} +``` + +**响应示例** (部分失败): +```json +{ + "result": true, + "message": "创建成功", + "data": { + "success": 3, + "failed": 1, + "duplicates": 0 + } +} +``` + +**错误响应**: +```json +{ + "result": false, + "message": "时间格式错误: 2026-01-10" +} +``` + +--- + +### 5. 获取需要复核的记录 + +**端点**: `GET /api/review` + +**功能**: 获取清洗过程中标记为需要人工复核的记录 + +**请求参数** (Query): + +| 参数名 | 类型 | 必需 | 说明 | +|-------|------|------|------| +| file | string | ✓ | 输出文件名(来自上传接口返回值) | +| page | number | - | 页码(默认: 1) | +| page_size | number | - | 每页数量(默认: 20) | + +**响应示例**: +```json +{ + "result": true, + "data": { + "total": 5, + "page": 1, + "page_size": 20, + "high_count": 2, + "low_count": 3, + "records": [ + { + "row": 15, + "time": "2026-01-10 10:30:00", + "merchant": "未知商户", + "amount": 100.00, + "category": "未分类", + "review_level": "HIGH", + "issue": "无法识别的商户名称", + "suggestion": "请手动确认或修改商户名称" + } + ] + } +} +``` + +--- + +### 6. 获取月度统计数据 + +**端点**: `GET /api/monthly-stats` + +**功能**: 获取每个月的收入、支出统计(不受日期筛选影响,返回全部月份数据) + +**请求参数**: 无 + +**响应示例**: +```json +{ + "result": true, + "data": [ + { + "month": "2025-12", + "expense": 8234.50, + "income": 15000.00 + }, + { + "month": "2026-01", + "expense": 5250.50, + "income": 8000.00 + } + ] +} +``` + +**错误响应**: +```json +{ + "result": false, + "message": "数据库未连接" +} +``` + +--- + +## 数据模型 + +### 账单数据 (CleanedBill) + +```go +type CleanedBill struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"_id"` + BillType string `bson:"bill_type" json:"bill_type"` // alipay/wechat/manual + TransactionID string `bson:"transaction_id" json:"transaction_id"` + MerchantOrderNo string `bson:"merchant_order_no" json:"merchant_order_no"` + Time time.Time `bson:"time" json:"time"` + Category string `bson:"category" json:"category"` + Merchant string `bson:"merchant" json:"merchant"` + Description string `bson:"description" json:"description"` + IncomeExpense string `bson:"income_expense" json:"income_expense"` // 收入/支出 + Amount float64 `bson:"amount" json:"amount"` + PayMethod string `bson:"pay_method" json:"pay_method"` + Status string `bson:"status" json:"status"` + Remark string `bson:"remark" json:"remark"` + ReviewLevel string `bson:"review_level" json:"review_level"` // HIGH/LOW/"" + CreatedAt time.Time `bson:"created_at" json:"created_at"` + UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` + SourceFile string `bson:"source_file" json:"source_file"` + UploadBatch string `bson:"upload_batch" json:"upload_batch"` +} +``` + +### 月度统计 (MonthlyStat) + +```go +type MonthlyStat struct { + Month string `bson:"month" json:"month"` // YYYY-MM + Expense float64 `bson:"expense" json:"expense"` // 支出总额 + Income float64 `bson:"income" json:"income"` // 收入总额 +} +``` + +--- + +## 错误处理 + +所有 API 响应都遵循以下格式: + +```json +{ + "result": boolean, + "message": "错误或成功消息", + "data": {} +} +``` + +### 常见错误码 + +| HTTP 状态码 | result | 说明 | +|-----------|--------|------| +| 200 | true | 请求成功 | +| 200 | false | 业务逻辑错误(如参数不合法) | +| 400 | false | 请求参数错误 | +| 404 | false | 资源不存在 | +| 500 | false | 服务器内部错误 | + +--- + +## 时区说明 + +- **存储**: 所有时间在数据库中按 **UTC 时区** 存储(ISO 8601 格式) +- **输入**: 手动添加账单时,时间按 **本地时区** 解析(使用 `time.ParseInLocation`) +- **输出**: API 返回的时间均为 ISO 8601 UTC 格式 + +### 时间格式 + +- 手动创建账单时: `YYYY-MM-DD HH:mm:ss`(本地时间) +- 日期筛选参数: `YYYY-MM-DD`(本地日期) +- API 返回时间: ISO 8601 UTC 格式(如 `2026-01-10T10:30:00Z`) + +--- + +## 使用示例 + +### 上传账单文件 + +```bash +curl -X POST http://localhost:8080/api/upload \ + -F "file=@statement.csv" \ + -F "type=alipay" \ + -F "format=csv" +``` + +### 查询账单列表 + +```bash +curl "http://localhost:8080/api/bills?page=1&page_size=20&start_date=2026-01-01&end_date=2026-01-10&type=alipay" +``` + +### 手动添加账单 + +```bash +curl -X POST http://localhost:8080/api/bills/manual \ + -H "Content-Type: application/json" \ + -d '{ + "bills": [ + { + "time": "2026-01-10 14:30:00", + "category": "餐饮美食", + "merchant": "星巴克", + "income_expense": "支出", + "amount": 28.00, + "pay_method": "支付宝" + } + ] + }' +``` + +### 获取月度统计 + +```bash +curl http://localhost:8080/api/monthly-stats +``` + +--- + +## 配置 + +服务器配置文件位于 `config.yaml`,主要配置项: + +```yaml +server: + port: "8080" + +mongodb: + uri: "mongodb://admin:password@localhost:27017" + database: "billai" + collections: + raw: "bills_raw" + cleaned: "bills_cleaned" + +analyzer: + mode: "http" # http 或 process + url: "http://analyzer:8001" +``` + +--- + +## 开发信息 + +### 目录结构 + +``` +server/ +├── main.go # 程序入口 +├── router/ # 路由配置 +├── handler/ # 请求处理器 +├── service/ # 业务逻辑 +├── repository/ # 数据访问层 +├── database/ # 数据库连接 +├── model/ # 数据模型 +├── adapter/ # 外部服务适配器 +├── config/ # 配置管理 +└── README.md # 本文件 +``` + +### 主要依赖 + +- **Gin**: Web 框架 +- **MongoDB Driver**: 数据库驱动 +- **Go 1.21**: 运行环境 + +--- + +## 版本历史 + +| 版本 | 日期 | 说明 | +|-----|------|------| +| 1.0.0 | 2026-01-10 | 初始版本,支持账单上传、查询、手动添加和统计 | + diff --git a/server/handler/bills.go b/server/handler/bills.go index b873d56..b4189af 100644 --- a/server/handler/bills.go +++ b/server/handler/bills.go @@ -198,3 +198,43 @@ func MonthlyStats(c *gin.Context) { Data: stats, }) } + +// ReviewStats 获取待复核数据统计 +func ReviewStats(c *gin.Context) { + repo := repository.GetRepository() + + // 从MongoDB查询所有需要复核的账单 + bills, err := repo.GetBillsNeedReview() + if err != nil { + c.JSON(http.StatusInternalServerError, model.ReviewResponse{ + Result: false, + Message: "查询失败: " + err.Error(), + }) + return + } + + highCount := 0 + lowCount := 0 + totalCount := 0 + + // 统计各等级的复核记录 + for _, bill := range bills { + totalCount++ + if bill.ReviewLevel == "HIGH" { + highCount++ + } else if bill.ReviewLevel == "LOW" { + lowCount++ + } + } + + c.JSON(http.StatusOK, model.ReviewResponse{ + Result: true, + Message: "获取成功", + Data: &model.ReviewData{ + Total: totalCount, + High: highCount, + Low: lowCount, + Records: nil, // 统计接口不返回具体记录 + }, + }) +} diff --git a/server/router/router.go b/server/router/router.go index bd1b045..1fb8aef 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -55,5 +55,8 @@ func setupAPIRoutes(r *gin.Engine) { // 月度统计(全部数据) api.GET("/monthly-stats", handler.MonthlyStats) + + // 待复核数据统计 + api.GET("/review-stats", handler.ReviewStats) } } diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 8cfe2f7..f5b773f 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -318,3 +318,14 @@ export async function createManualBills(bills: ManualBillInput[]): Promise { + const response = await fetch(`${API_BASE}/api/review-stats`); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + return response.json(); +} diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 495bf51..a563903 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,5 +1,6 @@