diff --git a/.env b/.env index 39e2c22..a74c634 100644 --- a/.env +++ b/.env @@ -19,7 +19,7 @@ ALIYUN_SMS_TEMPLATE_CODE=SMS_324470054 # 环境配置 NODE_ENV=development -PORT=3000 +PORT=3008 # 前端地址配置 FRONTEND_URL=https://www.zrbjr.com/frontend diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/UniappTool.xml b/.idea/UniappTool.xml new file mode 100644 index 0000000..f7328e8 --- /dev/null +++ b/.idea/UniappTool.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..497a8d0 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://114.55.111.44:3306/test_mao + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..14f154b --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jurong_circle_shopping_black.iml b/.idea/jurong_circle_shopping_black.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/jurong_circle_shopping_black.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..42954ec --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..56782ca --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.trae/TODO.md b/.trae/TODO.md deleted file mode 100644 index 8ad817f..0000000 --- a/.trae/TODO.md +++ /dev/null @@ -1,7 +0,0 @@ -# TODO: - -- [x] backend-api: 在 routes/transfers.js 中创建 GET /api/transfers/pending-allocations 接口 (priority: High) -- [x] frontend-tabs: 在 admin/src/views/Transfers.vue 中添加待处理匹配订单标签页 (priority: High) -- [x] frontend-display: 实现待处理匹配订单的数据显示和表格 (priority: High) -- [x] frontend-operations: 添加待处理匹配订单的操作功能(确认、拒绝等) (priority: Medium) -- [x] testing: 测试新功能的完整性和正确性 (priority: Medium) diff --git a/.vercel/project.json b/.vercel/project.json deleted file mode 100644 index d931e9c..0000000 --- a/.vercel/project.json +++ /dev/null @@ -1 +0,0 @@ -{"projectName":"trae_code_bv1k"} \ No newline at end of file diff --git a/API-DOCS-README.md b/API-DOCS-README.md deleted file mode 100644 index a47efc1..0000000 --- a/API-DOCS-README.md +++ /dev/null @@ -1,123 +0,0 @@ -# API文档生成与Apifox同步指南 - -本项目已集成Swagger用于API文档生成,并提供了与Apifox同步的工具脚本。 - -## 1. 查看API文档 - -启动服务器后,可以通过以下URL访问Swagger UI界面查看API文档: - -``` -http://localhost:3000/api-docs -``` - -## 2. 手动导出并导入Apifox - -### 2.1 导出Swagger文档 - -运行以下命令导出Swagger文档: - -```bash -node export-swagger.js -``` - -这将在`api-docs`目录下生成`swagger.json`文件。 - -### 2.2 手动导入Apifox - -1. 打开Apifox应用 -2. 选择您的项目 -3. 点击"导入"按钮 -4. 选择"导入OpenAPI(Swagger)" -5. 选择刚才导出的swagger.json文件 -6. 按照Apifox的导入向导完成导入 - -## 3. 自动同步到Apifox - -### 3.1 安装Apifox CLI - -```bash -npm install -g apifox-cli -``` - -### 3.2 登录Apifox CLI - -```bash -apifox-cli login -``` - -### 3.3 配置项目ID - -编辑`apifox-sync.js`文件,将`YOUR_APIFOX_PROJECT_ID`替换为您的Apifox项目ID。 - -> 获取项目ID:在Apifox网页版中打开项目,URL中包含项目ID - -### 3.4 运行同步脚本 - -```bash -node apifox-sync.js -``` - -## 4. 为API添加Swagger注释 - -为了使API文档保持最新,在添加新的API接口时,请按照以下格式添加Swagger注释: - -```javascript -/** - * @swagger - * /api/resource: - * get: - * summary: 接口描述 - * tags: [资源分类] - * parameters: - * - name: param - * in: query - * description: 参数描述 - * required: true - * schema: - * type: string - * responses: - * 200: - * description: 成功响应 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - */ -router.get('/resource', (req, res) => { - // 实现代码 -}); -``` - -## 5. 定义数据模型 - -在路由文件顶部定义数据模型,例如: - -```javascript -/** - * @swagger - * components: - * schemas: - * ModelName: - * type: object - * properties: - * id: - * type: integer - * name: - * type: string - * required: - * - name - */ -``` - -## 6. 自动化集成 - -可以将API文档生成和同步集成到CI/CD流程中,在每次部署前自动更新API文档。 - ---- - -如有任何问题,请参考[Swagger官方文档](https://swagger.io/docs/)和[Apifox官方文档](https://www.apifox.cn/help/)。 \ No newline at end of file diff --git a/MIGRATION-GUIDE.md b/MIGRATION-GUIDE.md deleted file mode 100644 index 9fbf739..0000000 --- a/MIGRATION-GUIDE.md +++ /dev/null @@ -1,186 +0,0 @@ -# 文件迁移到 MinIO 指南 - -本指南将帮助您将现有的本地静态文件迁移到 MinIO 对象存储。 - -## 迁移前准备 - -### 1. 确保 MinIO 配置正确 - -确保 `.env` 文件中的 MinIO 配置正确: - -```env -MINIO_ENDPOINT=your-minio-server -MINIO_PORT=9000 -MINIO_ACCESS_KEY=your-access-key -MINIO_SECRET_KEY=your-secret-key -MINIO_USE_SSL=false -MINIO_PUBLIC_URL=https://minio.yourdomain.com -``` - -### 2. 确保存储桶已创建 - -确保以下存储桶在 MinIO 中已存在: -- `avatars` - 用户头像 -- `products` - 产品图片 -- `documents` - 文档文件 - -### 3. 备份数据库 - -**重要:在开始迁移前,请务必备份数据库!** - -```bash -mysqldump -u username -p database_name > backup_before_migration.sql -``` - -## 执行迁移 - -### 1. 运行迁移脚本 - -```bash -cd jurong_circle_black -node migrate-to-minio.js -``` - -### 2. 监控迁移过程 - -脚本会显示详细的迁移进度: -- 扫描本地文件 -- 逐个上传到 MinIO -- 更新数据库中的文件路径引用 -- 生成迁移报告 - -## 迁移后验证 - -### 1. 检查迁移报告 - -查看生成的 `migration-report.json` 文件: - -```json -{ - "migrationDate": "2024-01-15T10:30:00.000Z", - "totalFiles": 150, - "successCount": 148, - "failedCount": 2, - "migratedFiles": [...], - "failedFiles": [...] -} -``` - -### 2. 验证文件访问 - -- 检查用户头像是否正常显示 -- 检查产品图片是否正常显示 -- 检查文档下载是否正常 - -### 3. 验证数据库更新 - -检查数据库中的文件路径是否已更新: - -```sql --- 检查用户头像路径 -SELECT id, username, avatar FROM users WHERE avatar LIKE 'https://minio%' LIMIT 10; - --- 检查产品图片路径 -SELECT id, name, image_url FROM products WHERE image_url LIKE 'https://minio%' LIMIT 10; -``` - -## 文件组织结构 - -迁移后,文件将按以下结构组织: - -``` -MinIO 存储桶/ -├── avatars/ -│ ├── 2024/ -│ │ ├── 01/ -│ │ │ ├── 15/ -│ │ │ │ ├── 1640995200000_a1b2c3d4.jpg -│ │ │ │ └── 1640995300000_b2c3d4e5.png -│ │ │ └── 16/ -│ │ └── 02/ -│ └── 2023/ -├── products/ -│ ├── 2024/ -│ │ ├── 01/ -│ │ │ ├── 15/ -│ │ │ └── 16/ -│ │ └── 02/ -│ └── 2023/ -└── documents/ - ├── 2024/ - └── 2023/ -``` - -## 故障排除 - -### 常见问题 - -1. **连接 MinIO 失败** - - 检查 MinIO 服务是否运行 - - 验证网络连接 - - 确认访问密钥正确 - -2. **存储桶不存在** - - 在 MinIO 控制台创建所需的存储桶 - - 确保存储桶名称与配置一致 - -3. **权限问题** - - 确保 MinIO 用户有读写权限 - - 检查存储桶策略设置 - -4. **部分文件迁移失败** - - 查看迁移报告中的失败文件列表 - - 检查文件是否损坏或被占用 - - 手动重新上传失败的文件 - -### 回滚方案 - -如果迁移出现问题,可以通过以下步骤回滚: - -1. **恢复数据库备份** - ```bash - mysql -u username -p database_name < backup_before_migration.sql - ``` - -2. **重新配置文件上传路径** - - 修改 `routes/upload.js` 使用本地存储 - - 确保 `uploads` 目录存在且有正确权限 - -## 迁移完成后的清理 - -### 1. 删除本地文件(可选) - -**警告:只有在确认迁移成功且系统运行正常后才执行此操作!** - -```bash -# 备份 uploads 目录 -mv uploads uploads_backup_$(date +%Y%m%d) - -# 或者直接删除(谨慎操作) -# rm -rf uploads -``` - -### 2. 更新部署脚本 - -更新生产环境的部署脚本,移除对 `uploads` 目录的依赖。 - -### 3. 更新备份策略 - -确保备份策略包含 MinIO 数据的备份。 - -## 注意事项 - -1. **迁移时间**:根据文件数量和大小,迁移可能需要较长时间 -2. **网络稳定性**:确保网络连接稳定,避免迁移中断 -3. **存储空间**:确保 MinIO 有足够的存储空间 -4. **并发限制**:脚本已添加延迟避免过快请求,如需调整可修改代码 -5. **文件路径**:迁移后的文件路径将包含日期文件夹结构 - -## 技术支持 - -如果在迁移过程中遇到问题,请: - -1. 查看控制台输出的错误信息 -2. 检查 `migration-report.json` 中的详细信息 -3. 确保 MinIO 服务正常运行 -4. 验证网络连接和权限设置 \ No newline at end of file diff --git a/api-docs/swagger.json b/api-docs/swagger.json deleted file mode 100644 index 066f91c..0000000 --- a/api-docs/swagger.json +++ /dev/null @@ -1,8639 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "融豆商城 API", - "version": "1.0.0", - "description": "融豆商城后端API文档", - "contact": { - "name": "技术支持", - "email": "support@example.com" - } - }, - "servers": [ - { - "url": "/api", - "description": "API服务器" - } - ], - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "Announcement": { - "type": "object", - "required": [ - "title", - "content", - "type", - "priority" - ], - "properties": { - "id": { - "type": "integer", - "description": "公告ID", - "example": 1 - }, - "title": { - "type": "string", - "description": "公告标题", - "example": "系统维护通知" - }, - "content": { - "type": "string", - "description": "公告内容", - "example": "系统将于今晚进行维护,预计维护时间2小时,期间可能影响部分功能使用。" - }, - "type": { - "type": "string", - "description": "公告类型", - "enum": [ - "system", - "maintenance", - "promotion", - "warning" - ], - "example": "maintenance" - }, - "priority": { - "type": "string", - "description": "优先级", - "enum": [ - "low", - "medium", - "high", - "urgent" - ], - "example": "high" - }, - "status": { - "type": "string", - "description": "状态", - "enum": [ - "draft", - "published", - "archived" - ], - "example": "published" - }, - "isTop": { - "type": "boolean", - "description": "是否置顶", - "example": false - }, - "publishTime": { - "type": "string", - "format": "date-time", - "description": "发布时间", - "example": "2024-01-15T10:00:00Z" - }, - "expireTime": { - "type": "string", - "format": "date-time", - "description": "过期时间", - "example": "2024-01-20T10:00:00Z" - }, - "createdBy": { - "type": "integer", - "description": "创建者用户ID", - "example": 1 - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "创建时间", - "example": "2024-01-15T09:00:00Z" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "更新时间", - "example": "2024-01-15T09:30:00Z" - }, - "creator": { - "type": "object", - "description": "创建者信息", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "username": { - "type": "string", - "example": "admin" - }, - "email": { - "type": "string", - "example": "admin@example.com" - } - } - }, - "is_pinned": { - "type": "boolean", - "description": "是否置顶" - }, - "publish_time": { - "type": "string", - "format": "date-time", - "description": "发布时间" - }, - "expire_time": { - "type": "string", - "format": "date-time", - "description": "过期时间" - }, - "created_by": { - "type": "integer", - "description": "创建者ID" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - } - } - }, - "AnnouncementCreate": { - "type": "object", - "required": [ - "title", - "content", - "type", - "priority" - ], - "properties": { - "title": { - "type": "string", - "description": "公告标题", - "example": "系统维护通知" - }, - "content": { - "type": "string", - "description": "公告内容", - "example": "系统将于今晚进行维护,预计维护时间2小时。" - }, - "type": { - "type": "string", - "description": "公告类型", - "enum": [ - "system", - "activity", - "maintenance", - "urgent" - ], - "example": "maintenance" - }, - "priority": { - "type": "string", - "description": "优先级", - "enum": [ - "high", - "medium", - "low" - ], - "example": "high" - }, - "status": { - "type": "string", - "description": "公告状态", - "enum": [ - "draft", - "published" - ], - "default": "draft", - "example": "draft" - }, - "isTop": { - "type": "boolean", - "description": "是否置顶", - "default": false, - "example": false - }, - "publishTime": { - "type": "string", - "format": "date-time", - "description": "发布时间", - "example": "2024-01-15T10:00:00Z" - }, - "expireTime": { - "type": "string", - "format": "date-time", - "description": "过期时间", - "example": "2024-01-20T10:00:00Z" - } - } - }, - "AnnouncementUpdate": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "公告标题", - "example": "系统维护通知(更新)" - }, - "content": { - "type": "string", - "description": "公告内容", - "example": "系统维护时间调整为明晚进行。" - }, - "type": { - "type": "string", - "description": "公告类型", - "enum": [ - "system", - "activity", - "maintenance", - "urgent" - ], - "example": "maintenance" - }, - "priority": { - "type": "string", - "description": "优先级", - "enum": [ - "high", - "medium", - "low" - ], - "example": "medium" - }, - "status": { - "type": "string", - "description": "公告状态", - "enum": [ - "draft", - "published", - "expired" - ], - "example": "published" - }, - "isTop": { - "type": "boolean", - "description": "是否置顶", - "example": true - }, - "publishTime": { - "type": "string", - "format": "date-time", - "description": "发布时间", - "example": "2024-01-16T10:00:00Z" - }, - "expireTime": { - "type": "string", - "format": "date-time", - "description": "过期时间", - "example": "2024-01-21T10:00:00Z" - } - } - }, - "AnnouncementList": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "announcements": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Announcement" - } - }, - "total": { - "type": "integer", - "description": "总记录数", - "example": 50 - }, - "page": { - "type": "integer", - "description": "当前页码", - "example": 1 - }, - "limit": { - "type": "integer", - "description": "每页数量", - "example": 10 - }, - "totalPages": { - "type": "integer", - "description": "总页数", - "example": 5 - } - } - } - } - }, - "AnnouncementResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "操作成功" - }, - "data": { - "$ref": "#/components/schemas/Announcement" - } - } - }, - "AnnouncementError": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "操作失败" - }, - "error": { - "type": "string", - "example": "公告不存在" - } - } - }, - "CartItem": { - "type": "object", - "required": [ - "user_id", - "product_id", - "quantity" - ], - "properties": { - "id": { - "type": "integer", - "description": "购物车项ID" - }, - "user_id": { - "type": "integer", - "description": "用户ID" - }, - "product_id": { - "type": "integer", - "description": "商品ID" - }, - "quantity": { - "type": "integer", - "description": "商品数量" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - }, - "spec_combination_id": { - "type": "integer", - "description": "商品规格组合ID" - }, - "product": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "price": { - "type": "integer" - }, - "points_price": { - "type": "integer" - }, - "rongdou_price": { - "type": "integer" - }, - "image_url": { - "type": "string" - }, - "stock": { - "type": "integer" - }, - "status": { - "type": "string" - } - } - } - } - }, - "CartItemWithProduct": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "购物车商品ID" - }, - "product_id": { - "type": "integer", - "description": "商品ID" - }, - "product_name": { - "type": "string", - "description": "商品名称" - }, - "quantity": { - "type": "integer", - "description": "商品数量" - }, - "points_price": { - "type": "integer", - "description": "积分价格" - }, - "rongdou_price": { - "type": "number", - "description": "融豆价格" - }, - "image_url": { - "type": "string", - "description": "商品图片URL" - }, - "stock": { - "type": "integer", - "description": "库存数量" - }, - "payment_methods": { - "type": "array", - "items": { - "type": "string" - }, - "description": "支付方式列表" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - } - } - }, - "AddToCartRequest": { - "type": "object", - "required": [ - "product_id", - "quantity" - ], - "properties": { - "product_id": { - "type": "integer", - "description": "商品ID" - }, - "quantity": { - "type": "integer", - "minimum": 1, - "description": "商品数量" - } - } - }, - "UpdateCartRequest": { - "type": "object", - "required": [ - "quantity" - ], - "properties": { - "quantity": { - "type": "integer", - "minimum": 1, - "description": "商品数量" - } - } - }, - "Order": { - "type": "object", - "required": [ - "user_id", - "total_amount", - "status" - ], - "properties": { - "id": { - "type": "integer", - "description": "订单ID" - }, - "order_number": { - "type": "string", - "description": "订单号" - }, - "user_id": { - "type": "integer", - "description": "用户ID" - }, - "total_amount": { - "type": "number", - "description": "订单总金额" - }, - "total_points": { - "type": "integer", - "description": "订单总积分" - }, - "total_rongdou": { - "type": "number", - "description": "订单总融豆" - }, - "status": { - "type": "string", - "description": "订单状态", - "enum": [ - "pending", - "confirmed", - "shipped", - "delivered", - "cancelled" - ] - }, - "payment_status": { - "type": "string", - "description": "支付状态", - "enum": [ - "pending", - "paid", - "failed", - "refunded" - ] - }, - "shipping_address": { - "type": "string", - "description": "收货地址" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - } - } - }, - "OrderItem": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "订单商品ID" - }, - "order_id": { - "type": "integer", - "description": "订单ID" - }, - "product_id": { - "type": "integer", - "description": "商品ID" - }, - "quantity": { - "type": "integer", - "description": "商品数量" - }, - "price": { - "type": "number", - "description": "商品价格" - }, - "points_price": { - "type": "integer", - "description": "积分价格" - }, - "rongdou_price": { - "type": "number", - "description": "融豆价格" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - } - } - }, - "PreOrder": { - "type": "object", - "properties": { - "preOrderId": { - "type": "integer", - "description": "预订单ID" - }, - "orderNumber": { - "type": "string", - "description": "订单号" - }, - "totalAmount": { - "type": "number", - "description": "总金额" - }, - "totalPoints": { - "type": "integer", - "description": "所需积分总数" - }, - "totalRongdou": { - "type": "number", - "description": "所需融豆总数" - }, - "paymentMethods": { - "type": "array", - "items": { - "type": "string" - }, - "description": "去重后的支付方式列表" - } - } - }, - "Product": { - "type": "object", - "required": [ - "name", - "points_price", - "stock" - ], - "properties": { - "id": { - "type": "integer", - "description": "商品ID" - }, - "name": { - "type": "string", - "description": "商品名称" - }, - "category": { - "type": "string", - "description": "商品分类" - }, - "points_price": { - "type": "integer", - "description": "积分价格" - }, - "rongdou_price": { - "type": "number", - "description": "融豆价格" - }, - "stock": { - "type": "integer", - "description": "库存数量" - }, - "image_url": { - "type": "string", - "description": "商品图片URL" - }, - "description": { - "type": "string", - "description": "商品描述" - }, - "status": { - "type": "string", - "description": "商品状态", - "enum": [ - "active", - "inactive" - ] - }, - "payment_methods": { - "type": "array", - "items": { - "type": "string" - }, - "description": "支付方式列表" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - } - } - }, - "User": { - "type": "object", - "required": [ - "username", - "password", - "real_name", - "id_card" - ], - "properties": { - "id": { - "type": "integer", - "description": "用户ID" - }, - "username": { - "type": "string", - "description": "用户名" - }, - "email": { - "type": "string", - "format": "email", - "description": "邮箱地址" - }, - "phone": { - "type": "string", - "description": "手机号" - }, - "points": { - "type": "integer", - "description": "用户积分" - }, - "rongdou": { - "type": "number", - "description": "融豆余额" - }, - "avatar": { - "type": "string", - "description": "用户头像URL" - }, - "status": { - "type": "string", - "description": "用户状态", - "enum": [ - "active", - "inactive", - "banned" - ] - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - }, - "role": { - "type": "string", - "description": "用户角色", - "enum": [ - "user", - "admin", - "merchant" - ] - }, - "real_name": { - "type": "string", - "description": "真实姓名" - }, - "id_card": { - "type": "string", - "description": "身份证号" - }, - "is_system_account": { - "type": "boolean", - "description": "是否为系统账户" - }, - "is_distribute": { - "type": "boolean", - "description": "是否为分发账户" - } - } - }, - "UserProfile": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "用户ID" - }, - "username": { - "type": "string", - "description": "用户名" - }, - "email": { - "type": "string", - "description": "邮箱地址" - }, - "phone": { - "type": "string", - "description": "手机号码" - }, - "points": { - "type": "integer", - "description": "积分余额" - }, - "rongdou": { - "type": "number", - "description": "融豆余额" - }, - "avatar": { - "type": "string", - "description": "头像URL" - } - } - }, - "LoginRequest": { - "type": "object", - "required": [ - "username", - "password" - ], - "properties": { - "username": { - "type": "string", - "description": "用户名或邮箱" - }, - "password": { - "type": "string", - "description": "密码" - } - } - }, - "RegisterRequest": { - "type": "object", - "required": [ - "username", - "phone", - "password", - "registrationCode", - "city", - "district_id", - "captchaId", - "captchaText", - "smsCode" - ], - "properties": { - "username": { - "type": "string", - "description": "用户名" - }, - "email": { - "type": "string", - "format": "email", - "description": "邮箱地址" - }, - "password": { - "type": "string", - "description": "密码" - }, - "phone": { - "type": "string", - "description": "手机号" - }, - "registrationCode": { - "type": "string", - "description": "注册激活码" - }, - "city": { - "type": "string", - "description": "城市" - }, - "district_id": { - "type": "string", - "description": "区域ID" - }, - "captchaId": { - "type": "string", - "description": "图形验证码ID" - }, - "captchaText": { - "type": "string", - "description": "图形验证码文本" - }, - "smsCode": { - "type": "string", - "description": "短信验证码" - }, - "role": { - "type": "string", - "description": "用户角色", - "default": "user" - } - } - }, - "Address": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "地址ID" - }, - "user_id": { - "type": "integer", - "description": "用户ID" - }, - "recipient_name": { - "type": "string", - "description": "收件人姓名" - }, - "phone": { - "type": "string", - "description": "联系电话" - }, - "province_code": { - "type": "string", - "description": "省份编码" - }, - "province_name": { - "type": "string", - "description": "省份名称" - }, - "city_code": { - "type": "string", - "description": "城市编码" - }, - "city_name": { - "type": "string", - "description": "城市名称" - }, - "district_code": { - "type": "string", - "description": "区县编码" - }, - "district_name": { - "type": "string", - "description": "区县名称" - }, - "detailed_address": { - "type": "string", - "description": "详细地址" - }, - "postal_code": { - "type": "string", - "description": "邮政编码" - }, - "label_id": { - "type": "integer", - "description": "地址标签ID" - }, - "is_default": { - "type": "boolean", - "description": "是否为默认地址" - }, - "label_name": { - "type": "string", - "description": "标签名称" - }, - "label_color": { - "type": "string", - "description": "标签颜色" - } - }, - "required": [ - "recipient_name", - "phone", - "province_code", - "province_name", - "city_code", - "city_name", - "district_code", - "district_name", - "detailed_address" - ] - }, - "LoginCredentials": { - "type": "object", - "required": [ - "username", - "password" - ], - "properties": { - "username": { - "type": "string", - "description": "用户名或手机号" - }, - "password": { - "type": "string", - "description": "密码" - } - } - }, - "MatchingOrder": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "匹配订单ID" - }, - "initiator_id": { - "type": "integer", - "description": "发起人ID" - }, - "matching_type": { - "type": "string", - "enum": [ - "small", - "large" - ], - "description": "匹配类型(小额或大额)" - }, - "amount": { - "type": "number", - "description": "匹配总金额" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "matching", - "completed", - "failed" - ], - "description": "订单状态" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - } - } - }, - "Allocation": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "分配ID" - }, - "from_user_id": { - "type": "integer", - "description": "发送方用户ID" - }, - "to_user_id": { - "type": "integer", - "description": "接收方用户ID" - }, - "amount": { - "type": "number", - "description": "分配金额" - }, - "cycle_number": { - "type": "integer", - "description": "轮次编号" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "confirmed", - "rejected", - "cancelled" - ], - "description": "分配状态" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - } - } - }, - "UnreasonableMatch": { - "type": "object", - "properties": { - "allocation_id": { - "type": "integer", - "description": "分配ID" - }, - "from_user_id": { - "type": "integer", - "description": "发送方用户ID" - }, - "to_user_id": { - "type": "integer", - "description": "接收方用户ID" - }, - "amount": { - "type": "number", - "description": "分配金额" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "confirmed", - "rejected", - "cancelled" - ], - "description": "分配状态" - }, - "to_username": { - "type": "string", - "description": "接收方用户名" - }, - "to_user_balance": { - "type": "number", - "description": "接收方用户余额" - }, - "from_username": { - "type": "string", - "description": "发送方用户名" - }, - "from_user_balance": { - "type": "number", - "description": "发送方用户余额" - } - } - }, - "PointsHistory": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "积分历史记录ID" - }, - "points_change": { - "type": "integer", - "description": "积分变动数量" - }, - "type": { - "type": "string", - "description": "积分变动类型(earn-获得, spend-消费, admin_adjust-管理员调整)" - }, - "description": { - "type": "string", - "description": "积分变动描述" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - } - } - }, - "Region": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "地区编码" - }, - "name": { - "type": "string", - "description": "地区名称" - } - } - }, - "ZhejiangRegion": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "地区ID" - }, - "city_name": { - "type": "string", - "description": "城市名称" - }, - "district_name": { - "type": "string", - "description": "区县名称" - }, - "region_code": { - "type": "string", - "description": "地区编码" - }, - "is_available": { - "type": "integer", - "description": "是否可用(1:可用 0:不可用)" - } - } - }, - "SMSVerification": { - "type": "object", - "properties": { - "phone": { - "type": "string", - "description": "手机号码" - }, - "code": { - "type": "string", - "description": "验证码" - } - } - }, - "SpecName": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "description": "规格名称(如:颜色、尺寸)" - }, - "display_name": { - "type": "string", - "description": "显示名称" - }, - "sort_order": { - "type": "integer", - "description": "排序" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - }, - "SpecValue": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "spec_name_id": { - "type": "integer" - }, - "value": { - "type": "string", - "description": "规格值(如:红色、XL)" - }, - "display_value": { - "type": "string" - }, - "color_code": { - "type": "string", - "description": "颜色代码" - }, - "image_url": { - "type": "string" - }, - "sort_order": { - "type": "integer" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - }, - "Transfer": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "转账记录ID" - }, - "user_id": { - "type": "integer", - "description": "用户ID" - }, - "recipient_id": { - "type": "integer", - "description": "接收方用户ID" - }, - "amount": { - "type": "number", - "format": "float", - "description": "转账金额" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "completed", - "failed", - "cancelled" - ], - "description": "转账状态" - }, - "transfer_type": { - "type": "string", - "enum": [ - "user_to_user", - "user_to_system", - "system_to_user" - ], - "description": "转账类型" - }, - "voucher_image": { - "type": "string", - "description": "转账凭证图片路径" - }, - "remark": { - "type": "string", - "description": "转账备注" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "更新时间" - } - } - }, - "Pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "description": "总记录数" - }, - "page": { - "type": "integer", - "description": "当前页码" - }, - "limit": { - "type": "integer", - "description": "每页记录数" - }, - "total_pages": { - "type": "integer", - "description": "总页数" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/api/announcements/{id}": { - "get": { - "summary": "获取单个公告详情", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "公告ID" - } - ], - "responses": { - "200": { - "description": "成功获取公告详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "$ref": "#/components/schemas/Announcement" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公告不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "put": { - "summary": "更新通知公告", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "公告ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "公告标题" - }, - "content": { - "type": "string", - "description": "公告内容" - }, - "type": { - "type": "string", - "enum": [ - "system", - "activity", - "maintenance", - "urgent" - ], - "description": "公告类型" - }, - "priority": { - "type": "string", - "enum": [ - "high", - "medium", - "low", - "urgent" - ], - "description": "优先级" - }, - "status": { - "type": "string", - "enum": [ - "draft", - "published", - "expired" - ], - "description": "状态" - }, - "is_pinned": { - "type": "boolean" - }, - "publish_time": { - "type": "string", - "format": "date-time" - }, - "expire_time": { - "type": "string", - "format": "date-time" - }, - "isTop": { - "type": "boolean", - "description": "是否置顶" - }, - "publishTime": { - "type": "string", - "format": "date-time", - "description": "发布时间" - }, - "expireTime": { - "type": "string", - "format": "date-time", - "description": "过期时间" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "公告更新成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "公告更新成功" - }, - "data": { - "$ref": "#/components/schemas/Announcement" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公告不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "delete": { - "summary": "删除通知公告", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "公告ID" - } - ], - "responses": { - "200": { - "description": "公告删除成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "公告删除成功" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公告不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements/{id}/read": { - "post": { - "summary": "标记公告为已读", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "公告ID" - } - ], - "responses": { - "200": { - "description": "标记已读成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "已标记为已读" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公告不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements/unread/count": { - "get": { - "summary": "获取用户未读公告数量", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "获取未读数量成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "unread_count": { - "type": "integer", - "example": 5, - "description": "未读公告数量" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements/batch/read": { - "post": { - "summary": "批量标记公告为已读", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "announcement_ids" - ], - "properties": { - "announcement_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "example": [ - 1, - 2, - 3 - ], - "description": "公告ID列表" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "批量标记已读成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "批量标记已读成功" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements": { - "post": { - "summary": "创建新的通知公告", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "title", - "content", - "type", - "priority" - ], - "properties": { - "title": { - "type": "string", - "description": "公告标题", - "example": "系统维护通知" - }, - "content": { - "type": "string", - "description": "公告内容", - "example": "系统将于今晚进行维护,预计维护时间2小时" - }, - "type": { - "type": "string", - "enum": [ - "system", - "activity", - "maintenance", - "urgent" - ], - "default": "system", - "description": "公告类型", - "example": "maintenance" - }, - "priority": { - "type": "string", - "enum": [ - "high", - "medium", - "low", - "urgent" - ], - "default": "medium", - "description": "优先级", - "example": "high" - }, - "status": { - "type": "string", - "enum": [ - "draft", - "published" - ], - "default": "draft", - "description": "状态" - }, - "is_pinned": { - "type": "boolean", - "default": false - }, - "publish_time": { - "type": "string", - "format": "date-time" - }, - "expire_time": { - "type": "string", - "format": "date-time" - }, - "isTop": { - "type": "boolean", - "default": false, - "description": "是否置顶" - }, - "publishTime": { - "type": "string", - "format": "date-time", - "description": "发布时间" - }, - "expireTime": { - "type": "string", - "format": "date-time", - "description": "过期时间" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "公告创建成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "公告创建成功" - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "integer" - } - }, - "$ref": "#/components/schemas/Announcement" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - }, - "get": { - "summary": "获取通知公告列表", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "搜索关键词(标题或内容)" - }, - { - "in": "query", - "name": "type", - "schema": { - "type": "string", - "enum": [ - "system", - "activity", - "maintenance", - "urgent" - ] - }, - "description": "公告类型" - }, - { - "in": "query", - "name": "priority", - "schema": { - "type": "string", - "enum": [ - "high", - "medium", - "low" - ] - }, - "description": "优先级" - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string", - "enum": [ - "draft", - "published", - "expired" - ] - }, - "description": "状态" - }, - { - "in": "query", - "name": "isTop", - "schema": { - "type": "boolean" - }, - "description": "是否置顶" - }, - { - "in": "query", - "name": "sortBy", - "schema": { - "type": "string", - "enum": [ - "created_at", - "updated_at", - "publish_time", - "priority" - ], - "default": "created_at" - }, - "description": "排序字段" - }, - { - "in": "query", - "name": "sortOrder", - "schema": { - "type": "string", - "enum": [ - "ASC", - "DESC" - ], - "default": "DESC" - }, - "description": "排序方向" - } - ], - "responses": { - "200": { - "description": "成功获取公告列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "announcements": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Announcement" - } - }, - "total": { - "type": "integer", - "example": 50 - }, - "page": { - "type": "integer", - "example": 1 - }, - "limit": { - "type": "integer", - "example": 10 - }, - "totalPages": { - "type": "integer", - "example": 5 - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements/public/list": { - "get": { - "summary": "获取公开发布的公告列表(无需认证)", - "tags": [ - "Announcements" - ], - "parameters": [ - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 5 - }, - "description": "获取数量" - } - ], - "responses": { - "200": { - "description": "成功获取公开公告列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Announcement" - } - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/announcements/{id}/toggle-top": { - "put": { - "summary": "切换公告置顶状态", - "tags": [ - "Announcements" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "公告ID" - } - ], - "responses": { - "200": { - "description": "置顶状态切换成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "置顶状态更新成功" - }, - "data": { - "type": "object", - "properties": { - "isTop": { - "type": "boolean", - "example": true - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公告不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders": { - "get": { - "summary": "获取订单列表", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "搜索关键词" - }, - { - "in": "query", - "name": "orderNumber", - "schema": { - "type": "string" - }, - "description": "订单号" - }, - { - "in": "query", - "name": "username", - "schema": { - "type": "string" - }, - "description": "用户名" - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string" - }, - "description": "订单状态" - }, - { - "in": "query", - "name": "startDate", - "schema": { - "type": "string", - "format": "date" - }, - "description": "开始日期" - }, - { - "in": "query", - "name": "endDate", - "schema": { - "type": "string", - "format": "date" - }, - "description": "结束日期" - } - ], - "responses": { - "200": { - "description": "成功获取订单列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "orders": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Order" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "pages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/confirm": { - "post": { - "summary": "确认下单", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "pre_order_id", - "address" - ], - "properties": { - "pre_order_id": { - "type": "integer", - "description": "预订单ID" - }, - "address": { - "type": "object", - "properties": { - "recipient_name": { - "type": "string", - "description": "收货人姓名" - }, - "phone": { - "type": "string", - "description": "收货人电话" - }, - "province": { - "type": "string", - "description": "省份" - }, - "city": { - "type": "string", - "description": "城市" - }, - "district": { - "type": "string", - "description": "区县" - }, - "detail_address": { - "type": "string", - "description": "详细地址" - } - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "确认下单成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "order_id": { - "type": "integer" - }, - "order_no": { - "type": "string" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "预订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/pre-order/{id}": { - "get": { - "summary": "获取预订单详情", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "预订单ID" - } - ], - "responses": { - "200": { - "description": "获取预订单详情成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "order_no": { - "type": "string" - }, - "total_amount": { - "type": "integer" - }, - "total_points": { - "type": "integer" - }, - "total_rongdou": { - "type": "integer" - }, - "status": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "product_id": { - "type": "integer" - }, - "product_name": { - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "price": { - "type": "integer" - }, - "points_price": { - "type": "integer" - }, - "rongdou_price": { - "type": "integer" - }, - "spec_info": { - "type": "object" - } - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "预订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/{id}": { - "get": { - "summary": "获取单个订单详情", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "订单ID" - } - ], - "responses": { - "200": { - "description": "成功获取订单详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "order": { - "$ref": "#/components/schemas/Order" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/products": { - "get": { - "summary": "获取商品列表", - "tags": [ - "Products" - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "搜索关键词" - }, - { - "in": "query", - "name": "category", - "schema": { - "type": "string" - }, - "description": "商品分类" - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "description": "商品状态" - } - ], - "responses": { - "200": { - "description": "成功获取商品列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "products": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Product" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "pages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/products/categories": { - "get": { - "summary": "获取商品分类列表", - "tags": [ - "Products" - ], - "responses": { - "200": { - "description": "成功获取分类列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "/products/hot": { - "get": { - "summary": "获取热门商品", - "tags": [ - "Products" - ], - "parameters": [ - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "返回数量" - } - ], - "responses": { - "200": { - "description": "成功获取热门商品", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "products": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Product" - } - } - } - } - } - } - } - } - } - } - } - }, - "/products/{id}": { - "get": { - "summary": "获取单个商品详情(包含增强规格信息)", - "tags": [ - "Products" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "商品ID" - } - ], - "responses": { - "200": { - "description": "成功获取商品详情,包含完整的规格信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "$ref": "#/components/schemas/Product", - "type": "object", - "properties": { - "product": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "category": { - "type": "string" - }, - "price": { - "type": "number" - }, - "points_price": { - "type": "number" - }, - "rongdou_price": { - "type": "number" - }, - "stock": { - "type": "integer" - }, - "specifications": { - "type": "array", - "description": "商品规格组合列表(笛卡尔积规格系统)", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "规格组合ID" - }, - "combination_key": { - "type": "string", - "description": "规格组合键(如:1-3-5)" - }, - "spec_display": { - "type": "string", - "description": "规格显示文本(如:颜色:红色 | 尺寸:XL)" - }, - "spec_details": { - "type": "array", - "description": "规格详细信息", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "spec_name": { - "type": "string", - "description": "规格名称" - }, - "spec_display_name": { - "type": "string", - "description": "规格显示名称" - }, - "value": { - "type": "string", - "description": "规格值" - }, - "display_value": { - "type": "string", - "description": "规格显示值" - }, - "color_code": { - "type": "string", - "description": "颜色代码" - }, - "image_url": { - "type": "string", - "description": "规格图片" - } - } - } - }, - "price_adjustment": { - "type": "number", - "description": "价格调整" - }, - "points_adjustment": { - "type": "number", - "description": "积分调整" - }, - "rongdou_adjustment": { - "type": "number", - "description": "融豆调整" - }, - "stock": { - "type": "integer", - "description": "规格库存" - }, - "sku_code": { - "type": "string", - "description": "SKU编码" - }, - "barcode": { - "type": "string", - "description": "条形码" - }, - "weight": { - "type": "number", - "description": "重量" - }, - "volume": { - "type": "number", - "description": "体积" - }, - "actual_price": { - "type": "number", - "description": "实际价格(基础价格+调整)" - }, - "actual_points_price": { - "type": "number", - "description": "实际积分价格" - }, - "actual_rongdou_price": { - "type": "number", - "description": "实际融豆价格" - }, - "is_available": { - "type": "boolean", - "description": "是否有库存" - } - } - } - }, - "specification_count": { - "type": "integer", - "description": "规格总数" - }, - "available_specifications": { - "type": "integer", - "description": "有库存的规格数量" - }, - "attributes": { - "type": "array", - "description": "商品属性" - }, - "isFavorited": { - "type": "boolean", - "description": "是否已收藏" - } - } - } - } - } - } - } - } - } - }, - "404": { - "description": "商品不存在" - } - } - } - }, - "/addresses": { - "get": { - "summary": "获取用户收货地址列表", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取地址列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Address" - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - }, - "post": { - "summary": "创建收货地址", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "recipient_name": { - "type": "string", - "description": "收件人姓名" - }, - "phone": { - "type": "string", - "description": "联系电话" - }, - "province_code": { - "type": "string", - "description": "省份编码" - }, - "province_name": { - "type": "string", - "description": "省份名称" - }, - "city_code": { - "type": "string", - "description": "城市编码" - }, - "city_name": { - "type": "string", - "description": "城市名称" - }, - "district_code": { - "type": "string", - "description": "区县编码" - }, - "district_name": { - "type": "string", - "description": "区县名称" - }, - "detailed_address": { - "type": "string", - "description": "详细地址" - }, - "postal_code": { - "type": "string", - "description": "邮政编码" - }, - "label_id": { - "type": "integer", - "description": "地址标签ID" - }, - "is_default": { - "type": "boolean", - "description": "是否为默认地址" - } - }, - "required": [ - "recipient_name", - "phone", - "province_code", - "province_name", - "city_code", - "city_name", - "district_code", - "district_name", - "detailed_address" - ] - } - } - } - }, - "responses": { - "201": { - "description": "地址创建成功" - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/addresses/{id}": { - "get": { - "summary": "获取单个收货地址详情", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "地址ID" - } - ], - "responses": { - "200": { - "description": "成功获取地址详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "$ref": "#/components/schemas/Address" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "地址不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "put": { - "summary": "更新收货地址", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "地址ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "recipient_name": { - "type": "string", - "description": "收件人姓名" - }, - "phone": { - "type": "string", - "description": "联系电话" - }, - "province_code": { - "type": "string", - "description": "省份编码" - }, - "province_name": { - "type": "string", - "description": "省份名称" - }, - "city_code": { - "type": "string", - "description": "城市编码" - }, - "city_name": { - "type": "string", - "description": "城市名称" - }, - "district_code": { - "type": "string", - "description": "区县编码" - }, - "district_name": { - "type": "string", - "description": "区县名称" - }, - "detailed_address": { - "type": "string", - "description": "详细地址" - }, - "postal_code": { - "type": "string", - "description": "邮政编码" - }, - "label_id": { - "type": "integer", - "description": "地址标签ID" - }, - "is_default": { - "type": "boolean", - "description": "是否为默认地址" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "地址更新成功" - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "地址不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "delete": { - "summary": "删除收货地址", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "地址ID" - } - ], - "responses": { - "200": { - "description": "地址删除成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "收货地址删除成功" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "地址不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/addresses/{id}/default": { - "put": { - "summary": "设置默认地址", - "tags": [ - "Addresses" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "地址ID" - } - ], - "responses": { - "200": { - "description": "默认地址设置成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "默认地址设置成功" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "地址不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/auth/register": { - "post": { - "summary": "用户注册", - "description": "需要提供有效的激活码才能注册", - "tags": [ - "Authentication" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegisterRequest" - } - } - } - }, - "responses": { - "201": { - "description": "用户注册成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "token": { - "type": "string", - "description": "JWT认证令牌" - }, - "user": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "role": { - "type": "string" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/auth/login": { - "post": { - "summary": "用户登录", - "tags": [ - "Authentication" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginCredentials" - } - } - } - }, - "responses": { - "200": { - "description": "登录成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "token": { - "type": "string", - "description": "JWT认证令牌" - }, - "user": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "role": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "points": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "用户名或密码错误" - }, - "403": { - "description": "账户审核未通过" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/captcha/generate": { - "get": { - "summary": "生成图形验证码", - "tags": [ - "Captcha" - ], - "responses": { - "200": { - "description": "成功生成验证码", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "captchaId": { - "type": "string", - "description": "验证码唯一ID" - }, - "image": { - "type": "string", - "description": "Base64编码的SVG验证码图片" - } - } - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/captcha/verify": { - "post": { - "summary": "验证用户输入的验证码", - "tags": [ - "Captcha" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "captchaId", - "captchaText" - ], - "properties": { - "captchaId": { - "type": "string", - "description": "验证码唯一ID" - }, - "captchaText": { - "type": "string", - "description": "用户输入的验证码" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "验证码验证成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "验证码验证成功" - } - } - } - } - } - }, - "400": { - "description": "验证码错误或已过期", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "验证码错误" - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart": { - "get": { - "summary": "获取购物车列表", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "获取购物车成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CartItem" - } - }, - "total_count": { - "type": "integer", - "description": "购物车商品总数量" - }, - "total_amount": { - "type": "integer", - "description": "购物车总金额" - }, - "total_points": { - "type": "integer", - "description": "购物车总积分" - }, - "total_rongdou": { - "type": "integer", - "description": "购物车总融豆" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - }, - "post": { - "summary": "添加商品到购物车", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "product_id": { - "type": "integer", - "description": "商品ID" - }, - "quantity": { - "type": "integer", - "description": "商品数量", - "minimum": 1 - }, - "spec_combination_id": { - "type": "integer", - "description": "商品规格组合ID(可选)" - } - }, - "required": [ - "product_id", - "quantity" - ] - } - } - } - }, - "responses": { - "201": { - "description": "添加到购物车成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "cart_item_id": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误或库存不足" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "商品不存在或已下架" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart/{id}": { - "put": { - "summary": "更新购物车商品数量", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "购物车项ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "quantity": { - "type": "integer", - "description": "新的商品数量", - "minimum": 1 - } - }, - "required": [ - "quantity" - ] - } - } - } - }, - "responses": { - "200": { - "description": "更新购物车成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "参数错误或库存不足" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "购物车项不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "delete": { - "summary": "删除购物车商品", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "购物车项ID" - } - ], - "responses": { - "200": { - "description": "删除购物车商品成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "购物车项不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart/batch": { - "delete": { - "summary": "批量删除购物车商品", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "cart_item_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "购物车项ID数组" - } - }, - "required": [ - "cart_item_ids" - ] - } - } - } - }, - "responses": { - "200": { - "description": "批量删除购物车商品成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "deleted_count": { - "type": "integer", - "description": "删除的商品数量" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart/clear": { - "delete": { - "summary": "清空购物车", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "清空购物车成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart/count": { - "get": { - "summary": "获取购物车商品数量", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "获取购物车商品数量成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "count": { - "type": "integer", - "description": "购物车商品总数量" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/cart/checkout": { - "post": { - "summary": "购物车结账", - "tags": [ - "Cart" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "cart_item_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "要结账的购物车项ID数组" - }, - "shipping_address": { - "type": "string", - "description": "收货地址" - } - }, - "required": [ - "cart_item_ids", - "shipping_address" - ] - } - } - } - }, - "responses": { - "201": { - "description": "结账成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "order_id": { - "type": "integer" - }, - "order_no": { - "type": "string" - }, - "total_amount": { - "type": "integer" - }, - "total_points": { - "type": "integer" - }, - "total_rongdou": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误或库存不足" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/create": { - "post": { - "summary": "创建匹配订单", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "matchingType": { - "type": "string", - "enum": [ - "small", - "large" - ], - "default": "small", - "description": "匹配类型(小额或大额)" - }, - "customAmount": { - "type": "number", - "description": "大额匹配时的自定义金额(5000-50000之间)" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "匹配订单创建成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "matchingOrderId": { - "type": "integer" - }, - "amounts": { - "type": "array", - "items": { - "type": "number" - } - }, - "matchingType": { - "type": "string" - }, - "totalAmount": { - "type": "number" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误或用户未满足匹配条件" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "用户不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/my-orders": { - "get": { - "summary": "获取用户的匹配订单列表", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - } - ], - "responses": { - "200": { - "description": "成功获取匹配订单列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MatchingOrder" - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/pending-allocations": { - "get": { - "summary": "获取用户待处理的分配", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取待处理分配", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Allocation" - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/allocation/{id}": { - "get": { - "summary": "获取分配详情", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "responses": { - "200": { - "description": "成功获取分配详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "$ref": "#/components/schemas/Allocation" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无权限访问" - }, - "404": { - "description": "分配不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/confirm-allocation/{allocationId}": { - "post": { - "summary": "确认分配(创建转账)", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "allocationId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "transferAmount": { - "type": "number", - "description": "转账金额" - }, - "description": { - "type": "string", - "description": "转账描述" - }, - "voucher": { - "type": "string", - "description": "转账凭证(图片URL)" - } - }, - "required": [ - "voucher" - ] - } - } - } - }, - "responses": { - "200": { - "description": "转账凭证提交成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "transferId": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/reject-allocation/{allocationId}": { - "post": { - "summary": "拒绝分配", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "allocationId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "拒绝原因" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "拒绝分配成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "分配不存在或无权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/order/{orderId}": { - "get": { - "summary": "获取匹配订单详情", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "orderId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "订单ID" - } - ], - "responses": { - "200": { - "description": "成功获取订单详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "order": { - "$ref": "#/components/schemas/MatchingOrder" - }, - "allocations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Allocation" - } - }, - "records": { - "type": "array", - "items": { - "type": "object" - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无权限查看" - }, - "404": { - "description": "订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching/stats": { - "get": { - "summary": "获取匹配统计信息", - "tags": [ - "Matching" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取统计信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "userStats": { - "type": "object", - "properties": { - "initiated_orders": { - "type": "integer" - }, - "participated_allocations": { - "type": "integer" - }, - "total_initiated_amount": { - "type": "number" - }, - "total_participated_amount": { - "type": "number" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/unreasonable-matches": { - "get": { - "summary": "获取不合理的匹配记录(正余额用户被匹配的情况)", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 20 - }, - "description": "每页数量" - } - ], - "responses": { - "200": { - "description": "成功获取不合理匹配记录", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "matches": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnreasonableMatch" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "totalPages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/fix-unreasonable-match/{allocationId}": { - "post": { - "summary": "修复不合理的匹配记录", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "allocationId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "cancel", - "reassign" - ], - "description": "修复操作类型(取消或重新分配)" - } - }, - "required": [ - "action" - ] - } - } - } - }, - "responses": { - "200": { - "description": "修复成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "参数错误或无需修复" - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "404": { - "description": "分配记录不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/matching-stats": { - "get": { - "summary": "获取匹配统计信息", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取匹配统计信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "currentStats": { - "type": "object", - "properties": { - "unreasonable_matches": { - "type": "integer" - }, - "reasonable_matches": { - "type": "integer" - }, - "system_matches": { - "type": "integer" - }, - "unreasonable_amount": { - "type": "number" - }, - "reasonable_amount": { - "type": "number" - } - } - }, - "yesterdayStats": { - "type": "object", - "properties": { - "total_outbound": { - "type": "number" - }, - "unique_amounts": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/fix-all-unreasonable": { - "post": { - "summary": "批量修复所有不合理匹配", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "批量修复完成", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "fixedCount": { - "type": "integer", - "description": "成功修复的记录数" - }, - "errorCount": { - "type": "integer", - "description": "修复失败的记录数" - }, - "errors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "错误信息列表(最多10条)" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/confirm-allocation/{allocationId}": { - "post": { - "summary": "管理员确认分配", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "allocationId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "responses": { - "200": { - "description": "分配确认成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "404": { - "description": "分配不存在或状态不是待处理" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/matching-admin/cancel-allocation/{allocationId}": { - "post": { - "summary": "管理员取消分配", - "tags": [ - "MatchingAdmin" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "allocationId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "分配ID" - } - ], - "responses": { - "200": { - "description": "分配取消成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "404": { - "description": "分配不存在或状态不是待处理" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/{id}/cancel": { - "put": { - "summary": "用户取消订单", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "订单ID" - } - ], - "responses": { - "200": { - "description": "订单取消成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "只能取消待处理的订单" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/{id}/confirm": { - "put": { - "summary": "确认收货", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "订单ID" - } - ], - "responses": { - "200": { - "description": "确认收货成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "只能确认已发货的订单" - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/{id}/status": { - "put": { - "summary": "更新订单状态(管理员)", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "订单ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "pending", - "shipped", - "completed", - "cancelled" - ], - "description": "订单状态" - } - }, - "required": [ - "status" - ] - } - } - } - }, - "responses": { - "200": { - "description": "订单状态更新成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "400": { - "description": "无效的订单状态" - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "404": { - "description": "订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/pending-payment/{id}": { - "get": { - "summary": "获取待支付预订单详情", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "预订单ID" - } - ], - "responses": { - "200": { - "description": "获取预订单详情成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "order_no": { - "type": "string" - }, - "total_amount": { - "type": "integer" - }, - "total_points": { - "type": "integer" - }, - "total_rongdou": { - "type": "integer" - }, - "status": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "product_id": { - "type": "integer" - }, - "product_name": { - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "price": { - "type": "integer" - }, - "points_price": { - "type": "integer" - }, - "rongdou_price": { - "type": "integer" - }, - "spec_info": { - "type": "object" - } - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "预订单不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/confirm-payment": { - "post": { - "summary": "确认支付订单", - "description": "根据商品支付方式确认订单支付:\n- 仅积分支付:按10000积分=1融豆的比例扣除积分\n- 仅融豆支付:直接扣除融豆\n- 组合支付:优先扣除积分(按10000:1转换),不足部分扣除融豆\n", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "order_id", - "address_id" - ], - "properties": { - "order_id": { - "type": "integer", - "description": "订单ID", - "example": 123 - }, - "address_id": { - "type": "integer", - "description": "收货地址ID", - "example": 456 - } - } - } - } - } - }, - "responses": { - "200": { - "description": "确认支付成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "订单支付成功" - }, - "data": { - "type": "object", - "properties": { - "order_id": { - "type": "integer", - "example": 123 - }, - "order_no": { - "type": "string", - "example": "ORD20240101123456" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误或余额不足", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "enum": [ - "订单ID和收货地址ID为必填项", - "积分不足", - "融豆不足", - "积分和融豆余额不足", - "商品支付方式配置错误" - ] - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "订单或地址不存在", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "enum": [ - "订单不存在或已处理", - "收货地址不存在", - "用户不存在" - ] - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/orders/stats": { - "get": { - "summary": "获取订单统计信息(管理员权限)", - "tags": [ - "Orders" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取订单统计信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "totalOrders": { - "type": "integer", - "description": "总订单数" - }, - "pendingOrders": { - "type": "integer", - "description": "待发货订单数" - }, - "completedOrders": { - "type": "integer", - "description": "已完成订单数" - }, - "monthOrders": { - "type": "integer", - "description": "本月新增订单数" - }, - "monthGrowthRate": { - "type": "number", - "description": "月增长率" - }, - "totalPointsConsumed": { - "type": "number", - "description": "总积分消费" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "无管理员权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/balance": { - "get": { - "summary": "获取用户当前积分余额", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取积分余额", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "points": { - "type": "integer", - "description": "用户当前积分" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权,需要登录" - }, - "404": { - "description": "用户不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/history": { - "get": { - "summary": "获取用户积分历史记录", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页记录数" - }, - { - "in": "query", - "name": "type", - "schema": { - "type": "string", - "enum": [ - "earn", - "spend", - "admin_adjust" - ] - }, - "description": "积分变动类型" - }, - { - "in": "query", - "name": "username", - "schema": { - "type": "string" - }, - "description": "用户名(仅管理员可用)" - }, - { - "in": "query", - "name": "change", - "schema": { - "type": "string", - "enum": [ - "positive", - "negative" - ] - }, - "description": "积分变动方向(仅管理员可用)" - }, - { - "in": "query", - "name": "startDate", - "schema": { - "type": "string", - "format": "date" - }, - "description": "开始日期(仅管理员可用)" - }, - { - "in": "query", - "name": "endDate", - "schema": { - "type": "string", - "format": "date" - }, - "description": "结束日期(仅管理员可用)" - } - ], - "responses": { - "200": { - "description": "成功获取积分历史", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "records": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PointsHistory" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "totalPages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权,需要登录" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/adjust": { - "post": { - "summary": "管理员调整用户积分", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "userId", - "points", - "reason" - ], - "properties": { - "userId": { - "type": "integer", - "description": "用户ID" - }, - "points": { - "type": "integer", - "description": "调整的积分数量(正数为增加,负数为减少)" - }, - "reason": { - "type": "string", - "description": "调整原因" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "积分调整成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "积分调整成功" - }, - "data": { - "type": "object", - "properties": { - "userId": { - "type": "integer" - }, - "pointsChanged": { - "type": "integer" - }, - "newBalance": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误或积分不足" - }, - "401": { - "description": "未授权,需要管理员权限" - }, - "404": { - "description": "用户不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/recharge": { - "post": { - "summary": "管理员给用户充值积分", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "user_id", - "points" - ], - "properties": { - "user_id": { - "type": "integer", - "description": "用户ID" - }, - "points": { - "type": "integer", - "description": "充值的积分数量(必须为正数)" - }, - "description": { - "type": "string", - "description": "充值描述", - "default": "管理员充值" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "积分充值成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "积分充值成功" - }, - "data": { - "type": "object", - "properties": { - "userId": { - "type": "integer" - }, - "pointsAdded": { - "type": "integer" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误" - }, - "401": { - "description": "未授权,需要管理员权限" - }, - "404": { - "description": "用户不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/leaderboard": { - "get": { - "summary": "获取积分排行榜", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "返回的排行榜数量" - } - ], - "responses": { - "200": { - "description": "成功获取积分排行榜", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "leaderboard": { - "type": "array", - "items": { - "type": "object", - "properties": { - "rank": { - "type": "integer" - }, - "userId": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "points": { - "type": "integer" - } - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权,需要登录" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/points/stats": { - "get": { - "summary": "获取积分统计信息(管理员权限)", - "tags": [ - "Points" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取积分统计信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "stats": { - "type": "object", - "properties": { - "totalPoints": { - "type": "integer", - "description": "系统中总积分数量" - }, - "totalEarned": { - "type": "integer", - "description": "总积分发放量" - }, - "totalSpent": { - "type": "integer", - "description": "总积分消费量" - }, - "activeUsers": { - "type": "integer", - "description": "活跃用户数" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权,需要管理员权限" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/products/flash-sale": { - "get": { - "summary": "获取秒杀商品", - "tags": [ - "Products" - ], - "responses": { - "200": { - "description": "成功获取秒杀商品", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "products": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Product" - } - } - } - } - } - } - } - } - } - } - } - }, - "/regions/zhejiang": { - "get": { - "summary": "获取浙江省所有地区数据", - "tags": [ - "Regions" - ], - "responses": { - "200": { - "description": "成功获取浙江省地区数据", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ZhejiangRegion" - } - }, - "message": { - "type": "string", - "example": "获取地区数据成功" - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/regions/provinces": { - "get": { - "summary": "获取所有省份", - "tags": [ - "Regions" - ], - "responses": { - "200": { - "description": "成功获取省份列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Region" - } - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/regions/cities/{provinceCode}": { - "get": { - "summary": "根据省份代码获取城市列表", - "tags": [ - "Regions" - ], - "parameters": [ - { - "in": "path", - "name": "provinceCode", - "required": true, - "schema": { - "type": "string" - }, - "description": "省份代码" - } - ], - "responses": { - "200": { - "description": "成功获取城市列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Region" - } - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/regions/districts/{cityCode}": { - "get": { - "summary": "根据城市代码获取区县列表", - "tags": [ - "Regions" - ], - "parameters": [ - { - "in": "path", - "name": "cityCode", - "required": true, - "schema": { - "type": "string" - }, - "description": "城市代码" - } - ], - "responses": { - "200": { - "description": "成功获取区县列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Region" - } - } - } - } - } - } - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/regions/path/{regionCode}": { - "get": { - "summary": "根据区域代码获取完整路径(省-市-区)", - "tags": [ - "Regions" - ], - "parameters": [ - { - "in": "path", - "name": "regionCode", - "required": true, - "schema": { - "type": "string" - }, - "description": "区域代码" - } - ], - "responses": { - "200": { - "description": "成功获取区域完整路径", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "province": { - "$ref": "#/components/schemas/Region" - }, - "city": { - "$ref": "#/components/schemas/Region" - }, - "district": { - "$ref": "#/components/schemas/Region" - } - } - } - } - } - } - } - }, - "404": { - "description": "区域不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/risk-management/users": { - "get": { - "summary": "获取风险用户列表", - "tags": [ - "RiskManagement" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "is_blacklisted", - "schema": { - "type": "integer", - "enum": [ - 0, - 1 - ] - }, - "description": "是否被拉黑" - }, - { - "in": "query", - "name": "username", - "schema": { - "type": "string" - }, - "description": "用户名" - } - ], - "responses": { - "200": { - "description": "成功获取风险用户列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "users": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "real_name": { - "type": "string" - }, - "is_blacklisted": { - "type": "boolean" - }, - "blacklist_reason": { - "type": "string" - }, - "blacklisted_at": { - "type": "string", - "format": "date-time" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "pages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/risk-management/blacklist/{userId}": { - "post": { - "summary": "拉黑用户", - "tags": [ - "RiskManagement" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "userId", - "schema": { - "type": "integer" - }, - "required": true, - "description": "用户ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "reason" - ], - "properties": { - "reason": { - "type": "string", - "description": "拉黑原因" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "用户已被拉黑", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "用户已被拉黑" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/risk-management/unblacklist/{userId}": { - "post": { - "summary": "解除拉黑", - "tags": [ - "RiskManagement" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "userId", - "schema": { - "type": "integer" - }, - "required": true, - "description": "用户ID" - } - ], - "responses": { - "200": { - "description": "已解除拉黑", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "已解除拉黑" - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/risk-management/overdue-transfers": { - "get": { - "summary": "获取超时转账列表", - "tags": [ - "RiskManagement" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - } - ], - "responses": { - "200": { - "description": "成功获取超时转账列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "transfers": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "user_id": { - "type": "integer" - }, - "recipient_id": { - "type": "integer" - }, - "amount": { - "type": "number" - }, - "status": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "username": { - "type": "string" - }, - "recipient_name": { - "type": "string" - }, - "overdue_hours": { - "type": "number" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "pages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/sms/send": { - "post": { - "summary": "发送短信验证码", - "tags": [ - "SMS" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone" - ], - "properties": { - "phone": { - "type": "string", - "description": "手机号码" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "验证码发送成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "验证码发送成功" - } - } - } - } - } - }, - "400": { - "description": "参数错误或发送频率限制" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/sms/verify": { - "post": { - "summary": "验证短信验证码", - "tags": [ - "SMS" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone", - "code" - ], - "properties": { - "phone": { - "type": "string", - "description": "手机号码" - }, - "code": { - "type": "string", - "description": "验证码" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "验证码验证成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "手机号验证成功" - }, - "data": { - "type": "object", - "properties": { - "phone": { - "type": "string" - }, - "verified": { - "type": "boolean" - } - } - } - } - } - } - } - }, - "400": { - "description": "参数错误或验证码错误" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/specifications/names": { - "get": { - "summary": "获取所有规格名称", - "tags": [ - "Specifications" - ], - "parameters": [ - { - "in": "query", - "name": "status", - "schema": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "description": "状态筛选" - } - ], - "responses": { - "200": { - "description": "成功获取规格名称列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SpecName" - } - } - } - } - } - } - } - } - }, - "post": { - "summary": "创建规格名称", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "display_name" - ], - "properties": { - "name": { - "type": "string", - "description": "规格名称" - }, - "display_name": { - "type": "string", - "description": "显示名称" - }, - "sort_order": { - "type": "integer", - "default": 0 - } - } - } - } - } - }, - "responses": { - "201": { - "description": "规格名称创建成功" - } - } - } - }, - "/specifications/names/{id}": { - "delete": { - "summary": "删除规格名称", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "规格名称ID" - } - ], - "responses": { - "200": { - "description": "删除成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string", - "example": "规格名称删除成功" - } - } - } - } - } - }, - "400": { - "description": "该规格名称下还有规格值,无法删除" - }, - "404": { - "description": "规格名称不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/specifications/values": { - "get": { - "summary": "获取规格值列表", - "tags": [ - "Specifications" - ], - "parameters": [ - { - "in": "query", - "name": "spec_name_id", - "schema": { - "type": "integer" - }, - "description": "规格名称ID" - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "description": "状态筛选" - } - ], - "responses": { - "200": { - "description": "成功获取规格值列表" - } - } - }, - "post": { - "summary": "创建规格值", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "spec_name_id", - "value", - "display_value" - ], - "properties": { - "spec_name_id": { - "type": "integer" - }, - "value": { - "type": "string" - }, - "display_value": { - "type": "string" - }, - "color_code": { - "type": "string" - }, - "image_url": { - "type": "string" - }, - "sort_order": { - "type": "integer", - "default": 0 - } - } - } - } - } - }, - "responses": { - "201": { - "description": "规格值创建成功" - } - } - } - }, - "/specifications/combinations/{productId}": { - "get": { - "summary": "获取商品的规格组合", - "tags": [ - "Specifications" - ], - "parameters": [ - { - "in": "path", - "name": "productId", - "required": true, - "schema": { - "type": "integer" - }, - "description": "商品ID" - }, - { - "in": "query", - "name": "status", - "schema": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "description": "状态筛选" - } - ], - "responses": { - "200": { - "description": "成功获取规格组合" - } - } - } - }, - "/specifications/combinations/{id}": { - "get": { - "summary": "获取单个规格组合详情", - "tags": [ - "Specifications" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "规格组合ID" - } - ], - "responses": { - "200": { - "description": "成功获取规格组合详情", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "product_id": { - "type": "integer" - }, - "combination_key": { - "type": "string" - }, - "spec_values": { - "type": "array", - "items": { - "type": "integer" - } - }, - "price_adjustment": { - "type": "integer" - }, - "points_adjustment": { - "type": "integer" - }, - "rongdou_adjustment": { - "type": "integer" - }, - "stock": { - "type": "integer" - }, - "sku_code": { - "type": "string" - }, - "barcode": { - "type": "string" - }, - "weight": { - "type": "number" - }, - "volume": { - "type": "number" - }, - "status": { - "type": "string" - }, - "spec_details": { - "type": "array", - "items": { - "type": "object" - } - }, - "actual_price": { - "type": "number" - }, - "actual_points_price": { - "type": "number" - }, - "actual_rongdou_price": { - "type": "number" - }, - "is_available": { - "type": "boolean" - } - } - } - } - } - } - } - }, - "404": { - "description": "规格组合不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "delete": { - "summary": "删除规格组合", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "规格组合ID" - } - ], - "responses": { - "200": { - "description": "删除成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string", - "example": "规格组合删除成功" - } - } - } - } - } - }, - "404": { - "description": "规格组合不存在" - }, - "500": { - "description": "服务器错误" - } - } - }, - "put": { - "summary": "更新规格组合", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "规格组合ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "price_adjustment": { - "type": "integer" - }, - "points_adjustment": { - "type": "integer" - }, - "rongdou_adjustment": { - "type": "integer" - }, - "stock": { - "type": "integer" - }, - "sku_code": { - "type": "string" - }, - "barcode": { - "type": "string" - }, - "weight": { - "type": "number" - }, - "volume": { - "type": "number" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "规格组合更新成功" - }, - "404": { - "description": "规格组合不存在" - } - } - } - }, - "/specifications/combinations": { - "post": { - "summary": "创建商品规格组合", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "product_id", - "spec_values" - ], - "properties": { - "product_id": { - "type": "integer" - }, - "spec_values": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "规格值ID数组" - }, - "price_adjustment": { - "type": "integer", - "default": 0 - }, - "points_adjustment": { - "type": "integer", - "default": 0 - }, - "rongdou_adjustment": { - "type": "integer", - "default": 0 - }, - "stock": { - "type": "integer", - "default": 0 - }, - "sku_code": { - "type": "string" - }, - "barcode": { - "type": "string" - }, - "weight": { - "type": "number" - }, - "volume": { - "type": "number" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "规格组合创建成功" - } - } - } - }, - "/specifications/generate-combinations": { - "post": { - "summary": "为商品生成笛卡尔积规格组合", - "tags": [ - "Specifications" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "product_id", - "spec_name_ids" - ], - "properties": { - "product_id": { - "type": "integer" - }, - "spec_name_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "规格名称ID数组" - }, - "default_stock": { - "type": "integer", - "default": 0, - "description": "默认库存" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "规格组合生成成功" - } - } - } - }, - "/transfers": { - "get": { - "summary": "获取转账列表", - "tags": [ - "Transfers" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "status", - "schema": { - "type": "string" - }, - "description": "转账状态过滤" - }, - { - "in": "query", - "name": "transfer_type", - "schema": { - "type": "string" - }, - "description": "转账类型过滤" - }, - { - "in": "query", - "name": "start_date", - "schema": { - "type": "string", - "format": "date" - }, - "description": "开始日期过滤" - }, - { - "in": "query", - "name": "end_date", - "schema": { - "type": "string", - "format": "date" - }, - "description": "结束日期过滤" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "搜索关键词(用户名或真实姓名)" - }, - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "sort", - "schema": { - "type": "string" - }, - "description": "排序字段" - }, - { - "in": "query", - "name": "order", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - }, - "description": "排序方向" - } - ], - "responses": { - "200": { - "description": "成功获取转账列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "transfers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Transfer" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/transfers/list": { - "get": { - "summary": "获取转账记录列表", - "tags": [ - "Transfers" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "status", - "schema": { - "type": "string" - }, - "description": "转账状态过滤" - }, - { - "in": "query", - "name": "transfer_type", - "schema": { - "type": "string" - }, - "description": "转账类型过滤" - }, - { - "in": "query", - "name": "start_date", - "schema": { - "type": "string", - "format": "date" - }, - "description": "开始日期过滤" - }, - { - "in": "query", - "name": "end_date", - "schema": { - "type": "string", - "format": "date" - }, - "description": "结束日期过滤" - }, - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - }, - { - "in": "query", - "name": "sort", - "schema": { - "type": "string" - }, - "description": "排序字段" - }, - { - "in": "query", - "name": "order", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - }, - "description": "排序方向" - } - ], - "responses": { - "200": { - "description": "成功获取转账记录列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "transfers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Transfer" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/transfers/public-account": { - "get": { - "summary": "获取公户信息", - "tags": [ - "Transfers" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "成功获取公户信息", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "公户ID" - }, - "username": { - "type": "string", - "description": "公户用户名", - "example": "public_account" - }, - "real_name": { - "type": "string", - "description": "公户名称" - }, - "balance": { - "type": "number", - "format": "float", - "description": "公户余额" - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "404": { - "description": "公户不存在" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/transfers/create": { - "post": { - "summary": "创建转账记录", - "tags": [ - "Transfers" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "to_user_id", - "amount", - "transfer_type" - ], - "properties": { - "to_user_id": { - "type": "integer", - "description": "接收方用户ID" - }, - "amount": { - "type": "number", - "format": "float", - "description": "转账金额" - }, - "transfer_type": { - "type": "string", - "enum": [ - "user_to_user", - "user_to_system", - "system_to_user" - ], - "description": "转账类型" - }, - "remark": { - "type": "string", - "description": "转账备注" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "转账记录创建成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "转账记录创建成功,等待确认" - }, - "data": { - "type": "object", - "properties": { - "transfer_id": { - "type": "integer", - "description": "转账记录ID" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/transfers/admin/create": { - "post": { - "summary": "管理员创建转账记录", - "tags": [ - "Transfers" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "from_user_id", - "to_user_id", - "amount", - "transfer_type" - ], - "properties": { - "from_user_id": { - "type": "integer", - "description": "发送方用户ID" - }, - "to_user_id": { - "type": "integer", - "description": "接收方用户ID" - }, - "amount": { - "type": "number", - "format": "float", - "description": "转账金额" - }, - "transfer_type": { - "type": "string", - "enum": [ - "user_to_user", - "user_to_system", - "system_to_user" - ], - "description": "转账类型" - }, - "description": { - "type": "string", - "description": "转账描述" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "转账记录创建成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "转账记录创建成功" - }, - "data": { - "type": "object", - "properties": { - "transfer_id": { - "type": "integer", - "description": "转账记录ID" - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/upload/image": { - "post": { - "summary": "上传图片", - "tags": [ - "Upload" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "file": { - "type": "string", - "format": "binary", - "description": "要上传的图片文件" - }, - "type": { - "type": "string", - "enum": [ - "avatar", - "product", - "document" - ], - "default": "document", - "description": "上传文件类型" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "图片上传成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "url": { - "type": "string", - "description": "上传后的文件URL" - }, - "filename": { - "type": "string", - "description": "上传后的文件名" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/upload": { - "post": { - "summary": "多文件上传接口 (支持MediaUpload组件)", - "tags": [ - "Upload" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "files": { - "type": "array", - "items": { - "type": "string", - "format": "binary" - }, - "description": "要上传的文件列表" - }, - "type": { - "type": "string", - "enum": [ - "avatar", - "product", - "document" - ], - "default": "document", - "description": "上传文件类型" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "文件上传成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "文件上传成功" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "filename": { - "type": "string" - }, - "originalname": { - "type": "string" - }, - "mimetype": { - "type": "string" - }, - "size": { - "type": "integer" - }, - "path": { - "type": "string" - }, - "url": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/upload/single": { - "post": { - "summary": "单文件上传接口(兼容性接口)", - "tags": [ - "Upload" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "file": { - "type": "string", - "format": "binary", - "description": "要上传的文件" - }, - "type": { - "type": "string", - "enum": [ - "avatar", - "product", - "document" - ], - "default": "document", - "description": "上传文件类型" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "文件上传成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "文件上传成功" - }, - "url": { - "type": "string", - "description": "上传后的文件URL" - }, - "filename": { - "type": "string", - "description": "上传后的文件名" - }, - "originalname": { - "type": "string", - "description": "原始文件名" - }, - "size": { - "type": "integer", - "description": "文件大小" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/users": { - "post": { - "summary": "创建用户(管理员权限)", - "tags": [ - "Users" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "username", - "password", - "real_name", - "id_card" - ], - "properties": { - "username": { - "type": "string" - }, - "password": { - "type": "string" - }, - "role": { - "type": "string", - "enum": [ - "user", - "admin", - "merchant" - ], - "default": "user" - }, - "is_system_account": { - "type": "boolean", - "default": false - }, - "real_name": { - "type": "string" - }, - "id_card": { - "type": "string" - }, - "wechat_qr": { - "type": "string" - }, - "alipay_qr": { - "type": "string" - }, - "bank_card": { - "type": "string" - }, - "unionpay_qr": { - "type": "string" - }, - "phone": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "用户创建成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - } - } - } - } - } - }, - "400": { - "description": "请求参数错误" - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/users/pending-audit": { - "get": { - "summary": "获取待审核用户列表(管理员权限)", - "tags": [ - "Users" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "页码" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "integer", - "default": 10 - }, - "description": "每页数量" - } - ], - "responses": { - "200": { - "description": "成功获取待审核用户列表", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "users": { - "type": "array", - "items": { - "$ref": "#/components/schemas/User" - } - }, - "pagination": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "pages": { - "type": "integer" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "未授权" - }, - "403": { - "description": "权限不足" - }, - "500": { - "description": "服务器错误" - } - } - } - }, - "/api/users/{id}/distribute": { - "put": { - "summary": "设置用户分发状态", - "description": "更新指定用户的分发状态", - "tags": [ - "Users" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer" - }, - "description": "用户ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "is_distribute" - ], - "properties": { - "is_distribute": { - "type": "boolean", - "description": "分发状态,true为启用分发,false为禁用分发", - "example": true - } - } - } - } - } - }, - "responses": { - "200": { - "description": "分发状态更新成功", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "分发状态更新成功" - }, - "is_distribute": { - "type": "boolean", - "description": "更新后的分发状态", - "example": true - } - } - } - } - } - }, - "400": { - "description": "请求参数错误", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "分发状态无效" - } - } - } - } - } - }, - "404": { - "description": "用户不存在", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "用户不存在" - } - } - } - } - } - }, - "500": { - "description": "服务器内部错误", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "服务器内部错误" - } - } - } - } - } - } - } - } - } - }, - "tags": [ - { - "name": "Announcements", - "description": "通知公告管理API" - }, - { - "name": "Orders", - "description": "订单管理API" - }, - { - "name": "Products", - "description": "商品管理API" - }, - { - "name": "Authentication", - "description": "用户认证API" - }, - { - "name": "Captcha", - "description": "验证码API" - }, - { - "name": "Cart", - "description": "购物车管理相关接口" - }, - { - "name": "Matching", - "description": "匹配订单相关接口" - }, - { - "name": "MatchingAdmin", - "description": "匹配订单管理员相关接口" - }, - { - "name": "Points", - "description": "积分管理相关接口" - }, - { - "name": "Regions", - "description": "地区数据API" - }, - { - "name": "RiskManagement", - "description": "风险管理API" - }, - { - "name": "SMS", - "description": "短信验证码相关接口" - }, - { - "name": "Upload", - "description": "文件上传API" - }, - { - "name": "Users", - "description": "用户管理API" - } - ] -} \ No newline at end of file diff --git a/apifox-sync.js b/apifox-sync.js deleted file mode 100644 index 9c93f17..0000000 --- a/apifox-sync.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 自动导出Swagger文档到本地文件 - * 使用方法: - * 1. 运行此脚本: node apifox-sync.js - * 2. 手动将生成的swagger.json文件导入到Apifox - */ -const fs = require('fs'); -const path = require('path'); -const swaggerSpecs = require('./swagger'); - -// 确保输出目录存在 -const outputDir = path.join(__dirname, 'api-docs'); -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// 导出Swagger文档到JSON文件 -const outputPath = path.join(outputDir, 'swagger.json'); -fs.writeFileSync(outputPath, JSON.stringify(swaggerSpecs, null, 2)); -console.log(`Swagger文档已导出到: ${outputPath}`); -console.log('请手动将此文件导入到Apifox:'); -console.log('1. 登录Apifox网页版'); -console.log('2. 打开您的项目'); -console.log('3. 点击"导入"按钮'); -console.log('4. 选择"导入OpenAPI(Swagger)"'); -console.log('5. 上传刚才生成的swagger.json文件'); -console.log('6. 选择导入方式(合并或覆盖)并完成导入'); \ No newline at end of file diff --git a/cert/apiclient_cert.p12 b/cert/apiclient_cert.p12 deleted file mode 100644 index 425cddd..0000000 Binary files a/cert/apiclient_cert.p12 and /dev/null differ diff --git a/cert/apiclient_cert.pem b/cert/apiclient_cert.pem deleted file mode 100644 index ee924ac..0000000 --- a/cert/apiclient_cert.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEKDCCAxCgAwIBAgIUXysc+VIXyKce5hqbA2vg9d9uH7kwDQYJKoZIhvcNAQEL -BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT -FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg -Q0EwHhcNMjUwOTAyMDg0NDA0WhcNMzAwOTAxMDg0NDA0WjCBgTETMBEGA1UEAwwK -MTcyNjM3NzMzNjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL -DCTlroHms6Lngqzono3mrYbliJvnp5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT -AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAKncf/y0dT12nyCmnK5w3ECQgtoHrU32LqQH/N5SUbiJVbkys99Qq41D -MO0539Ki2MBuOuDHnWOrgNbRhH4WMXS8qodTcWu39cgVlnmw/KtQJdyInrPynFYH -lWO6boc1jfM4UEbEk90iaLM49uFo75+bCNGZVHOt6UMmTNFB9zHDfnhm+UaxNJOf -p1z7sntmD3H98c4ghzcE5L51KL6A7JtW+cp0zTpOunj0T/drvmRoyQVzqZ4IlXga -9pJ/6Un2fVNn8pZL804ldKumAf5KVKmqEmz64ydh3896MoAshT/UJIHDuduN+Jbk -gROt9QiM68jR6CuqeRsV7EZ1OA/BLI8CAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG -A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu -aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC -MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB -OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQCzyJZ/ -+rnvUd1kJ74KCb6kxAwM/bX8w5lhkdUkeyQxdmbUCXCrkOGJ8uRQMfiK93eeET4h -KMrZywQHvL1E5WXQpUmQZVYj6eAxaUO+RoW8wPeWb5x/LbSXqQCrCNF6U+AvC6wj -6haW8TK8egCLsjxPBXL9NjkxvcsIOIM8F6JKhMSAAjT7F1nkXthyxC50o/Mbox6l -YfvVZ70gWuUR2e6o9Sob1tTq6YwSwDr5OMwJT8QpDEdjAbWLLtv7a+ApzfqWeTHP -dmVawsaQnOzFaUPDtEGYSh23/eaTrJ9DHpgtubtU/CQsGos6QHD6huRYMmvw3Wml -mFAVWux4DJ8lZXGZ ------END CERTIFICATE----- diff --git a/cert/apiclient_key.pem b/cert/apiclient_key.pem deleted file mode 100644 index e87a2d3..0000000 --- a/cert/apiclient_key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCp3H/8tHU9dp8g -ppyucNxAkILaB61N9i6kB/zeUlG4iVW5MrPfUKuNQzDtOd/SotjAbjrgx51jq4DW -0YR+FjF0vKqHU3Frt/XIFZZ5sPyrUCXciJ6z8pxWB5Vjum6HNY3zOFBGxJPdImiz -OPbhaO+fmwjRmVRzrelDJkzRQfcxw354ZvlGsTSTn6dc+7J7Zg9x/fHOIIc3BOS+ -dSi+gOybVvnKdM06Trp49E/3a75kaMkFc6meCJV4GvaSf+lJ9n1TZ/KWS/NOJXSr -pgH+SlSpqhJs+uMnYd/PejKALIU/1CSBw7nbjfiW5IETrfUIjOvI0egrqnkbFexG -dTgPwSyPAgMBAAECggEAF5dGN0Sg285zv0ckj52hGV54retPCHrec22gkwf/zY9V -VolSLfu4N8BTNT9KdKilTeSBTOKsW0FgfXVP32sZp0rkrDLMl9dOzWEiKviHvws8 -lupqkDdruw8Gknk8DI9FjbgOfiWjG51ByVJqB1hZn2Ma0HFpJz/KG8df99gfisuP -3s4s/N8gChKRobNJzoFTVlEexVwM8vPZl7foxm9SEc0tbXyU2yB+pglVv/BkkLo5 -aZFNIC2TVnGX1WJlTTfuhng+MMeckSIPjJGH9c7RpgAChKGOBFOAK87/1+3tep47 -qp3i2WnceIxy2Y/wIT4cGWUAd0Ghi/kkIo8KqpagEQKBgQDfiwhBitymWc5QRr+R -LM6+Yu/EWlpd4/ZrcIgX9S5Z/La4gs/fqyWyKp9r/KjyYKG5sEfArim3NenvQBac -Vp99kNpjAl6SCSWFNeA7aY+zLDJBRHSaG4ovxWXk1ZYD2hBLKahzp7PCdcNvPICU -60AapDMiGlfbcJDot1uP+9i16wKBgQDChiW9qroJGfZcpe5nkh38orPOp7or09mG -perNOAXGghJMV7tc1Se+ttjcvlfK6hrQcxPCMccH/x0bmeZ/MaG1RZjZS2zAXqH5 -9huZtgIHc2idVS+j1t4HsszuIBp84c6ykCU+6NltW1LQFRZ9Y2HuA5HW/fgyhLMG -RHpRdgfG7QKBgDd9P5NlcNgqOrhal3rl8Hv5+yJ2ezALQkPxLxcjWVolDQZIEmmn -BjhvtBsOILHporuBMo51rQ05aNRmyDYOmpCEwHELSYZelt22Pe8BiRYkxmTFJVyL -sYWiLmTbT92s55aAxLvQySJgMR8Pmatdqg/y6m5ws5ZZHt9lhGj9TxH5AoGAR8Rc -WjyJxF/av9XMPlPvUkzoz76b9h2D7KR8G1im8NT+UUIw8xAFSNyG5/Ily8xRNkSu -rn/U8YNSxuMh4h16jrltqgWkythfJCyDhFNdLkiK+Tj7iZP1eJuj9drMSvS4YLLD -uxEHXsxJolGVaY9oCvswLESo9GJ29kH/atyEBAUCgYB+xm2NI9OpF+dSq5O8vMiW -NtlD744WfsiBDzatQz1A9PAB6cyLi03zjW10+HCNZRxBSfI8au/sn9lOdcCjvSGk -8tjZXMzL71jDhEYs4qTUaiKJ2YpbLNcLFeTy48hWeRpew7t7bunksx8zb+0W14M2 -zDmIHi+CSve59dOzoiYuNg== ------END PRIVATE KEY----- diff --git a/cert/证书使用说明.txt b/cert/证书使用说明.txt deleted file mode 100644 index 9a0aab1..0000000 --- a/cert/证书使用说明.txt +++ /dev/null @@ -1,18 +0,0 @@ -欢迎使用微信支付! -附件中的三份文件(证书pkcs12格式、证书pem格式、证书密钥pem格式),为接口中强制要求时需携带的证书文件。 -证书属于敏感信息,请妥善保管不要泄露和被他人复制。 -不同开发语言下的证书格式不同,以下为说明指引: - 证书pkcs12格式(apiclient_cert.p12) - 包含了私钥信息的证书文件,为p12(pfx)格式,由微信支付签发给您用来标识和界定您的身份 - 部分安全性要求较高的API需要使用该证书来确认您的调用身份 - windows上可以直接双击导入系统,导入过程中会提示输入证书密码,证书密码默认为您的商户号(如:1900006031) - 证书pem格式(apiclient_cert.pem) - 从apiclient_cert.p12中导出证书部分的文件,为pem格式,请妥善保管不要泄漏和被他人复制 - 部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供 - 您也可以使用openssl命令来自己导出:openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem - 证书密钥pem格式(apiclient_key.pem) - 从apiclient_cert.p12中导出密钥部分的文件,为pem格式 - 部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供 - 您也可以使用openssl命令来自己导出:openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem -备注说明: - 由于绝大部分操作系统已内置了微信支付服务器证书的根CA证书, 2018年3月6日后, 不再提供CA证书文件(rootca.pem)下载 \ No newline at end of file diff --git a/certs/README.md b/certs/README.md deleted file mode 100644 index bef908a..0000000 --- a/certs/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# 支付宝支付证书配置指南 - -## 证书文件说明 - -本目录需要包含以下证书文件,用于支付宝支付V3版本API的证书模式: - -1. `appCertPublicKey.crt` - 应用公钥证书(已存在) -2. `alipayRootCert.crt` - 支付宝根证书(需下载) -3. `alipayCertPublicKey_RSA2.crt` - 支付宝公钥证书(需下载) - -## 如何获取证书 - -### 已有证书 -- `appCertPublicKey.crt` - 应用公钥证书已存在于本目录 - -### 需要下载的证书 -请登录支付宝开放平台,从您的应用详情页面下载以下证书: - -1. **支付宝根证书**:下载后重命名为 `alipayRootCert.crt` 并放入本目录 -2. **支付宝公钥证书**:下载后重命名为 `alipayCertPublicKey_RSA2.crt` 并放入本目录 - -## 证书下载步骤 - -1. 登录[支付宝开放平台](https://open.alipay.com/) -2. 进入「开发者中心」->「我的应用」 -3. 选择对应的应用 -4. 点击「开发设置」 -5. 在「接口加签方式」区域,选择「公钥证书」 -6. 下载「支付宝根证书」和「支付宝公钥证书」 -7. 将下载的证书放入本目录,并按上述名称重命名 - -## 配置验证 - -证书配置完成后,系统将使用证书模式进行支付宝API调用,支持V3版本的接口。 \ No newline at end of file diff --git a/certs/alipay-private-key-test.pem b/certs/alipay-private-key-test.pem deleted file mode 100644 index 8385c1e..0000000 --- a/certs/alipay-private-key-test.pem +++ /dev/null @@ -1 +0,0 @@ -MIIEowIBAAKCAQEAk3HgYh7dK97PBX4UhlsusYGI4bLL7EQak2JfOZUeoVJK144ENg6yF0t3eohiryiK/vpyuyqX3hP5jEQMQHM92Mor9mf/KPj6Dw5AAvNLEdEaDCLwKUByk8sN2M6Xibdh6X0nQ67wOe6kaDkOG5Efkr4rHJCBfwbINXuaxXnJUr1mLdzwp1XN09IBUcKt+EXOKfpwbIuU6JoxTMiz7hnRHDuVKTYqnvx9CdV5Ng1eqYv5OI4r9abiFttRUJ37+RXbvXzffJGa/HFjpyPp4MOuK8n24ZW4vdjSBlflzzRHTAuo3tSo56gy9UouY6XtT0C9sXE7CxHkHeupOO3Wzl6t9wIDAQABAoIBAFvPRNDUNxPiITZiU5V1oZWV+w3Or3vmzEWJs5G/cNsyyrd+DtE6RVqL+1GpKwm2TRaIDHjPBNGbzn+wv5BCMfiTqtw71X5Fyi8lyGYN6Gins3hrKPAG2VF91plxyppOPgGNlK1oeN0Z4/Wh0U8JBofxMhcPRBM8vd3PoYflVZ7WpeHaApdL4n0S0Z+HcaCtnhfbJDh2fTXNkl01e2nI1Rqatkqc/Giwc8Pg5SrTKBO2g6GhPbk9LUUPqat9+3jmbT1CI56HS4OVTWFbqCMFqQE/KSXNpuFYRRTCgWDQSPbmJEKyDQMinG+DdJj2IIMhD60qYut+OBb6XQVqtdlBnoECgYEA/fbCiFIZPZ6IU7hnn75Jj2JDc6XIqFAVrbofViL3HUyeZ1KSYkXtDdh42Cl8fBCiT0xyagA/fdHmFTTJBueKfoW3+sqFMm3vKe4spJCNLmSAP8gu60zTAslmiQ5zyq+UVd74BE1Y7HSLoAv3lZnwIJkhOgrUq2mH4+r73SPN+C8CgYEAlKB+s7RZW+h4NX0YGZFdQ1AQJI0uSYOFt6YVqhTIRhUSqM++Yy/giE8X0TRckqhhAsBD6SN05rB54o6CdSacVriALtD+vrVZBDt+abTmeh1BhRUkrYeC7wZK5/+5pni9Nr1GfNKe+U2a8rzKCUJg6BWW6qGUrHOiU+9lYhQ17LkCgYBudtiipuMntD8j+z/HceNZJKqmMOQYoczsJdrfgpHuApeb5YSajkPQE+psS49D/5A54cyaYsU7GwNzEeSmxiutYMhno5NQHhU7LcfpRJ7EIR7Pn2kZG+9kdOnOj6S58qkYuMU0Sdh18TOSR+JHBhA2faTANFnQvTRIZLtsstgyLQKBgQCBl8/icYbZFMJ8IS86W/2uC8mHlXKetweJMlABlU1rjkRO3ZVsdvqY4B4sVDPDzP2JoIuWZUwxOf+NBCXMcHYxR369U45MS2PqxNVc5ldwcsIGgIESre4E7L+zus7t0KlraW5kuGHVj01kCilAGZjVxL1qqKkyFUGdXkhQVL8QQQKBgCO6GkpCR5yBs181iPh+AdI3EZh70SEBHfZNNJFs5a4FvDhxZhvkwIG77FY9IVyBn/aZLrVHJXUVB/HGA1QeNhHSXvIkWGwsNQ04OZJu0uio+4D+yGxY5XQ4F6Ti9KPI9dbu9S7OZKfZCsj5nhm/kKB3MlExN2MKqoQIMGHDcI0k \ No newline at end of file diff --git a/certs/alipay-private-key.pem b/certs/alipay-private-key.pem deleted file mode 100644 index a908ace..0000000 --- a/certs/alipay-private-key.pem +++ /dev/null @@ -1 +0,0 @@ -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCV9dEIPimkwxhJXXy2cntNZqeZdV4JAdx7ZpnvbjV6OjeEZczksKFa6lgsYR6Wq6G/khEdVRyNW4RsepBRwVGDBxxUN0nB9uTtPti220PtXZIJycLY585nL/iaUjUt+Cyh2qsyIGQ0lGx9VFWU+x5stGCLgfZbHOaMo0S+49upEsX5p4MyRbc4zA/vf+xgmGm5f1YB1Nwm32JBhVRourmHhi82MLX3Hxwo6R/SIOrDIcUrELYC9XRC2LUWYJIsQ1QMtrE/0XVFmIB68gU9JqRTNXhLCyL19K3WUGvRXG9N5mTaZSBIYco7ZrtbTFHtIxSOYxqYTOtkPKVYxGn1I2WHAgMBAAECggEAEeaA6Dn7YJaFPKSzMVgjDc82LGRNMEgPmI9byq/eJFP0spIwThAjgqW8lreVHikoqqR19IlnWhxVh1luBsRLxZdAs3DSFhwxoXxCBDnSNvBXcWGrJ5csFTcttsYfYPhh44QlsVsaewhIlwFNIfaD4Df72ktOK/wcLSeEGkE6xEixfMcLawg6a8rkHbvJtppFdTSL2YBB4X+dnt3cYCxFYch7Q5+RF+Wqr++860B5ztBRSX8xmqEIDS/95tf09qDyhyrnE2a2wh/jT+BWzvILKNc8goNo29kACo8bE9p+rWCnG8YsL+4emlrxk6/OYzR5lG6xZNvXAmnY0LDKzQsqeQKBgQDTm/uQP82o0ez9YHP/sAvTVq7pIJlEnaZqI2jBvyGxf6fDTU5b0yfJF9VO5EoPVT9CHRJ7A0T4RUDhIAzo9fjrz3VjkXO/RZyOIHsptjvOFeKCiFGrHO8sKxC5i4IZIRwIB7A84DU7rVzQ5Kh3XRqi8srKl/DBXo1BloWU0HJetQKBgQC1axqVH/eIwZrYjSbXgSHwGYMhH0YmOIJEYavD6TmOp9QL5N1ax6B0r/BIVt7XAIRck8kueKY4LeRulq3azXIhBy5Qx6sp2Sb/SX3vrPrvA/ImoDQKR4CeL5zXQJkqbVgbbS//UTEWilKyQXNz13D4qbCqNBbLJaDirXqBT0ycywKBgCMvT2/XvAlzBlXHAOKl0gGM6z5mJjXrhK0nQBbfAeoykKF/rCTGgloEdXpNqSbNhNwoW1dK3t/tG/GS07K0m3QSJbGtkLJgD7zuF6yC2YTVzLjpk7LA99+/NWO0l6g4AiIvrRUiLpfCpqkxK/XU7EXl2uQ+yVBNuW0LayCoXCv1AoGAU/CJbSRMUO9baQTuStoJzODRBltFBtwwkdkrM0tPAU1v1E0BikZBXJwnLiFbm9k2ZOtQM3tJVUcOoYiASnOyccuzx1aLQKKj44yqg2Hi/QIzYWHQkk0BGq/m/sV52OKc2JvNkHGNp+M6XhXgiGHPeI5zGl1dioMPjLI9s2Twir8CgYEAn5wAbpx2imCPDtKgMxb0zxbKB5kq37QGuIRRAoorPkFr0Z7xSZs1M0qkhZcaX/kfnNgigPVwpUeUDQmETBRaXrCuXxRkOXGo1st4v5gn9kgQYBCv0dVbV1QNnnXp2fAtQ/3Jw6l9w4T0PgPBhu9TPnSTVZA0YldNo7TEEL5kEbA= \ No newline at end of file diff --git a/certs/alipay-public-key-test.pem b/certs/alipay-public-key-test.pem deleted file mode 100644 index 29aa871..0000000 --- a/certs/alipay-public-key-test.pem +++ /dev/null @@ -1 +0,0 @@ -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhptRt5n0KVACDX69KfZogfeLULSuZGkH0m3wVDI32a4eBH3TrzKjSShdO2WBF6glfvdCruuMuLmYJrIxa09koNcAF5CNc/2iHE8O8pUbrsLKgX+dpXoNZt6XVo/jHqsxmYDFjZogL3xFVW0z0pGcxaIWnVEhQ0hQ+ji2RxJ0Bb45mmOZOOVihpLf9hEFW0rHamf2Tfu+Hd4NWTb/CVZwgzchJ/cwLTqP1Ar3GeQdmB2tmaCTu1h8wyt6lkSUTOYTEf8xCdmv8xfS12dXAeh11t1a3SuqPT2b4UxuLJHHMmiKndD7BnPZIENxi7e6N5JKz1zahyuh23GeBCHs1wHtQQIDAQAB diff --git a/certs/alipay-public-key-y.pem b/certs/alipay-public-key-y.pem deleted file mode 100644 index 89738c0..0000000 --- a/certs/alipay-public-key-y.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlfXRCD4ppMMYSV18tnJ7 -TWanmXVeCQHce2aZ7241ejo3hGXM5LChWupYLGEelquhv5IRHVUcjVuEbHqQUcFR -gwccVDdJwfbk7T7YtttD7V2SCcnC2OfOZy/4mlI1LfgsodqrMiBkNJRsfVRVlPse -bLRgi4H2WxzmjKNEvuPbqRLF+aeDMkW3OMwP73/sYJhpuX9WAdTcJt9iQYVUaLq5 -h4YvNjC19x8cKOkf0iDqwyHFKxC2AvV0Qti1FmCSLENUDLaxP9F1RZiAevIFPSak -UzV4Swsi9fSt1lBr0VxvTeZk2mUgSGHKO2a7W0xR7SMUjmMamEzrZDylWMRp9SNl -hwIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/certs/alipay-public-key.pem b/certs/alipay-public-key.pem deleted file mode 100644 index 8c927a9..0000000 --- a/certs/alipay-public-key.pem +++ /dev/null @@ -1 +0,0 @@ -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5swLKPSzOMucRC52c9kKJZI9cYWDFd+s3UuE+aDtWodGrGV8g3szmp7hUWlaWY/didKc9vQNq93y67eEyw6QsMn26WwlzDbgP0xTcHEt+qDCeAltSqf6MX3KPmlz0f/DNneR9DR9ZGwaW1ATY3kg8gj+kIWngrqgjOv37UJWEpQOxUfWDGTBC1zzhC0PTXY7lX3GUZmDEtDtBs1BsFUdk995TbTD1cTiyDFuea49br0dovmU1ROOg6vK3G9xDd4Mke/opDunLTHe63+fBCnB7FyZ9F8zWg4LYND1QPmIX2m5gwICBHhNm8WqIfp9T64vpAxlM74BEsMlv3hNy0INQQIDAQAB \ No newline at end of file diff --git a/certs/alipayCertPublicKey_RSA2.crt b/certs/alipayCertPublicKey_RSA2.crt deleted file mode 100644 index 60f26b0..0000000 --- a/certs/alipayCertPublicKey_RSA2.crt +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5swLKPSzOMucRC52c9kKJZI9cYWDFd+s3UuE+aDtWodGrGV8g3szmp7hUWlaWY/didKc9vQNq93y67eEyw6QsMn26WwlzDbgP0xTcHEt+qDCeAltSqf6MX3KPmlz0f/DNneR9DR9ZGwaW1ATY3kg8gj+kIWngrqgjOv37UJWEpQOxUfWDGTBC1zzhC0PTXY7lX3GUZmDEtDtBs1BsFUdk995TbTD1cTiyDFuea49br0dovmU1ROOg6vK3G9xDd4Mke/opDunLTHe63+fBCnB7FyZ9F8zWg4LYND1QPmIX2m5gwICBHhNm8WqIfp9T64vpAxlM74BEsMlv3hNy0INQQIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/certs/alipayPublicKey_RSA2.crt b/certs/alipayPublicKey_RSA2.crt deleted file mode 100644 index 60f26b0..0000000 --- a/certs/alipayPublicKey_RSA2.crt +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5swLKPSzOMucRC52c9kKJZI9cYWDFd+s3UuE+aDtWodGrGV8g3szmp7hUWlaWY/didKc9vQNq93y67eEyw6QsMn26WwlzDbgP0xTcHEt+qDCeAltSqf6MX3KPmlz0f/DNneR9DR9ZGwaW1ATY3kg8gj+kIWngrqgjOv37UJWEpQOxUfWDGTBC1zzhC0PTXY7lX3GUZmDEtDtBs1BsFUdk995TbTD1cTiyDFuea49br0dovmU1ROOg6vK3G9xDd4Mke/opDunLTHe63+fBCnB7FyZ9F8zWg4LYND1QPmIX2m5gwICBHhNm8WqIfp9T64vpAxlM74BEsMlv3hNy0INQQIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/certs/appCertPublicKey.crt b/certs/appCertPublicKey.crt deleted file mode 100644 index d13ac61..0000000 --- a/certs/appCertPublicKey.crt +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlfXRCD4ppMMYSV18tnJ7TWanmXVeCQHce2aZ7241ejo3hGXM5LChWupYLGEelquhv5IRHVUcjVuEbHqQUcFRgwccVDdJwfbk7T7YtttD7V2SCcnC2OfOZy/4mlI1LfgsodqrMiBkNJRsfVRVlPsebLRgi4H2WxzmjKNEvuPbqRLF+aeDMkW3OMwP73/sYJhpuX9WAdTcJt9iQYVUaLq5h4YvNjC19x8cKOkf0iDqwyHFKxC2AvV0Qti1FmCSLENUDLaxP9F1RZiAevIFPSakUzV4Swsi9fSt1lBr0VxvTeZk2mUgSGHKO2a7W0xR7SMUjmMamEzrZDylWMRp9SNlhwIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/config/dbv2.js b/config/dbv2.js index e7e253b..9d971f1 100644 --- a/config/dbv2.js +++ b/config/dbv2.js @@ -1,13 +1,15 @@ +// queryBuilder.js class QueryBuilder { constructor() { - this.conditions = {}; + this.conditions = []; // { sql, params } this.limit = null; this.offset = null; this.groupBy = null; + this.orderBy = null; } where(condition, ...params) { - this.conditions[condition] = params; + this.conditions.push({ sql: condition, params }); return this; } @@ -26,88 +28,48 @@ class QueryBuilder { return this; } - sqdata(sql, params) { - return new Promise((resolve, reject) => { - global.sqlReq.query(sql, params, (err, result) => { - if (err) { - reject(err); - } - resolve(result); - }); - }); - } - - getParams() { - return Object.values(this.conditions).flat(); + orderByField(field, direction = 'ASC') { + this.orderBy = { field, direction: direction.toUpperCase() }; + return this; } buildConditions() { - return Object.keys(this.conditions).map(condition => `${condition}`).join(' AND '); + if (!this.conditions.length) return { sql: '', params: [] }; + const sql = this.conditions.map(c => `(${c.sql})`).join(' AND '); + const params = this.conditions.flatMap(c => c.params); + return { sql, params }; + } + + buildLimitOffset() { + let sql = ''; + if (this.limit !== null) sql += ` LIMIT ${this.limit}`; + if (this.offset !== null) sql += ` OFFSET ${this.offset}`; + return sql; + } + + async execute(db) { + const { sql, params } = this.build(); + const [rows] = await db.query(sql, params); + return rows; } } +// ------------------- SELECT ------------------- class SelectBuilder extends QueryBuilder { constructor() { super(); this.selectFields = []; this.tables = []; - this.orderByField = ''; - this.orderByDirection = 'ASC'; - this.subQueries = []; // 用于存储子查询 - this.unions = []; // 存储UNION查询 - } - // 添加UNION查询 - union(queryBuilder, type = 'UNION') { - this.unions.push({queryBuilder, type}); - return this; - } - - // 添加UNION ALL查询 - unionAll(queryBuilder) { - this.union(queryBuilder, 'UNION ALL'); - return this; - } - - // 构建主查询部分(不含ORDER BY/LIMIT/OFFSET) - buildMainQuery() { - const subQuerySQL = this.subQueries.map(({alias, subQuery}) => `(${subQuery}) AS ${alias}`); - const selectClause = this.selectFields.concat(subQuerySQL).join(', '); - - let sql = `SELECT ${selectClause} - FROM ${this.tables.join(' ')}`; - - const conditionClauses = this.buildConditions(); - if (conditionClauses) { - sql += ` WHERE ${conditionClauses}`; - } - - if (this.groupBy) { - sql += ` GROUP BY ${this.groupBy}`; - } - - const params = this.getParams(); - return {sql, params}; - } - - // 供UNION查询调用的构建方法 - buildForUnion() { - return this.buildMainQuery(); + this.joins = []; + this.unions = []; } select(fields) { - this.selectFields = fields.split(',').map(field => field.trim()); - return this; - } - - // 添加子查询 - addSubQuery(alias, subQuery) { - this.subQueries.push({alias, subQuery}); - return this; - } - - whereLike(fields, keyword) { - const likeConditions = fields.map(field => `${field} LIKE ?`).join(' OR '); - this.conditions[likeConditions] = fields.map(() => `%${keyword}%`); + if (typeof fields === 'string') { + this.selectFields = fields.split(',').map(f => f.trim()); + } else if (Array.isArray(fields)) { + this.selectFields = fields; + } return this; } @@ -116,78 +78,76 @@ class SelectBuilder extends QueryBuilder { return this; } - leftJoin(table, condition) { - this.tables.push(`LEFT JOIN ${table} ON ${condition}`); + join(type, table, condition) { + this.joins.push(`${type.toUpperCase()} JOIN ${table} ON ${condition}`); return this; } - orderBy(field, direction = 'ASC') { - this.orderByField = field; - this.orderByDirection = direction.toUpperCase(); + union(queryBuilder, type = 'UNION') { + this.unions.push({ queryBuilder, type }); return this; } - paginate(page, pageSize) { + unionAll(queryBuilder) { + return this.union(queryBuilder, 'UNION ALL'); + } + + build() { + const selectClause = this.selectFields.length ? this.selectFields.join(', ') : '*'; + let sql = `SELECT ${selectClause} FROM ${this.tables.join(', ')}`; + if (this.joins.length) sql += ' ' + this.joins.join(' '); + + const { sql: whereSql, params } = this.buildConditions(); + if (whereSql) sql += ` WHERE ${whereSql}`; + + if (this.groupBy) sql += ` GROUP BY ${this.groupBy}`; + + // 处理 UNION + for (const u of this.unions) { + const { sql: uSql, params: uParams } = u.queryBuilder.build(); + sql += ` ${u.type} ${uSql}`; + params.push(...uParams); + } + + if (this.orderBy) { + sql += ` ORDER BY ${this.orderBy.field} ${this.orderBy.direction}`; + } + + sql += this.buildLimitOffset(); + return { sql, params }; + } + + async paginateWithCount(db, page = 1, pageSize = 10) { if (page <= 0 || pageSize <= 0) { throw new Error('分页参数必须大于0'); } - this.limit = pageSize; - this.offset = (page - 1) * pageSize; - return this; - } - async chidBuild() { + // count 查询 + let countSql = `SELECT COUNT(*) as total FROM ${this.tables.join(', ')}`; + if (this.joins.length) countSql += ' ' + this.joins.join(' '); - let sql = `SELECT ${this.selectFields.join(', ')} - FROM ${this.tables.join(' ')}`; - let conditionClauses = this.buildConditions(); - if (conditionClauses) { - sql += ` WHERE ${conditionClauses}`; - } - if (this.orderByField) { - sql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; - } - if (this.limit !== null) { - sql += ` LIMIT ${this.limit}`; - } - if (this.offset !== null) { - sql += ` OFFSET ${this.offset}`; - } - return sql; - } + const { sql: whereSql, params } = this.buildConditions(); + if (whereSql) countSql += ` WHERE ${whereSql}`; - async build() { - const main = this.buildMainQuery(); - let fullSql = `(${main.sql})`; - const allParams = [...main.params]; + const [countRows] = await db.query(countSql, params); + const total = countRows[0].total; - // 处理UNION部分 - for (const union of this.unions) { - const unionBuilder = union.queryBuilder; - if (!(unionBuilder instanceof SelectBuilder)) { - throw new Error('UNION query must be a SelectBuilder instance'); - } - const unionResult = unionBuilder.buildForUnion(); - fullSql += ` ${union.type} (${unionResult.sql})`; - allParams.push(...unionResult.params); - } + // 数据查询 + this.setLimit(pageSize).setOffset((page - 1) * pageSize); + const { sql, params: dataParams } = this.build(); + const [rows] = await db.query(sql, dataParams); - // 添加ORDER BY、LIMIT、OFFSET - if (this.orderByField) { - fullSql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; - } - if (this.limit !== null) { - fullSql += ` LIMIT ${this.limit}`; - } - if (this.offset !== null) { - fullSql += ` OFFSET ${this.offset}`; - } - console.log(fullSql,allParams); - return await this.sqdata(fullSql, allParams); + return { + data: rows, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize) + }; } } - +// ------------------- UPDATE ------------------- class UpdateBuilder extends QueryBuilder { constructor() { super(); @@ -201,157 +161,107 @@ class UpdateBuilder extends QueryBuilder { } set(field, value) { - if (value && value.increment && typeof value === 'object' ) { - this.updateFields[field] = {increment: value.increment}; + if (typeof value === 'object' && value.increment !== undefined) { + this.updateFields[field] = { increment: value.increment }; } else { this.updateFields[field] = value; } return this; } - async build() { - let sql = `UPDATE ${this.table} - SET `; - let updateClauses = Object.keys(this.updateFields).map(field => { - const value = this.updateFields[field]; - if (value && value.increment && typeof value === 'object' ) { + setFields(fieldsObj) { + Object.entries(fieldsObj).forEach(([k, v]) => this.set(k, v)); + return this; + } + + build() { + const updateClauses = Object.keys(this.updateFields).map(field => { + const val = this.updateFields[field]; + if (typeof val === 'object' && val.increment !== undefined) { return `${field} = ${field} + ?`; } return `${field} = ?`; }).join(', '); - sql += updateClauses; + let sql = `UPDATE ${this.table} SET ${updateClauses}`; + const { sql: whereSql, params: whereParams } = this.buildConditions(); + if (whereSql) sql += ` WHERE ${whereSql}`; - let conditionClauses = this.buildConditions(); - if (conditionClauses) { - sql += ` WHERE ${conditionClauses}`; - } - // 处理参数,确保自增字段也传入增量值 const params = [ - ...Object.values(this.updateFields).map(value => - (value && value.increment && typeof value === 'object' ) ? value.increment : value + ...Object.values(this.updateFields).map(v => + (typeof v === 'object' && v.increment !== undefined) ? v.increment : v ), - ...this.getParams() + ...whereParams ]; - return await this.sqdata(sql, params); + return { sql, params }; } } +// ------------------- INSERT ------------------- class InsertBuilder extends QueryBuilder { constructor() { super(); this.table = ''; - this.insertValues = []; + this.records = []; this.updateValues = {}; } - insertInto(table) { + into(table) { this.table = table; return this; } - // 仍然保留单条记录的插入 - values(values) { - if (Array.isArray(values)) { - this.insertValues = values; - } else { - this.insertValues = [values]; // 将单条记录包装成数组 - } + values(records) { + if (!Array.isArray(records)) records = [records]; + this.records = records; return this; } - // 新增方法,支持一次插入多条记录 - valuesMultiple(records) { - if (!Array.isArray(records) || records.length === 0) { - throw new Error('Values must be a non-empty array'); - } - - // 确保每一条记录都是对象 - records.forEach(record => { - if (typeof record !== 'object') { - throw new Error('Each record must be an object'); - } - }); - - this.insertValues = records; + upsert(records, updateFields) { + this.values(records); + this.updateValues = updateFields; return this; } - // 新增 upsert 方法,支持更新或插入 - upsert(values, updateFields) { - // values: 要插入的记录 - // updateFields: 如果记录存在时,需要更新的字段 - if (!Array.isArray(values) || values.length === 0) { - throw new Error('Values must be a non-empty array'); + build() { + if (!this.records.length) throw new Error('No values to insert'); + + const columns = Object.keys(this.records[0]); + const placeholders = `(${columns.map(() => '?').join(', ')})`; + const valuesClause = this.records.map(() => placeholders).join(', '); + const params = this.records.flatMap(r => columns.map(c => r[c])); + + let sql = `INSERT INTO ${this.table} (${columns.join(', ')}) VALUES ${valuesClause}`; + + if (Object.keys(this.updateValues).length) { + const updates = Object.keys(this.updateValues) + .map(f => `${f} = VALUES(${f})`) // MySQL 8.0+ 兼容写法 + .join(', '); + sql += ` ON DUPLICATE KEY UPDATE ${updates}`; } - // 检查每条记录是否是对象 - values.forEach(record => { - if (typeof record !== 'object') { - throw new Error('Each record must be an object'); - } - }); - - this.insertValues = values; - this.updateValues = updateFields || {}; - return this; - } - - async build() { - if (this.insertValues.length === 0) { - throw new Error("No values to insert"); - } - - // 获取表单列名,假设所有记录有相同的字段 - const columns = Object.keys(this.insertValues[0]); - - // 构建 VALUES 子句,支持批量插入 - const valuePlaceholders = this.insertValues.map(() => - `(${columns.map(() => '?').join(', ')})` - ).join(', '); - - // 展平所有的插入值 - const params = this.insertValues.flatMap(record => - columns.map(column => record[column]) - ); - - // 如果有 updateFields,构建 ON DUPLICATE KEY UPDATE 子句 - let updateClause = ''; - if (Object.keys(this.updateValues).length > 0) { - updateClause = ' ON DUPLICATE KEY UPDATE ' + - Object.keys(this.updateValues).map(field => { - return `${field} = VALUES(${field})`; - }).join(', '); - } - - // 生成 SQL 语句 - const sql = `INSERT INTO ${this.table} (${columns.join(', ')}) - VALUES ${valuePlaceholders} ${updateClause}`; - // 执行查询 - return await this.sqdata(sql, params); + return { sql, params }; } } - +// ------------------- DELETE ------------------- class DeleteBuilder extends QueryBuilder { constructor() { super(); this.table = ''; } - deleteFrom(table) { + from(table) { this.table = table; return this; } - async build() { - let sql = `DELETE - FROM ${this.table}`; - let conditionClauses = this.buildConditions(); - if (conditionClauses) { - sql += ` WHERE ${conditionClauses}`; - } - return await this.sqdata(sql, this.getParams()); + build() { + let sql = `DELETE FROM ${this.table}`; + const { sql: whereSql, params } = this.buildConditions(); + if (!whereSql) throw new Error('DELETE without WHERE is not allowed!'); + sql += ` WHERE ${whereSql}`; + return { sql, params }; } } @@ -359,5 +269,5 @@ module.exports = { SelectBuilder, UpdateBuilder, InsertBuilder, - DeleteBuilder, + DeleteBuilder }; diff --git a/database.js b/database.js index 949a3af..5d8b8df 100644 --- a/database.js +++ b/database.js @@ -14,7 +14,7 @@ const dbConfig = { dateStrings: true, // 连接池配置 connectionLimit: 20, // 连接池最大连接数 - queueLimit: 0, // 排队等待连接的最大数量,0表示无限制 + // queueLimit: 0, // 排队等待连接的最大数量,0表示无限制 // 连接超时配置 // acquireTimeout: 60000, // 获取连接超时时间 60秒 // timeout: 60000, // 查询超时时间 60秒 diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 73d8db2..0000000 --- a/docs/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# API 文档结构说明 - -本项目已将 Swagger API 文档从路由文件中分离出来,采用模块化的文档管理方式。 - -## 文件夹结构 - -``` -docs/ -├── README.md # 本说明文件 -├── schemas/ # 数据模型定义 -│ ├── product.js # 商品相关数据模型 -│ ├── order.js # 订单相关数据模型 -│ ├── user.js # 用户相关数据模型 -│ ├── cart.js # 购物车相关数据模型 -│ └── announcement.js # 通知公告相关数据模型 -└── apis/ # API 接口定义 - ├── products.js # 商品相关 API - ├── orders.js # 订单相关 API - └── announcements.js # 通知公告相关 API -``` - -## 优势 - -1. **模块化管理**: 按功能模块分离文档,便于维护和查找 -2. **代码分离**: 路由文件专注于业务逻辑,文档定义独立管理 -3. **复用性**: Schema 定义可以在多个 API 中复用 -4. **可维护性**: 文档修改不会影响业务代码,降低出错风险 - -## 使用方法 - -### 添加新的 Schema - -在 `schemas/` 文件夹中创建新的 `.js` 文件,使用 `@swagger` 注释定义数据模型: - -```javascript -/** - * @swagger - * components: - * schemas: - * YourModel: - * type: object - * properties: - * id: - * type: integer - * description: ID - */ -``` - -### 添加新的 API 文档 - -在 `apis/` 文件夹中创建新的 `.js` 文件,使用 `@swagger` 注释定义 API 接口: - -```javascript -/** - * @swagger - * tags: - * name: YourModule - * description: 模块描述 - */ - -/** - * @swagger - * /your-endpoint: - * get: - * summary: 接口描述 - * tags: [YourModule] - * responses: - * 200: - * description: 成功响应 - */ -``` - -## 配置 - -Swagger 配置文件 `swagger.js` 已更新扫描路径: - -```javascript -apis: ['./docs/schemas/*.js', './docs/apis/*.js', './routes/*.js', './admin/routes/*.js'] -``` - -这样既保持了对现有路由文件中文档的兼容性,又支持新的模块化文档结构。 \ No newline at end of file diff --git a/docs/apis/announcements.js b/docs/apis/announcements.js deleted file mode 100644 index 12c5eea..0000000 --- a/docs/apis/announcements.js +++ /dev/null @@ -1,736 +0,0 @@ -/** - * @swagger - * tags: - * name: Announcements - * description: 通知公告管理API - */ - -/** - * @swagger - * /api/announcements/{id}: - * get: - * summary: 获取单个公告详情 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * responses: - * 200: - * description: 成功获取公告详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * $ref: '#/components/schemas/Announcement' - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/{id}/read: - * post: - * summary: 标记公告为已读 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * responses: - * 200: - * description: 标记已读成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "已标记为已读" - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/unread/count: - * get: - * summary: 获取用户未读公告数量 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 获取未读数量成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * unread_count: - * type: integer - * example: 5 - * description: 未读公告数量 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/batch/read: - * post: - * summary: 批量标记公告为已读 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - announcement_ids - * properties: - * announcement_ids: - * type: array - * items: - * type: integer - * example: [1, 2, 3] - * description: 公告ID列表 - * responses: - * 200: - * description: 批量标记已读成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "批量标记已读成功" - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements: - * post: - * summary: 创建新公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - title - * - content - * properties: - * title: - * type: string - * description: 公告标题 - * content: - * type: string - * description: 公告内容 - * type: - * type: string - * enum: [system, maintenance, promotion, warning] - * default: system - * priority: - * type: string - * enum: [low, medium, high, urgent] - * default: medium - * status: - * type: string - * enum: [draft, published] - * default: draft - * is_pinned: - * type: boolean - * default: false - * publish_time: - * type: string - * format: date-time - * expire_time: - * type: string - * format: date-time - * responses: - * 201: - * description: 公告创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * id: - * type: integer - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/{id}: - * put: - * summary: 更新公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * title: - * type: string - * content: - * type: string - * type: - * type: string - * enum: [system, maintenance, promotion, warning] - * priority: - * type: string - * enum: [low, medium, high, urgent] - * status: - * type: string - * enum: [draft, published, archived] - * is_pinned: - * type: boolean - * publish_time: - * type: string - * format: date-time - * expire_time: - * type: string - * format: date-time - * responses: - * 200: - * description: 公告更新成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * $ref: '#/components/schemas/Announcement' - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - * - * delete: - * summary: 删除公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * responses: - * 200: - * description: 公告删除成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/public/list: - * get: - * summary: 获取公开发布的公告列表(无需认证) - * tags: [Announcements] - * parameters: - * - in: query - * name: limit - * schema: - * type: integer - * default: 5 - * description: 获取数量 - * responses: - * 200: - * description: 成功获取公开公告列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/Announcement' - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * components: - * schemas: - * Announcement: - * type: object - * required: - * - title - * - content - * properties: - * id: - * type: integer - * description: 公告ID - * title: - * type: string - * description: 公告标题 - * content: - * type: string - * description: 公告内容 - * type: - * type: string - * description: 公告类型 - * enum: [system, maintenance, promotion, warning] - * priority: - * type: string - * description: 优先级 - * enum: [low, medium, high, urgent] - * status: - * type: string - * description: 状态 - * enum: [draft, published, archived] - * is_pinned: - * type: boolean - * description: 是否置顶 - * publish_time: - * type: string - * format: date-time - * description: 发布时间 - * expire_time: - * type: string - * format: date-time - * description: 过期时间 - * created_by: - * type: integer - * description: 创建者ID - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - */ - -/** - * @swagger - * /api/announcements: - * get: - * summary: 获取通知公告列表 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: search - * schema: - * type: string - * description: 搜索关键词(标题或内容) - * - in: query - * name: type - * schema: - * type: string - * enum: [system, activity, maintenance, urgent] - * description: 公告类型 - * - in: query - * name: priority - * schema: - * type: string - * enum: [high, medium, low] - * description: 优先级 - * - in: query - * name: status - * schema: - * type: string - * enum: [draft, published, expired] - * description: 状态 - * - in: query - * name: isTop - * schema: - * type: boolean - * description: 是否置顶 - * - in: query - * name: sortBy - * schema: - * type: string - * enum: [created_at, updated_at, publish_time, priority] - * default: created_at - * description: 排序字段 - * - in: query - * name: sortOrder - * schema: - * type: string - * enum: [ASC, DESC] - * default: DESC - * description: 排序方向 - * responses: - * 200: - * description: 成功获取公告列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * announcements: - * type: array - * items: - * $ref: '#/components/schemas/Announcement' - * total: - * type: integer - * example: 50 - * page: - * type: integer - * example: 1 - * limit: - * type: integer - * example: 10 - * totalPages: - * type: integer - * example: 5 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - * - * post: - * summary: 创建新的通知公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - title - * - content - * - type - * - priority - * properties: - * title: - * type: string - * description: 公告标题 - * example: "系统维护通知" - * content: - * type: string - * description: 公告内容 - * example: "系统将于今晚进行维护,预计维护时间2小时" - * type: - * type: string - * enum: [system, activity, maintenance, urgent] - * description: 公告类型 - * example: "maintenance" - * priority: - * type: string - * enum: [high, medium, low] - * description: 优先级 - * example: "high" - * status: - * type: string - * enum: [draft, published] - * default: draft - * description: 状态 - * isTop: - * type: boolean - * default: false - * description: 是否置顶 - * publishTime: - * type: string - * format: date-time - * description: 发布时间 - * expireTime: - * type: string - * format: date-time - * description: 过期时间 - * responses: - * 201: - * description: 公告创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "公告创建成功" - * data: - * $ref: '#/components/schemas/Announcement' - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/{id}: - * put: - * summary: 更新通知公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * title: - * type: string - * description: 公告标题 - * content: - * type: string - * description: 公告内容 - * type: - * type: string - * enum: [system, activity, maintenance, urgent] - * description: 公告类型 - * priority: - * type: string - * enum: [high, medium, low] - * description: 优先级 - * status: - * type: string - * enum: [draft, published, expired] - * description: 状态 - * isTop: - * type: boolean - * description: 是否置顶 - * publishTime: - * type: string - * format: date-time - * description: 发布时间 - * expireTime: - * type: string - * format: date-time - * description: 过期时间 - * responses: - * 200: - * description: 公告更新成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "公告更新成功" - * data: - * $ref: '#/components/schemas/Announcement' - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - * - * delete: - * summary: 删除通知公告 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * responses: - * 200: - * description: 公告删除成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "公告删除成功" - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/announcements/{id}/toggle-top: - * put: - * summary: 切换公告置顶状态 - * tags: [Announcements] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 公告ID - * responses: - * 200: - * description: 置顶状态切换成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "置顶状态更新成功" - * data: - * type: object - * properties: - * isTop: - * type: boolean - * example: true - * 401: - * description: 未授权 - * 404: - * description: 公告不存在 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/apis/captcha.js b/docs/apis/captcha.js deleted file mode 100644 index 3a46213..0000000 --- a/docs/apis/captcha.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @swagger - * tags: - * name: Captcha - * description: 验证码API - */ -/** - * @swagger - * /captcha/generate: - * get: - * summary: 生成图形验证码 - * tags: [Captcha] - * responses: - * 200: - * description: 成功生成验证码 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * captchaId: - * type: string - * description: 验证码唯一ID - * image: - * type: string - * description: Base64编码的SVG验证码图片 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /captcha/verify: - * post: - * summary: 验证用户输入的验证码 - * tags: [Captcha] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - captchaId - * - captchaText - * properties: - * captchaId: - * type: string - * description: 验证码唯一ID - * captchaText: - * type: string - * description: 用户输入的验证码 - * responses: - * 200: - * description: 验证码验证成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 验证码验证成功 - * 400: - * description: 验证码错误或已过期 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * message: - * type: string - * example: 验证码错误 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/apis/matching.js b/docs/apis/matching.js deleted file mode 100644 index 7e7a72b..0000000 --- a/docs/apis/matching.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @swagger - * tags: - * name: Matching - * description: 匹配订单相关接口 - */ -/** - * @swagger - * /api/matching/my-orders: - * get: - * summary: 获取用户的匹配订单列表 - * tags: [Matching] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * responses: - * 200: - * description: 成功获取匹配订单列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/MatchingOrder' - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * components: - * schemas: - * MatchingOrder: - * type: object - * properties: - * id: - * type: integer - * description: 匹配订单ID - * initiator_id: - * type: integer - * description: 发起人ID - * matching_type: - * type: string - * enum: [small, large] - * description: 匹配类型(小额或大额) - * amount: - * type: number - * description: 匹配总金额 - * status: - * type: string - * enum: [pending, matching, completed, failed] - * description: 订单状态 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * Allocation: - * type: object - * properties: - * id: - * type: integer - * description: 分配ID - * from_user_id: - * type: integer - * description: 发送方用户ID - * to_user_id: - * type: integer - * description: 接收方用户ID - * amount: - * type: number - * description: 分配金额 - * cycle_number: - * type: integer - * description: 轮次编号 - * status: - * type: string - * enum: [pending, confirmed, rejected, cancelled] - * description: 分配状态 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - */ - -/** - * @swagger - * /api/matching/create: - * post: - * summary: 创建匹配订单 - * tags: [Matching] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * matchingType: - * type: string - * enum: [small, large] - * default: small - * description: 匹配类型(小额或大额) - * customAmount: - * type: number - * description: 大额匹配时的自定义金额(5000-50000之间) - * responses: - * 200: - * description: 匹配订单创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * matchingOrderId: - * type: integer - * amounts: - * type: array - * items: - * type: number - * matchingType: - * type: string - * totalAmount: - * type: number - * 400: - * description: 参数错误或用户未满足匹配条件 - * 401: - * description: 未授权 - * 404: - * description: 用户不存在 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/apis/orders.js b/docs/apis/orders.js deleted file mode 100644 index 8342c27..0000000 --- a/docs/apis/orders.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * @swagger - * tags: - * name: Orders - * description: 订单管理API - */ - -/** - * @swagger - * /api/orders: - * get: - * summary: 获取订单列表 - * tags: [Orders] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: search - * schema: - * type: string - * description: 搜索关键词 - * - in: query - * name: orderNumber - * schema: - * type: string - * description: 订单号 - * - in: query - * name: username - * schema: - * type: string - * description: 用户名 - * - in: query - * name: status - * schema: - * type: string - * description: 订单状态 - * - in: query - * name: startDate - * schema: - * type: string - * format: date - * description: 开始日期 - * - in: query - * name: endDate - * schema: - * type: string - * format: date - * description: 结束日期 - * responses: - * 200: - * description: 成功获取订单列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * orders: - * type: array - * items: - * $ref: '#/components/schemas/Order' - * pagination: - * type: object - * properties: - * page: - * type: integer - * limit: - * type: integer - * total: - * type: integer - * pages: - * type: integer - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/orders/confirm: - * post: - * summary: 确认下单 - * tags: [Orders] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - pre_order_id - * - address - * properties: - * pre_order_id: - * type: integer - * description: 预订单ID - * address: - * type: object - * properties: - * recipient_name: - * type: string - * description: 收货人姓名 - * phone: - * type: string - * description: 收货人电话 - * province: - * type: string - * description: 省份 - * city: - * type: string - * description: 城市 - * district: - * type: string - * description: 区县 - * detail_address: - * type: string - * description: 详细地址 - * responses: - * 200: - * description: 确认下单成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * order_id: - * type: integer - * order_no: - * type: string - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 404: - * description: 预订单不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/orders/pre-order/{id}: - * get: - * summary: 获取预订单详情 - * tags: [Orders] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 预订单ID - * responses: - * 200: - * description: 获取预订单详情成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * id: - * type: integer - * order_no: - * type: string - * total_amount: - * type: integer - * total_points: - * type: integer - * total_rongdou: - * type: integer - * status: - * type: string - * created_at: - * type: string - * items: - * type: array - * items: - * type: object - * properties: - * id: - * type: integer - * product_id: - * type: integer - * product_name: - * type: string - * quantity: - * type: integer - * price: - * type: integer - * points_price: - * type: integer - * rongdou_price: - * type: integer - * spec_info: - * type: object - * 401: - * description: 未授权 - * 404: - * description: 预订单不存在 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /api/orders/{id}: - * get: - * summary: 获取单个订单详情 - * tags: [Orders] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 订单ID - * responses: - * 200: - * description: 成功获取订单详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * order: - * $ref: '#/components/schemas/Order' - * 401: - * description: 未授权 - * 404: - * description: 订单不存在 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/apis/products.js b/docs/apis/products.js deleted file mode 100644 index da8520c..0000000 --- a/docs/apis/products.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * @swagger - * tags: - * name: Products - * description: 商品管理API - */ - -/** - * @swagger - * /products: - * get: - * summary: 获取商品列表 - * tags: [Products] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: search - * schema: - * type: string - * description: 搜索关键词 - * - in: query - * name: category - * schema: - * type: string - * description: 商品分类 - * - in: query - * name: status - * schema: - * type: string - * enum: [active, inactive] - * description: 商品状态 - * responses: - * 200: - * description: 成功获取商品列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * products: - * type: array - * items: - * $ref: '#/components/schemas/Product' - * pagination: - * type: object - * properties: - * page: - * type: integer - * limit: - * type: integer - * total: - * type: integer - * pages: - * type: integer - */ - -/** - * @swagger - * /products/categories: - * get: - * summary: 获取商品分类列表 - * tags: [Products] - * responses: - * 200: - * description: 成功获取分类列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * type: string - */ - -/** - * @swagger - * /products/hot: - * get: - * summary: 获取热门商品 - * tags: [Products] - * parameters: - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 返回数量 - * responses: - * 200: - * description: 成功获取热门商品 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * products: - * type: array - * items: - * $ref: '#/components/schemas/Product' - */ - -/** - * @swagger - * /products/{id}: - * get: - * summary: 获取商品详情 - * tags: [Products] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 商品ID - * responses: - * 200: - * description: 成功获取商品详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * $ref: '#/components/schemas/Product' - * 404: - * description: 商品不存在 - */ \ No newline at end of file diff --git a/docs/apis/transfers.js b/docs/apis/transfers.js deleted file mode 100644 index 1db9f85..0000000 --- a/docs/apis/transfers.js +++ /dev/null @@ -1,388 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * Transfer: - * type: object - * properties: - * id: - * type: integer - * description: 转账记录ID - * user_id: - * type: integer - * description: 用户ID - * recipient_id: - * type: integer - * description: 接收方用户ID - * amount: - * type: number - * format: float - * description: 转账金额 - * status: - * type: string - * enum: [pending, completed, failed, cancelled] - * description: 转账状态 - * transfer_type: - * type: string - * enum: [user_to_user, user_to_system, system_to_user] - * description: 转账类型 - * voucher_image: - * type: string - * description: 转账凭证图片路径 - * remark: - * type: string - * description: 转账备注 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - * Pagination: - * type: object - * properties: - * total: - * type: integer - * description: 总记录数 - * page: - * type: integer - * description: 当前页码 - * limit: - * type: integer - * description: 每页记录数 - * total_pages: - * type: integer - * description: 总页数 - */ -/** - * @swagger - * /transfers: - * get: - * summary: 获取转账列表 - * tags: [Transfers] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: status - * schema: - * type: string - * description: 转账状态过滤 - * - in: query - * name: transfer_type - * schema: - * type: string - * description: 转账类型过滤 - * - in: query - * name: start_date - * schema: - * type: string - * format: date - * description: 开始日期过滤 - * - in: query - * name: end_date - * schema: - * type: string - * format: date - * description: 结束日期过滤 - * - in: query - * name: search - * schema: - * type: string - * description: 搜索关键词(用户名或真实姓名) - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: sort - * schema: - * type: string - * description: 排序字段 - * - in: query - * name: order - * schema: - * type: string - * enum: [asc, desc] - * description: 排序方向 - * responses: - * 200: - * description: 成功获取转账列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * transfers: - * type: array - * items: - * $ref: '#/components/schemas/Transfer' - * pagination: - * $ref: '#/components/schemas/Pagination' - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /transfers/list: - * get: - * summary: 获取转账记录列表 - * tags: [Transfers] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: status - * schema: - * type: string - * description: 转账状态过滤 - * - in: query - * name: transfer_type - * schema: - * type: string - * description: 转账类型过滤 - * - in: query - * name: start_date - * schema: - * type: string - * format: date - * description: 开始日期过滤 - * - in: query - * name: end_date - * schema: - * type: string - * format: date - * description: 结束日期过滤 - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: sort - * schema: - * type: string - * description: 排序字段 - * - in: query - * name: order - * schema: - * type: string - * enum: [asc, desc] - * description: 排序方向 - * responses: - * 200: - * description: 成功获取转账记录列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * transfers: - * type: array - * items: - * $ref: '#/components/schemas/Transfer' - * pagination: - * $ref: '#/components/schemas/Pagination' - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /transfers/public-account: - * get: - * summary: 获取公户信息 - * tags: [Transfers] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取公户信息 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * id: - * type: integer - * description: 公户ID - * username: - * type: string - * description: 公户用户名 - * example: public_account - * real_name: - * type: string - * description: 公户名称 - * balance: - * type: number - * format: float - * description: 公户余额 - * 401: - * description: 未授权 - * 404: - * description: 公户不存在 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /transfers/create: - * post: - * summary: 创建转账记录 - * tags: [Transfers] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - to_user_id - * - amount - * - transfer_type - * properties: - * to_user_id: - * type: integer - * description: 接收方用户ID - * amount: - * type: number - * format: float - * description: 转账金额 - * transfer_type: - * type: string - * enum: [user_to_user, user_to_system, system_to_user] - * description: 转账类型 - * remark: - * type: string - * description: 转账备注 - * responses: - * 201: - * description: 转账记录创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 转账记录创建成功,等待确认 - * data: - * type: object - * properties: - * transfer_id: - * type: integer - * description: 转账记录ID - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /transfers/admin/create: - * post: - * summary: 管理员创建转账记录 - * tags: [Transfers] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - from_user_id - * - to_user_id - * - amount - * - transfer_type - * properties: - * from_user_id: - * type: integer - * description: 发送方用户ID - * to_user_id: - * type: integer - * description: 接收方用户ID - * amount: - * type: number - * format: float - * description: 转账金额 - * transfer_type: - * type: string - * enum: [user_to_user, user_to_system, system_to_user] - * description: 转账类型 - * description: - * type: string - * description: 转账描述 - * responses: - * 201: - * description: 转账记录创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 转账记录创建成功 - * data: - * type: object - * properties: - * transfer_id: - * type: integer - * description: 转账记录ID - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/apis/user.js b/docs/apis/user.js deleted file mode 100644 index c437846..0000000 --- a/docs/apis/user.js +++ /dev/null @@ -1,423 +0,0 @@ -/** - * @swagger - * tags: - * name: Authentication - * description: 用户认证API - */ -/** - * @swagger - * components: - * schemas: - * LoginCredentials: - * type: object - * required: - * - username - * - password - * properties: - * username: - * type: string - * description: 用户名或手机号 - * password: - * type: string - * description: 密码 - * RegisterRequest: - * type: object - * required: - * - username - * - phone - * - password - * - registrationCode - * - city - * - district_id - * - captchaId - * - captchaText - * - smsCode - * properties: - * username: - * type: string - * description: 用户名 - * phone: - * type: string - * description: 手机号 - * password: - * type: string - * description: 密码 - * registrationCode: - * type: string - * description: 注册激活码 - * city: - * type: string - * description: 城市 - * district_id: - * type: string - * description: 区域ID - * captchaId: - * type: string - * description: 图形验证码ID - * captchaText: - * type: string - * description: 图形验证码文本 - * smsCode: - * type: string - * description: 短信验证码 - * role: - * type: string - * description: 用户角色 - * default: user - */ -/** - * @swagger - * /users/pending-audit: - * get: - * summary: 获取待审核用户列表(管理员权限) - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * responses: - * 200: - * description: 成功获取待审核用户列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * users: - * type: array - * items: - * $ref: '#/components/schemas/User' - * pagination: - * type: object - * properties: - * page: - * type: integer - * limit: - * type: integer - * total: - * type: integer - * pages: - * type: integer - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /auth/register: - * post: - * summary: 用户注册 - * description: 需要提供有效的激活码才能注册 - * tags: [Authentication] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/RegisterRequest' - * responses: - * 201: - * description: 用户注册成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * token: - * type: string - * description: JWT认证令牌 - * user: - * type: object - * properties: - * id: - * type: integer - * username: - * type: string - * role: - * type: string - * 400: - * description: 请求参数错误 - * 500: - * description: 服务器错误 - */ - -/** - * @swagger - * /auth/login: - * post: - * summary: 用户登录 - * tags: [Authentication] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LoginCredentials' - * responses: - * 200: - * description: 登录成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * token: - * type: string - * description: JWT认证令牌 - * user: - * type: object - * properties: - * id: - * type: integer - * username: - * type: string - * role: - * type: string - * avatar: - * type: string - * points: - * type: integer - * 400: - * description: 请求参数错误 - * 401: - * description: 用户名或密码错误 - * 403: - * description: 账户审核未通过 - * 500: - * description: 服务器错误 - */ -/** - * @swagger - * /api/users/{id}/distribute: - * put: - * summary: 设置用户分发状态 - * description: 更新指定用户的分发状态 - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 用户ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - is_distribute - * properties: - * is_distribute: - * type: boolean - * description: 分发状态,true为启用分发,false为禁用分发 - * example: true - * responses: - * 200: - * description: 分发状态更新成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "分发状态更新成功" - * is_distribute: - * type: boolean - * description: 更新后的分发状态 - * example: true - * 400: - * description: 请求参数错误 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * message: - * type: string - * example: "分发状态无效" - * 404: - * description: 用户不存在 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * message: - * type: string - * example: "用户不存在" - * 500: - * description: 服务器内部错误 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * message: - * type: string - * example: "服务器内部错误" - */ -/** - * @swagger - * components: - * schemas: - * User: - * type: object - * required: - * - username - * - password - * - real_name - * - id_card - * properties: - * id: - * type: integer - * description: 用户ID - * username: - * type: string - * description: 用户名 - * role: - * type: string - * description: 用户角色 - * enum: [user, admin, merchant] - * avatar: - * type: string - * description: 用户头像URL - * points: - * type: integer - * description: 用户积分 - * real_name: - * type: string - * description: 真实姓名 - * id_card: - * type: string - * description: 身份证号 - * phone: - * type: string - * description: 手机号 - * is_system_account: - * type: boolean - * description: 是否为系统账户 - * is_distribute: - * type: boolean - * description: 是否为分发账户 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - */ - -/** - * @swagger - * /users: - * post: - * summary: 创建用户(管理员权限) - * tags: [Users] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - username - * - password - * - real_name - * - id_card - * properties: - * username: - * type: string - * password: - * type: string - * role: - * type: string - * enum: [user, admin, merchant] - * default: user - * is_system_account: - * type: boolean - * default: false - * real_name: - * type: string - * id_card: - * type: string - * wechat_qr: - * type: string - * alipay_qr: - * type: string - * bank_card: - * type: string - * unionpay_qr: - * type: string - * phone: - * type: string - * responses: - * 201: - * description: 用户创建成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * user: - * $ref: '#/components/schemas/User' - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ \ No newline at end of file diff --git a/docs/schemas/announcement.js b/docs/schemas/announcement.js deleted file mode 100644 index ddb6e92..0000000 --- a/docs/schemas/announcement.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * Announcement: - * type: object - * required: - * - title - * - content - * - type - * - priority - * properties: - * id: - * type: integer - * description: 公告ID - * example: 1 - * title: - * type: string - * description: 公告标题 - * example: "系统维护通知" - * content: - * type: string - * description: 公告内容 - * example: "系统将于今晚进行维护,预计维护时间2小时,期间可能影响部分功能使用。" - * type: - * type: string - * description: 公告类型 - * enum: [system, activity, maintenance, urgent] - * example: "maintenance" - * priority: - * type: string - * description: 优先级 - * enum: [high, medium, low] - * example: "high" - * status: - * type: string - * description: 公告状态 - * enum: [draft, published, expired] - * example: "published" - * isTop: - * type: boolean - * description: 是否置顶 - * example: false - * publishTime: - * type: string - * format: date-time - * description: 发布时间 - * example: "2024-01-15T10:00:00Z" - * expireTime: - * type: string - * format: date-time - * description: 过期时间 - * example: "2024-01-20T10:00:00Z" - * createdBy: - * type: integer - * description: 创建者用户ID - * example: 1 - * createdAt: - * type: string - * format: date-time - * description: 创建时间 - * example: "2024-01-15T09:00:00Z" - * updatedAt: - * type: string - * format: date-time - * description: 更新时间 - * example: "2024-01-15T09:30:00Z" - * creator: - * type: object - * description: 创建者信息 - * properties: - * id: - * type: integer - * example: 1 - * username: - * type: string - * example: "admin" - * email: - * type: string - * example: "admin@example.com" - * - * AnnouncementCreate: - * type: object - * required: - * - title - * - content - * - type - * - priority - * properties: - * title: - * type: string - * description: 公告标题 - * example: "系统维护通知" - * content: - * type: string - * description: 公告内容 - * example: "系统将于今晚进行维护,预计维护时间2小时。" - * type: - * type: string - * description: 公告类型 - * enum: [system, activity, maintenance, urgent] - * example: "maintenance" - * priority: - * type: string - * description: 优先级 - * enum: [high, medium, low] - * example: "high" - * status: - * type: string - * description: 公告状态 - * enum: [draft, published] - * default: draft - * example: "draft" - * isTop: - * type: boolean - * description: 是否置顶 - * default: false - * example: false - * publishTime: - * type: string - * format: date-time - * description: 发布时间 - * example: "2024-01-15T10:00:00Z" - * expireTime: - * type: string - * format: date-time - * description: 过期时间 - * example: "2024-01-20T10:00:00Z" - * - * AnnouncementUpdate: - * type: object - * properties: - * title: - * type: string - * description: 公告标题 - * example: "系统维护通知(更新)" - * content: - * type: string - * description: 公告内容 - * example: "系统维护时间调整为明晚进行。" - * type: - * type: string - * description: 公告类型 - * enum: [system, activity, maintenance, urgent] - * example: "maintenance" - * priority: - * type: string - * description: 优先级 - * enum: [high, medium, low] - * example: "medium" - * status: - * type: string - * description: 公告状态 - * enum: [draft, published, expired] - * example: "published" - * isTop: - * type: boolean - * description: 是否置顶 - * example: true - * publishTime: - * type: string - * format: date-time - * description: 发布时间 - * example: "2024-01-16T10:00:00Z" - * expireTime: - * type: string - * format: date-time - * description: 过期时间 - * example: "2024-01-21T10:00:00Z" - * - * AnnouncementList: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * announcements: - * type: array - * items: - * $ref: '#/components/schemas/Announcement' - * total: - * type: integer - * description: 总记录数 - * example: 50 - * page: - * type: integer - * description: 当前页码 - * example: 1 - * limit: - * type: integer - * description: 每页数量 - * example: 10 - * totalPages: - * type: integer - * description: 总页数 - * example: 5 - * - * AnnouncementResponse: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: "操作成功" - * data: - * $ref: '#/components/schemas/Announcement' - * - * AnnouncementError: - * type: object - * properties: - * success: - * type: boolean - * example: false - * message: - * type: string - * example: "操作失败" - * error: - * type: string - * example: "公告不存在" - */ \ No newline at end of file diff --git a/docs/schemas/cart.js b/docs/schemas/cart.js deleted file mode 100644 index 71ffbe9..0000000 --- a/docs/schemas/cart.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * CartItem: - * type: object - * required: - * - user_id - * - product_id - * - quantity - * properties: - * id: - * type: integer - * description: 购物车商品ID - * user_id: - * type: integer - * description: 用户ID - * product_id: - * type: integer - * description: 商品ID - * quantity: - * type: integer - * description: 商品数量 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - * - * CartItemWithProduct: - * type: object - * properties: - * id: - * type: integer - * description: 购物车商品ID - * product_id: - * type: integer - * description: 商品ID - * product_name: - * type: string - * description: 商品名称 - * quantity: - * type: integer - * description: 商品数量 - * points_price: - * type: integer - * description: 积分价格 - * rongdou_price: - * type: number - * description: 融豆价格 - * image_url: - * type: string - * description: 商品图片URL - * stock: - * type: integer - * description: 库存数量 - * payment_methods: - * type: array - * items: - * type: string - * description: 支付方式列表 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * - * AddToCartRequest: - * type: object - * required: - * - product_id - * - quantity - * properties: - * product_id: - * type: integer - * description: 商品ID - * quantity: - * type: integer - * minimum: 1 - * description: 商品数量 - * - * UpdateCartRequest: - * type: object - * required: - * - quantity - * properties: - * quantity: - * type: integer - * minimum: 1 - * description: 商品数量 - */ \ No newline at end of file diff --git a/docs/schemas/order.js b/docs/schemas/order.js deleted file mode 100644 index 9151207..0000000 --- a/docs/schemas/order.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * Order: - * type: object - * required: - * - user_id - * - total_amount - * - status - * properties: - * id: - * type: integer - * description: 订单ID - * order_number: - * type: string - * description: 订单号 - * user_id: - * type: integer - * description: 用户ID - * total_amount: - * type: number - * description: 订单总金额 - * total_points: - * type: integer - * description: 订单总积分 - * total_rongdou: - * type: number - * description: 订单总融豆 - * status: - * type: string - * description: 订单状态 - * enum: [pending, confirmed, shipped, delivered, cancelled] - * payment_status: - * type: string - * description: 支付状态 - * enum: [pending, paid, failed, refunded] - * shipping_address: - * type: string - * description: 收货地址 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - * - * OrderItem: - * type: object - * properties: - * id: - * type: integer - * description: 订单商品ID - * order_id: - * type: integer - * description: 订单ID - * product_id: - * type: integer - * description: 商品ID - * quantity: - * type: integer - * description: 商品数量 - * price: - * type: number - * description: 商品价格 - * points_price: - * type: integer - * description: 积分价格 - * rongdou_price: - * type: number - * description: 融豆价格 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * - * PreOrder: - * type: object - * properties: - * preOrderId: - * type: integer - * description: 预订单ID - * orderNumber: - * type: string - * description: 订单号 - * totalAmount: - * type: number - * description: 总金额 - * totalPoints: - * type: integer - * description: 所需积分总数 - * totalRongdou: - * type: number - * description: 所需融豆总数 - * paymentMethods: - * type: array - * items: - * type: string - * description: 去重后的支付方式列表 - */ \ No newline at end of file diff --git a/docs/schemas/product.js b/docs/schemas/product.js deleted file mode 100644 index 314372d..0000000 --- a/docs/schemas/product.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * Product: - * type: object - * required: - * - name - * - points_price - * - stock - * properties: - * id: - * type: integer - * description: 商品ID - * name: - * type: string - * description: 商品名称 - * category: - * type: string - * description: 商品分类 - * points_price: - * type: integer - * description: 积分价格 - * rongdou_price: - * type: number - * description: 融豆价格 - * stock: - * type: integer - * description: 库存数量 - * image_url: - * type: string - * description: 商品图片URL - * description: - * type: string - * description: 商品描述 - * status: - * type: string - * description: 商品状态 - * enum: [active, inactive] - * payment_methods: - * type: array - * items: - * type: string - * description: 支付方式列表 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - */ \ No newline at end of file diff --git a/docs/schemas/user.js b/docs/schemas/user.js deleted file mode 100644 index 08288a1..0000000 --- a/docs/schemas/user.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * @swagger - * components: - * schemas: - * User: - * type: object - * required: - * - username - * - email - * properties: - * id: - * type: integer - * description: 用户ID - * username: - * type: string - * description: 用户名 - * email: - * type: string - * format: email - * description: 邮箱地址 - * phone: - * type: string - * description: 手机号码 - * points: - * type: integer - * description: 积分余额 - * rongdou: - * type: number - * description: 融豆余额 - * avatar: - * type: string - * description: 头像URL - * status: - * type: string - * description: 用户状态 - * enum: [active, inactive, banned] - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - * - * UserProfile: - * type: object - * properties: - * id: - * type: integer - * description: 用户ID - * username: - * type: string - * description: 用户名 - * email: - * type: string - * description: 邮箱地址 - * phone: - * type: string - * description: 手机号码 - * points: - * type: integer - * description: 积分余额 - * rongdou: - * type: number - * description: 融豆余额 - * avatar: - * type: string - * description: 头像URL - * - * LoginRequest: - * type: object - * required: - * - username - * - password - * properties: - * username: - * type: string - * description: 用户名或邮箱 - * password: - * type: string - * description: 密码 - * - * RegisterRequest: - * type: object - * required: - * - username - * - email - * - password - * properties: - * username: - * type: string - * description: 用户名 - * email: - * type: string - * format: email - * description: 邮箱地址 - * password: - * type: string - * description: 密码 - * phone: - * type: string - * description: 手机号码 - */ \ No newline at end of file diff --git a/export-swagger.js b/export-swagger.js deleted file mode 100644 index e086c50..0000000 --- a/export-swagger.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 导出Swagger文档到JSON文件,用于导入Apifox - */ -const fs = require('fs'); -const path = require('path'); -const swaggerSpecs = require('./swagger'); - -// 确保输出目录存在 -const outputDir = path.join(__dirname, 'api-docs'); -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// 导出Swagger文档到JSON文件 -const outputPath = path.join(outputDir, 'swagger.json'); -fs.writeFileSync(outputPath, JSON.stringify(swaggerSpecs, null, 2)); - -console.log(`Swagger文档已导出到: ${outputPath}`); -console.log('现在您可以将此文件导入到Apifox中:'); -console.log('1. 打开Apifox应用'); -console.log('2. 选择您的项目'); -console.log('3. 点击"导入"按钮'); -console.log('4. 选择"导入OpenAPI(Swagger)"'); -console.log('5. 选择刚才导出的swagger.json文件'); -console.log('6. 按照Apifox的导入向导完成导入'); \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js index ddb8180..66f041c 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -10,7 +10,6 @@ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // 在生产环 const auth = async (req, res, next) => { try { const token = req.header('Authorization')?.replace('Bearer ', ''); - if (!token) { return res.status(401).json({ success: false, message: '未提供认证令牌' }); } @@ -48,6 +47,7 @@ const auth = async (req, res, next) => { req.user = user; next(); } catch (error) { + console.log(error); res.status(401).json({ success: false, message: '无效的认证令牌' }); } }; diff --git a/migrations/add_alipay_support.sql b/migrations/add_alipay_support.sql deleted file mode 100644 index 36d716d..0000000 --- a/migrations/add_alipay_support.sql +++ /dev/null @@ -1,17 +0,0 @@ --- 为支付订单表添加支付宝支持 --- 更新交易类型枚举,添加支付宝相关类型 -ALTER TABLE `payment_orders` MODIFY COLUMN `trade_type` varchar(32) NOT NULL COMMENT '交易类型(H5/JSAPI/ALIPAY_WAP/ALIPAY_APP等)'; - --- 更新transaction_id字段注释,支持多种支付方式 -ALTER TABLE `payment_orders` MODIFY COLUMN `transaction_id` varchar(64) DEFAULT NULL COMMENT '第三方支付订单号(微信/支付宝)'; - --- 添加支付方式字段 -ALTER TABLE `payment_orders` ADD COLUMN `payment_method` enum('wechat','alipay') DEFAULT 'wechat' COMMENT '支付方式' AFTER `trade_type`; - --- 添加支付宝特有字段 -ALTER TABLE `payment_orders` ADD COLUMN `buyer_user_id` varchar(32) DEFAULT NULL COMMENT '支付宝买家用户ID' AFTER `transaction_id`; -ALTER TABLE `payment_orders` ADD COLUMN `trade_status` varchar(32) DEFAULT NULL COMMENT '支付宝交易状态' AFTER `buyer_user_id`; - --- 添加索引 -ALTER TABLE `payment_orders` ADD KEY `idx_payment_method` (`payment_method`); -ALTER TABLE `payment_orders` ADD KEY `idx_trade_status` (`trade_status`); \ No newline at end of file diff --git a/migrations/create_payment_orders_table.sql b/migrations/create_payment_orders_table.sql deleted file mode 100644 index fb4ea53..0000000 --- a/migrations/create_payment_orders_table.sql +++ /dev/null @@ -1,27 +0,0 @@ --- 创建支付订单表 -CREATE TABLE IF NOT EXISTS `payment_orders` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单ID', - `user_id` int(11) NOT NULL COMMENT '用户ID', - `out_trade_no` varchar(64) NOT NULL COMMENT '商户订单号', - `transaction_id` varchar(64) DEFAULT NULL COMMENT '微信支付订单号', - `total_fee` int(11) NOT NULL COMMENT '订单金额(分)', - `body` varchar(128) NOT NULL COMMENT '商品描述', - `trade_type` varchar(16) NOT NULL COMMENT '交易类型', - `prepay_id` varchar(64) DEFAULT NULL COMMENT '预支付交易会话标识', - `mweb_url` text DEFAULT NULL COMMENT 'H5支付跳转链接', - `status` enum('pending','paid','failed','cancelled') NOT NULL DEFAULT 'pending' COMMENT '支付状态', - `paid_at` datetime DEFAULT NULL COMMENT '支付完成时间', - `created_at` datetime NOT NULL COMMENT '创建时间', - `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_out_trade_no` (`out_trade_no`), - KEY `idx_user_id` (`user_id`), - KEY `idx_status` (`status`), - KEY `idx_created_at` (`created_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表'; - --- 为用户表添加支付状态字段 -ALTER TABLE `users` ADD COLUMN `payment_status` enum('unpaid','paid') NOT NULL DEFAULT 'unpaid' COMMENT '支付状态' AFTER `status`; - --- 添加索引 -ALTER TABLE `users` ADD KEY `idx_payment_status` (`payment_status`); \ No newline at end of file diff --git a/routes/address-labels.js b/routes/address-labels.js deleted file mode 100644 index b304bce..0000000 --- a/routes/address-labels.js +++ /dev/null @@ -1,180 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../database'); -const { auth } = require('../middleware/auth'); - -// 获取地址标签列表(包含系统默认和用户自定义) -router.get('/', auth, async (req, res) => { - try { - const userId = req.user.id; - - const [labels] = await getDB().execute( - `SELECT * FROM address_labels - WHERE user_id IS NULL OR user_id = ? - ORDER BY is_system DESC, created_at ASC`, - [userId] - ); - - res.json({ - success: true, - data: labels - }); - } catch (error) { - console.error('获取地址标签列表错误:', error); - res.status(500).json({ message: '获取地址标签列表失败' }); - } -}); - -// 创建自定义地址标签 -router.post('/', auth, async (req, res) => { - try { - const userId = req.user.id; - const { name, color = '#1890ff' } = req.body; - - if (!name || name.trim() === '') { - return res.status(400).json({ message: '标签名称不能为空' }); - } - - // 检查标签名称是否已存在(系统标签或用户自定义标签) - const [existing] = await getDB().execute( - `SELECT id FROM address_labels - WHERE name = ? AND (user_id IS NULL OR user_id = ?)`, - [name.trim(), userId] - ); - - if (existing.length > 0) { - return res.status(400).json({ message: '标签名称已存在' }); - } - - const [result] = await getDB().execute( - `INSERT INTO address_labels (name, color, user_id, is_system, created_at, updated_at) - VALUES (?, ?, ?, false, NOW(), NOW())`, - [name.trim(), color, userId] - ); - - res.status(201).json({ - success: true, - message: '地址标签创建成功', - data: { labelId: result.insertId } - }); - } catch (error) { - console.error('创建地址标签错误:', error); - res.status(500).json({ message: '创建地址标签失败' }); - } -}); - -// 更新自定义地址标签 -router.put('/:id', auth, async (req, res) => { - try { - const labelId = req.params.id; - const userId = req.user.id; - const { name, color } = req.body; - - // 检查标签是否存在且属于当前用户(不能修改系统标签) - const [existing] = await getDB().execute( - 'SELECT id, is_system FROM address_labels WHERE id = ? AND user_id = ?', - [labelId, userId] - ); - - if (existing.length === 0) { - return res.status(404).json({ message: '地址标签不存在或无权限修改' }); - } - - if (existing[0].is_system) { - return res.status(403).json({ message: '系统标签不能修改' }); - } - - if (name && name.trim() !== '') { - // 检查新名称是否已存在 - const [nameExists] = await getDB().execute( - `SELECT id FROM address_labels - WHERE name = ? AND id != ? AND (user_id IS NULL OR user_id = ?)`, - [name.trim(), labelId, userId] - ); - - if (nameExists.length > 0) { - return res.status(400).json({ message: '标签名称已存在' }); - } - } - - const updateFields = []; - const updateValues = []; - - if (name && name.trim() !== '') { - updateFields.push('name = ?'); - updateValues.push(name.trim()); - } - - if (color) { - updateFields.push('color = ?'); - updateValues.push(color); - } - - if (updateFields.length === 0) { - return res.status(400).json({ message: '没有需要更新的字段' }); - } - - updateFields.push('updated_at = NOW()'); - updateValues.push(labelId, userId); - - await getDB().execute( - `UPDATE address_labels SET ${updateFields.join(', ')} WHERE id = ? AND user_id = ?`, - updateValues - ); - - res.json({ - success: true, - message: '地址标签更新成功' - }); - } catch (error) { - console.error('更新地址标签错误:', error); - res.status(500).json({ message: '更新地址标签失败' }); - } -}); - -// 删除自定义地址标签 -router.delete('/:id', auth, async (req, res) => { - try { - const labelId = req.params.id; - const userId = req.user.id; - - // 检查标签是否存在且属于当前用户(不能删除系统标签) - const [existing] = await getDB().execute( - 'SELECT id, is_system FROM address_labels WHERE id = ? AND user_id = ?', - [labelId, userId] - ); - - if (existing.length === 0) { - return res.status(404).json({ message: '地址标签不存在或无权限删除' }); - } - - if (existing[0].is_system) { - return res.status(403).json({ message: '系统标签不能删除' }); - } - - // 检查是否有地址正在使用该标签 - const [addressesUsingLabel] = await getDB().execute( - 'SELECT COUNT(*) as count FROM user_addresses WHERE label_id = ? AND deleted_at IS NULL', - [labelId] - ); - - if (addressesUsingLabel[0].count > 0) { - return res.status(400).json({ message: '该标签正在被使用,无法删除' }); - } - - await getDB().execute( - 'DELETE FROM address_labels WHERE id = ? AND user_id = ?', - [labelId, userId] - ); - - res.json({ - success: true, - message: '地址标签删除成功' - }); - } catch (error) { - console.error('删除地址标签错误:', error); - res.status(500).json({ message: '删除地址标签失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/addresses.js b/routes/addresses.js deleted file mode 100644 index 101bf80..0000000 --- a/routes/addresses.js +++ /dev/null @@ -1,567 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../database'); -const { auth } = require('../middleware/auth'); - -/** - * @swagger - * components: - * schemas: - * Address: - * type: object - * properties: - * id: - * type: integer - * description: 地址ID - * user_id: - * type: integer - * description: 用户ID - * recipient_name: - * type: string - * description: 收件人姓名 - * phone: - * type: string - * description: 联系电话 - * province_code: - * type: string - * description: 省份编码 - * province_name: - * type: string - * description: 省份名称 - * city_code: - * type: string - * description: 城市编码 - * city_name: - * type: string - * description: 城市名称 - * district_code: - * type: string - * description: 区县编码 - * district_name: - * type: string - * description: 区县名称 - * detailed_address: - * type: string - * description: 详细地址 - * postal_code: - * type: string - * description: 邮政编码 - * label_id: - * type: integer - * description: 地址标签ID - * is_default: - * type: boolean - * description: 是否为默认地址 - * label_name: - * type: string - * description: 标签名称 - * label_color: - * type: string - * description: 标签颜色 - * required: - * - recipient_name - * - phone - * - province_code - * - province_name - * - city_code - * - city_name - * - district_code - * - district_name - * - detailed_address - */ - -/** - * @swagger - * /addresses: - * get: - * summary: 获取用户收货地址列表 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取地址列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/Address' - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.get('/', auth, async (req, res) => { - try { - const userId = req.user.id; - - const [addresses] = await getDB().execute( - `SELECT ua.*, al.name as label_name, al.color as label_color, - p.name as province_name, c.name as city_name, d.name as district_name - FROM user_addresses ua - LEFT JOIN address_labels al ON ua.label = al.id - LEFT JOIN china_regions p ON ua.province = p.code - LEFT JOIN china_regions c ON ua.city = c.code - LEFT JOIN china_regions d ON ua.district = d.code - WHERE ua.user_id = ? - ORDER BY ua.is_default DESC, ua.created_at DESC`, - [userId] - ); - - res.json({ - success: true, - data: addresses - }); - } catch (error) { - console.error('获取收货地址列表错误:', error); - res.status(500).json({ message: '获取收货地址列表失败' }); - } -}); - -/** - * @swagger - * /addresses/{id}: - * get: - * summary: 获取单个收货地址详情 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 地址ID - * responses: - * 200: - * description: 成功获取地址详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * $ref: '#/components/schemas/Address' - * 401: - * description: 未授权 - * 404: - * description: 地址不存在 - * 500: - * description: 服务器错误 - */ -router.get('/:id', auth, async (req, res) => { - try { - const addressId = req.params.id; - const userId = req.user.id; - - const [addresses] = await getDB().execute( - `SELECT ua.*, al.name as label_name, al.color as label_color - FROM user_addresses ua - LEFT JOIN address_labels al ON ua.label_id = al.id - WHERE ua.id = ? AND ua.user_id = ? AND ua.deleted_at IS NULL`, - [addressId, userId] - ); - - if (addresses.length === 0) { - return res.status(404).json({ message: '收货地址不存在' }); - } - - res.json({ - success: true, - data: addresses[0] - }); - } catch (error) { - console.error('获取收货地址详情错误:', error); - res.status(500).json({ message: '获取收货地址详情失败' }); - } -}); - -/** - * @swagger - * /addresses: - * post: - * summary: 创建收货地址 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * recipient_name: - * type: string - * description: 收件人姓名 - * phone: - * type: string - * description: 联系电话 - * province_code: - * type: string - * description: 省份编码 - * province_name: - * type: string - * description: 省份名称 - * city_code: - * type: string - * description: 城市编码 - * city_name: - * type: string - * description: 城市名称 - * district_code: - * type: string - * description: 区县编码 - * district_name: - * type: string - * description: 区县名称 - * detailed_address: - * type: string - * description: 详细地址 - * postal_code: - * type: string - * description: 邮政编码 - * label_id: - * type: integer - * description: 地址标签ID - * is_default: - * type: boolean - * description: 是否为默认地址 - * required: - * - recipient_name - * - phone - * - province_code - * - province_name - * - city_code - * - city_name - * - district_code - * - district_name - * - detailed_address - * responses: - * 201: - * description: 地址创建成功 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.post('/', auth, async (req, res) => { - try { - const userId = req.user.id; - const { - recipient_name, - phone, - province_code, - city_code, - district_code, - detailed_address, - is_default = false - } = req.body; - - // 验证必填字段 - if (!recipient_name || !phone || !province_code || !city_code || !district_code || !detailed_address) { - return res.status(400).json({ message: '收件人姓名、电话、省市区和详细地址不能为空' }); - } - - // 如果设置为默认地址,先取消其他默认地址 - if (is_default) { - await getDB().execute( - 'UPDATE user_addresses SET is_default = false WHERE user_id = ? ', - [userId] - ); - } - - const [result] = await getDB().execute( - `INSERT INTO user_addresses ( - user_id, receiver_name, receiver_phone, province, city, - district, detailed_address, is_default, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, - [ - userId, recipient_name, phone, province_code, city_code, - district_code, detailed_address, is_default - ] - ); - - res.status(201).json({ - success: true, - message: '收货地址创建成功', - data: { addressId: result.insertId } - }); - } catch (error) { - console.error('创建收货地址错误:', error); - res.status(500).json({ message: '创建收货地址失败' }); - } -}); - -/** - * @swagger - * /addresses/{id}: - * put: - * summary: 更新收货地址 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 地址ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * recipient_name: - * type: string - * description: 收件人姓名 - * phone: - * type: string - * description: 联系电话 - * province_code: - * type: string - * description: 省份编码 - * province_name: - * type: string - * description: 省份名称 - * city_code: - * type: string - * description: 城市编码 - * city_name: - * type: string - * description: 城市名称 - * district_code: - * type: string - * description: 区县编码 - * district_name: - * type: string - * description: 区县名称 - * detailed_address: - * type: string - * description: 详细地址 - * postal_code: - * type: string - * description: 邮政编码 - * label_id: - * type: integer - * description: 地址标签ID - * is_default: - * type: boolean - * description: 是否为默认地址 - * responses: - * 200: - * description: 地址更新成功 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 404: - * description: 地址不存在 - * 500: - * description: 服务器错误 - */ -router.put('/:id', auth, async (req, res) => { - try { - const addressId = req.params.id; - const userId = req.user.id; - const { - recipient_name, - phone, - province_code, - city_code, - district_code, - detailed_address, - is_default - } = req.body; - if (!recipient_name || !phone || !province_code || !city_code || !district_code || !detailed_address) { - return res.status(400).json({ message: '收件人姓名、电话、省市区和详细地址不能为空' }); - } - - // 检查地址是否存在且属于当前用户 - const [existing] = await getDB().execute( - 'SELECT id FROM user_addresses WHERE id = ? AND user_id = ? ', - [addressId, userId] - ); - - if (existing.length === 0) { - return res.status(404).json({ message: '收货地址不存在' }); - } - - // 如果设置为默认地址,先取消其他默认地址 - if (is_default) { - await getDB().execute( - 'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND id != ? ', - [userId, addressId] - ); - } - - const [result] = await getDB().execute( - `UPDATE user_addresses SET - receiver_name = ?, receiver_phone = ?, province = ?, city = ?, - district = ?, detailed_address = ?, is_default = ?, updated_at = NOW() - WHERE id = ? AND user_id = ?`, - [ - recipient_name, phone, province_code, city_code, - district_code, detailed_address, is_default, - addressId, userId - ] - ); - - res.json({ - success: true, - message: '收货地址更新成功' - }); - } catch (error) { - console.error('更新收货地址错误:', error); - res.status(500).json({ message: '更新收货地址失败' }); - } -}); - -/** - * @swagger - * /addresses/{id}: - * delete: - * summary: 删除收货地址 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 地址ID - * responses: - * 200: - * description: 地址删除成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 收货地址删除成功 - * 401: - * description: 未授权 - * 404: - * description: 地址不存在 - * 500: - * description: 服务器错误 - */ -router.delete('/:id', auth, async (req, res) => { - try { - const addressId = req.params.id; - const userId = req.user.id; - - const [result] = await getDB().execute( - 'DELETE FROM user_addresses WHERE id = ? AND user_id = ?', - [addressId, userId] - ); - - if (result.affectedRows === 0) { - return res.status(404).json({ message: '收货地址不存在' }); - } - - res.json({ - success: true, - message: '收货地址删除成功' - }); - } catch (error) { - console.error('删除收货地址错误:', error); - res.status(500).json({ message: '删除收货地址失败' }); - } -}); - -/** - * @swagger - * /addresses/{id}/default: - * put: - * summary: 设置默认地址 - * tags: [Addresses] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 地址ID - * responses: - * 200: - * description: 默认地址设置成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 默认地址设置成功 - * 401: - * description: 未授权 - * 404: - * description: 地址不存在 - * 500: - * description: 服务器错误 - */ -router.put('/:id/default', auth, async (req, res) => { - try { - const addressId = req.params.id; - const userId = req.user.id; - - // 检查地址是否存在且属于当前用户 - const [existing] = await getDB().execute( - 'SELECT id FROM user_addresses WHERE id = ? AND user_id = ? AND deleted_at IS NULL', - [addressId, userId] - ); - - if (existing.length === 0) { - return res.status(404).json({ message: '收货地址不存在' }); - } - - // 取消其他默认地址 - await getDB().execute( - 'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND deleted_at IS NULL', - [userId] - ); - - // 设置当前地址为默认 - await getDB().execute( - 'UPDATE user_addresses SET is_default = true WHERE id = ? AND user_id = ?', - [addressId, userId] - ); - - res.json({ - success: true, - message: '默认地址设置成功' - }); - } catch (error) { - console.error('设置默认地址错误:', error); - res.status(500).json({ message: '设置默认地址失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/agent-withdrawals.js b/routes/agent-withdrawals.js deleted file mode 100644 index 7b4cd34..0000000 --- a/routes/agent-withdrawals.js +++ /dev/null @@ -1,475 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../database'); -const { auth } = require('../middleware/auth'); -const multer = require('multer'); -const path = require('path'); -const fs = require('fs'); - -// 配置multer用于文件上传 -const storage = multer.diskStorage({ - destination: function (req, file, cb) { - const uploadDir = 'uploads/qr-codes'; - // 确保上传目录存在 - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); - } - cb(null, uploadDir); - }, - filename: function (req, file, cb) { - // 生成唯一文件名 - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, 'qr-code-' + uniqueSuffix + path.extname(file.originalname)); - } -}); - -// 文件过滤器 -const fileFilter = (req, file, cb) => { - // 只允许图片文件 - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('只允许上传图片文件'), false); - } -}; - -const upload = multer({ - storage: storage, - fileFilter: fileFilter, - limits: { - fileSize: 5 * 1024 * 1024 // 限制文件大小为5MB - } -}); - -// 获取数据库连接 -const db = { - query: async (sql, params = []) => { - const connection = getDB(); - const [rows] = await connection.execute(sql, params); - return rows; - } -}; - -/** - * 检查用户是否为代理商 - */ -const requireAgent = async (req, res, next) => { - try { - const userId = req.user.id; - - // 查询用户是否为代理商 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE user_id = ? AND status = "active"', - [userId] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(403).json({ success: false, message: '您不是活跃的代理商' }); - } - - req.agent = agentResult[0]; - next(); - } catch (error) { - console.error('检查代理商身份失败:', error); - res.status(500).json({ success: false, message: '检查代理商身份失败' }); - } -}; - -/** - * 获取代理商佣金统计信息 - */ -router.get('/stats', auth, requireAgent, async (req, res) => { - try { - const agentId = req.agent.id; - - // 查询佣金统计 - const statsQuery = ` - SELECT - CAST(COALESCE(commission_sum.total_commission, 0) AS DECIMAL(10,2)) as total_commission, - CAST(COALESCE(ra.withdrawn_amount, 0) AS DECIMAL(10,2)) as withdrawn_amount, - CAST(COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as pending_withdrawal, - CAST(COALESCE(commission_sum.total_commission, 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount - FROM regional_agents ra - LEFT JOIN ( - SELECT agent_id, SUM(commission_amount) as total_commission - FROM agent_commission_records - WHERE agent_id = ? - GROUP BY agent_id - ) commission_sum ON ra.id = commission_sum.agent_id - WHERE ra.id = ? - `; - - const statsResult = await db.query(statsQuery, [agentId, agentId]); - const stats = statsResult && statsResult.length > 0 ? statsResult[0] : { - total_commission: 0, - withdrawn_amount: 0, - pending_withdrawal: 0, - available_amount: 0 - }; - - // 查询代理商信息包括收款方式 - const agentInfo = await db.query( - 'SELECT payment_type, bank_name, account_number, account_holder, qr_code_url, bank_account FROM regional_agents WHERE id = ?', - [agentId] - ); - - const agent = agentInfo[0] || {}; - - // 构建收款方式信息,兼容旧数据 - const paymentInfo = { - payment_type: agent.payment_type || 'bank', - bank_name: agent.bank_name || '', - account_number: agent.account_number || agent.bank_account, // 兼容旧字段 - account_holder: agent.account_holder, - qr_code_url: agent.qr_code_url || '' - }; - - // 兼容旧的bankInfo字段 - const bankInfo = { - bank_name: agent.bank_name || '', - bank_account: agent.bank_account || agent.account_number, - account_holder: agent.account_holder - }; - - res.json({ - success: true, - data: { - ...stats, - paymentInfo: paymentInfo, - bank_info: bankInfo // 保持向后兼容 - } - }); - } catch (error) { - console.error('获取佣金统计失败:', error); - res.status(500).json({ success: false, message: '获取佣金统计失败' }); - } -}); - -/** - * 更新收款方式信息 - */ -router.put('/payment-info', auth, requireAgent, async (req, res) => { - try { - const { payment_type, bank_name, account_number, account_holder, qr_code_url } = req.body; - const agentId = req.agent.id; - - // 验证收款方式类型 - const validPaymentTypes = ['bank', 'wechat', 'alipay', 'unionpay']; - if (!validPaymentTypes.includes(payment_type)) { - return res.status(400).json({ success: false, message: '收款方式类型不正确' }); - } - - // 根据收款方式类型进行不同的验证 - if (payment_type === 'bank') { - // 银行卡验证 - if (!bank_name || !account_number || !account_holder) { - return res.status(400).json({ success: false, message: '银行信息不完整' }); - } - // 验证银行账号格式(简单验证) - if (!/^\d{10,25}$/.test(account_number.replace(/\s/g, ''))) { - return res.status(400).json({ success: false, message: '银行账号格式不正确' }); - } - } else { - // 收款码验证 - if (!account_holder || !qr_code_url) { - return res.status(400).json({ success: false, message: '收款码信息不完整' }); - } - } - - // 更新收款方式信息 - await db.query( - 'UPDATE regional_agents SET payment_type = ?, bank_name = ?, account_number = ?, account_holder = ?, qr_code_url = ? WHERE id = ?', - [payment_type, bank_name, account_number, account_holder, qr_code_url, agentId] - ); - - res.json({ - success: true, - message: '收款方式信息更新成功' - }); - } catch (error) { - console.error('更新收款方式信息失败:', error); - res.status(500).json({ success: false, message: '更新收款方式信息失败' }); - } -}); - -/** - * 上传收款码图片 - */ -router.post('/upload-qr-code', auth, requireAgent, upload.single('qrCode'), async (req, res) => { - try { - if (!req.file) { - return res.status(400).json({ success: false, message: '请选择要上传的图片' }); - } - - // 构建文件访问URL - const fileUrl = `/uploads/qr-codes/${req.file.filename}`; - - res.json({ - success: true, - message: '收款码上传成功', - data: { - url: fileUrl, - filename: req.file.filename - } - }); - } catch (error) { - console.error('上传收款码失败:', error); - res.status(500).json({ success: false, message: '上传收款码失败' }); - } -}); - -/** - * 兼容旧的银行信息接口 - */ -router.put('/bank-info', auth, requireAgent, async (req, res) => { - try { - const agentId = req.agent.id; - const { bank_name, bank_account, account_holder } = req.body; - - // 验证必填字段 - if (!bank_name || !bank_account || !account_holder) { - return res.status(400).json({ success: false, message: '银行信息不完整' }); - } - - // 验证银行账号格式(简单验证) - if (!/^\d{10,25}$/.test(bank_account)) { - return res.status(400).json({ success: false, message: '银行账号格式不正确' }); - } - - // 更新银行信息 - await db.query( - 'UPDATE regional_agents SET payment_type = "bank", bank_name = ?, account_number = ?, account_holder = ?, bank_account = ? WHERE id = ?', - [bank_name, bank_account, account_holder, bank_account, agentId] - ); - - res.json({ - success: true, - message: '银行信息更新成功' - }); - } catch (error) { - console.error('更新银行信息失败:', error); - res.status(500).json({ success: false, message: '更新银行信息失败' }); - } -}); - -/** - * 申请提现 - */ -router.post('/apply', auth, requireAgent, async (req, res) => { - try { - const agentId = req.agent.id; - const { amount, apply_note } = req.body; - - // 验证提现金额 - if (!amount || amount <= 0) { - return res.status(400).json({ success: false, message: '提现金额必须大于0' }); - } - - if (amount < 10) { - return res.status(400).json({ success: false, message: '最低提现金额为100元' }); - } - - // 查询代理商信息和可提现金额 - const agentQuery = ` - SELECT - ra.*, - CAST(COALESCE(SUM(acr.commission_amount), 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount - FROM regional_agents ra - LEFT JOIN agent_commission_records acr ON ra.id = acr.agent_id - WHERE ra.id = ? - GROUP BY ra.id - `; - - const agentResult = await db.query(agentQuery, [agentId]); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理商信息不存在' }); - } - - const agent = agentResult[0]; - - // 检查收款方式信息是否完整 - const paymentType = agent.payment_type || 'bank'; - - if (paymentType === 'bank') { - // 银行卡收款方式验证 - if (!agent.bank_name || !agent.account_number || !agent.account_holder) { - return res.status(400).json({ success: false, message: '请先完善银行信息' }); - } - } else { - // 收款码收款方式验证 - if (!agent.account_holder || !agent.qr_code_url) { - return res.status(400).json({ success: false, message: '请先完善收款码信息' }); - } - } - - // 检查可提现金额 - if (amount > agent.available_amount) { - return res.status(400).json({ - success: false, - message: `可提现金额不足,当前可提现:¥${agent.available_amount}` - }); - } - - // 开始事务 - const pool = getDB(); - const connection = await pool.getConnection(); - await connection.beginTransaction(); - - try { - // 创建提现申请 - await connection.execute( - 'INSERT INTO agent_withdrawals (agent_id, amount, payment_type, bank_name, account_number, account_holder, qr_code_url, apply_note, bank_account) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', - [agentId, amount, paymentType, agent.bank_name || '', agent.account_number, agent.account_holder, agent.qr_code_url, apply_note || null, agent.account_number] - ); - - // 更新代理的待提现金额 - await connection.execute( - 'UPDATE regional_agents SET pending_withdrawal = pending_withdrawal + ? WHERE id = ?', - [amount, agentId] - ); - - await connection.commit(); - connection.release(); // 释放连接回连接池 - - res.json({ - success: true, - message: '提现申请提交成功,请等待审核', - data: { - paymentType: paymentType - } - }); - } catch (error) { - await connection.rollback(); - connection.release(); // 释放连接回连接池 - throw error; - } - } catch (error) { - console.error('申请提现失败:', error); - res.status(500).json({ success: false, message: '申请提现失败' }); - } -}); - -/** - * 获取提现记录 - */ -router.get('/records', auth, requireAgent, async (req, res) => { - try { - const agentId = req.agent.id; - const { page = 1, limit = 20, status } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 构建查询条件 - let whereConditions = ['agent_id = ?']; - let queryParams = [agentId]; - - if (status) { - whereConditions.push('status = ?'); - queryParams.push(status); - } - - const whereClause = whereConditions.join(' AND '); - - // 查询提现记录 - const recordsQuery = ` - SELECT - id, - amount, - payment_type, - bank_name, - account_number, - account_holder, - qr_code_url, - status, - apply_note, - admin_note, - created_at, - processed_at, - bank_account - FROM agent_withdrawals - WHERE ${whereClause} - ORDER BY created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - - const records = await db.query(recordsQuery, queryParams); - - // 处理记录数据,兼容旧格式 - const processedRecords = records.map(record => ({ - ...record, - payment_type: record.payment_type || 'bank', - account_number: record.account_number || record.bank_account, - qr_code_url: record.qr_code_url || '', - // 保持向后兼容 - bank_account: record.bank_account || record.account_number - })); - - // 查询总数 - const totalResult = await db.query( - `SELECT COUNT(*) as total FROM agent_withdrawals WHERE ${whereClause}`, - queryParams - ); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - res.json({ - success: true, - data: { - records: processedRecords, - total: parseInt(total) - } - }); - } catch (error) { - console.error('获取提现记录失败:', error); - res.status(500).json({ success: false, message: '获取提现记录失败' }); - } -}); - -/** - * 获取佣金明细 - */ -router.get('/commissions', auth, requireAgent, async (req, res) => { - try { - const agentId = req.agent.id; - const { page = 1, limit = 20 } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 查询佣金记录 - const commissionsQuery = ` - SELECT - acr.*, - u.real_name as merchant_name, - CONCAT(SUBSTRING(u.phone, 1, 3), '****', SUBSTRING(u.phone, -4)) as merchant_phone_masked - FROM agent_commission_records acr - JOIN users u ON acr.merchant_id = u.id - WHERE acr.agent_id = ? - ORDER BY acr.created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - - const commissions = await db.query(commissionsQuery, [agentId]); - - // 查询总数 - const totalResult = await db.query( - 'SELECT COUNT(*) as total FROM agent_commission_records WHERE agent_id = ?', - [agentId] - ); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - res.json({ - success: true, - data: { - commissions, - total: parseInt(total) - } - }); - } catch (error) { - console.error('获取佣金明细失败:', error); - res.status(500).json({ success: false, message: '获取佣金明细失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/agents.js b/routes/agents.js deleted file mode 100644 index 76147c7..0000000 --- a/routes/agents.js +++ /dev/null @@ -1,777 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const {getDB} = require('../database'); -const QRCode = require('qrcode'); -const crypto = require('crypto'); -const bcrypt = require('bcryptjs'); -const {auth} = require('../middleware/auth'); -const dayjs = require('dayjs'); - -// 获取浙江省所有区域列表 -router.get('/regions', async (req, res) => { - try { - const [regions] = await getDB().execute( - 'SELECT * FROM zhejiang_regions ORDER BY city_name, district_name' - ); - res.json({success: true, data: regions}); - } catch (error) { - console.error('获取区域列表失败:', error); - res.status(500).json({success: false, message: '获取区域列表失败'}); - } -}); - -// 申请成为区域代理 -router.post('/apply', async (req, res) => { - try { - const {region_id, real_name, phone, id_card, contact_address} = req.body; - - if (!region_id || !real_name || !phone || !id_card) { - return res.status(400).json({success: false, message: '请填写完整信息'}); - } - - // 检查该区域是否已有代理(包括所有状态,不仅仅是active) - const [existingRegionAgent] = await getDB().execute( - 'SELECT id, status FROM regional_agents WHERE region_id = ? AND status IN ("pending", "active")', - [region_id] - ); - - if (existingRegionAgent.length > 0) { - const status = existingRegionAgent[0].status; - if (status === 'active') { - return res.status(400).json({ - success: false, - message: '该区域已有激活的代理,每个区域只能有一个代理账号' - }); - } else if (status === 'pending') { - return res.status(400).json({ - success: false, - message: '该区域已有待审核的代理申请,每个区域只能有一个代理账号' - }); - } - } - - // 检查手机号是否已存在用户 - const [existingUser] = await getDB().execute( - 'SELECT id FROM users WHERE phone = ?', - [phone] - ); - - let userId; - if (existingUser.length > 0) { - userId = existingUser[0].id; - - // 检查该用户是否已申请过代理(包括所有状态) - const [existingUserAgent] = await getDB().execute( - 'SELECT id, status, region_id FROM regional_agents WHERE user_id = ?', - [userId] - ); - - if (existingUserAgent.length > 0) { - const agentStatus = existingUserAgent[0].status; - if (agentStatus === 'active') { - return res.status(400).json({ - success: false, - message: '该用户已是其他区域的激活代理,一个用户只能申请一个区域的代理' - }); - } else if (agentStatus === 'pending') { - return res.status(400).json({ - success: false, - message: '该用户已有待审核的代理申请,一个用户只能申请一个区域的代理' - }); - } else if (agentStatus === 'suspended' || agentStatus === 'terminated') { - return res.status(400).json({ - success: false, - message: '该用户的代理资格已被暂停或终止,无法重新申请' - }); - } - } - } else { - // 创建新用户(为代理申请用户生成临时密码) - const bcrypt = require('bcryptjs'); - const tempPassword = Math.random().toString(36).slice(-8); // 生成8位临时密码 - const hashedPassword = await bcrypt.hash(tempPassword, 10); - - const [userResult] = await getDB().execute( - 'INSERT INTO users (username, password, phone, real_name, id_card, created_at) VALUES (?, ?, ?, ?, ?, NOW())', - [phone, hashedPassword, phone, real_name, id_card] - ); - userId = userResult.insertId; - } - - // 生成代理编码 - const agentCode = 'AG' + Date.now().toString().slice(-8); - - // 创建代理申请 - await getDB().execute( - 'INSERT INTO regional_agents (user_id, region_id, agent_code, status, created_at) VALUES (?, ?, ?, "pending", NOW())', - [userId, region_id, agentCode] - ); - - res.json({success: true, message: '申请提交成功,请等待审核'}); - } catch (error) { - console.error('申请代理失败:', error); - res.status(500).json({success: false, message: '申请失败'}); - } -}); - -// 代理登录 -router.post('/login', async (req, res) => { - try { - const {phone, password} = req.body; - - if (!phone || !password) { - return res.status(400).json({success: false, message: '请输入手机号和密码'}); - } - - // 先查询用户和代理信息(包含密码用于验证) - const [agents] = await getDB().execute( - `SELECT ra.*, - u.id as user_id, - u.username, - u.phone, - u.real_name, - u.password, - u.role, - zr.name as city_name, - d.name as district_name - FROM regional_agents ra - JOIN users u ON ra.user_id = u.id - JOIN china_regions d ON d.code = u.district_id - JOIN china_regions zr ON ra.region_id = zr.code - WHERE u.phone = ? - AND ra.status = "active"`, - [phone] - ); - - if (agents.length === 0) { - return res.status(401).json({success: false, message: '手机号或密码错误,或账户未激活'}); - } - - const agent = agents[0]; - - // 验证密码 - const isPasswordValid = await bcrypt.compare(password, agent.password); - if (!isPasswordValid) { - return res.status(401).json({success: false, message: '手机号或密码错误,或账户未激活'}); - } - - // 生成JWT token - const jwt = require('jsonwebtoken'); - const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; - const token = jwt.sign( - { - userId: agent.user_id, - username: agent.username || agent.phone, - role: agent.role || 'agent', - agentId: agent.id - }, - JWT_SECRET, - {expiresIn: '24h'} - ); - - delete agent.password; // 不返回密码 - - res.json({ - success: true, - data: { - ...agent, - token - }, - message: '登录成功' - }); - } catch (error) { - console.error('代理登录失败:', error); - res.status(500).json({success: false, message: '登录失败'}); - } -}); - -// 获取代理的商户列表(包含所有商户,标注早期商户状态) -router.get('/merchants/:agent_id',auth, async (req, res) => { - try { - const {agent_id} = req.params; - const {page = 1, limit = 10} = req.query; - const offset = (page - 1) * limit; - - // 首先获取代理的注册时间 - const [agentInfo] = await getDB().execute( - `SELECT ra.created_at as agent_created_at, ra.region_id, ra.user_id - FROM regional_agents ra - WHERE ra.id = ?`, - [parseInt(agent_id)] - ); - const regionId = agentInfo[0].region_id; - - const userId = agentInfo[0].user_id; - - if (!agentInfo || agentInfo.length === 0) { - return res.status(404).json({success: false, message: '代理不存在'}); - } - - const agentCreatedAt = agentInfo[0].agent_created_at; - - // 获取商户列表(包含所有商户,包括agent_merchants表中的和符合条件的早期商户) - const [merchants] = await getDB().execute( - `SELECT u.id, - u.username, - u.phone, - u.real_name, - u.created_at, - u.audit_status, - IFNULL(u.created_at, '未关联') as joined_at, - CASE - WHEN u.created_at < ? AND u.district_id = ? THEN 1 - ELSE 0 - END as is_early_merchant, - CASE - WHEN u.created_at < ? AND u.district_id = ? THEN '早期商户(不记录佣金)' - ELSE '正常商户' - END as merchant_status, - (SELECT COUNT(*) - FROM matching_orders - WHERE initiator_id = u.id - AND status = 'completed') as completed_matches - - FROM users u - WHERE (u.inviter = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user')) - ORDER BY u.created_at DESC - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, - [agentCreatedAt, parseInt(regionId), agentCreatedAt, parseInt(regionId), parseInt(userId), agentCreatedAt, parseInt(regionId)] - ); - - // 获取总数(包括代理关联的商户和符合条件的早期商户) - const [countResult] = await getDB().execute( - `SELECT COUNT(*) as total - FROM users u - WHERE (u.inviter = ? OR (u.created_at < ? AND u.district_id = ?))`, - [parseInt(userId), agentCreatedAt, parseInt(regionId)] - ); - - // 获取早期商户统计(从user表获取所有符合条件的早期商户) - // 早期商户的判断条件:1.早期商户注册时间比代理要早。2.代理商代理的区县与商户的区县一致 - const [earlyMerchantStats] = await getDB().execute( - `SELECT COUNT(*) as early_merchant_count - FROM users u - WHERE u.created_at < ? - AND u.district_id = ? - AND u.role = 'user'`, - [agentCreatedAt, parseInt(regionId)] - ); - - // 获取正常商户统计(包括代理关联的商户,排除符合条件的早期商户) - const [normalMerchantStats] = await getDB().execute( - `SELECT COUNT(*) as normal_merchant_count - FROM users u - WHERE (u.inviter = ? AND (u.created_at >= ? OR u.district_id != ?))`, - [parseInt(userId), agentCreatedAt, parseInt(regionId)] - ); - - res.json({ - success: true, - data: { - merchants, - total: parseInt(countResult[0].total), - page: parseInt(page), - limit: parseInt(limit), - stats: { - total_merchants: parseInt(countResult[0].total), - early_merchants: parseInt(earlyMerchantStats[0].early_merchant_count), - normal_merchants: parseInt(normalMerchantStats[0].normal_merchant_count) - } - } - }); - } catch (error) { - console.error('获取商户列表失败:', error); - res.status(500).json({success: false, message: '获取商户列表失败'}); - } -}); - -// 获取代理的佣金记录 -router.get('/commissions/:agent_id',auth, async (req, res) => { - try { - const {agent_id} = req.params; - const {id} = req.user - const {page = 1, limit = 10} = req.query; - const offset = (page - 1) * limit; - - // 获取佣金记录 - const [commissions] = await getDB().execute( - `SELECT acr.*, u.username, u.real_name - FROM transfers acr - JOIN users u ON acr.from_user_id = u.id - WHERE acr.to_user_id = ${parseInt(id)} AND source_type='agent' - ORDER BY acr.created_at DESC - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}` - ); - - // 获取总数和总佣金 - const [summary] = await getDB().execute( - `SELECT COUNT(*) as total_records, - COALESCE(SUM(amount), 0) as total_commission - FROM transfers - WHERE source_type = 'agent' AND to_user_id=${id}` - ); - - // 由于agent_commission_records表没有status字段,设置默认值 - summary[0].paid_commission = summary[0].total_commission; - summary[0].pending_commission = 0; - - res.json({ - success: true, - data: { - commissions, - summary: summary[0], - page: parseInt(page), - limit: parseInt(limit) - } - }); - } catch (error) { - console.error('获取佣金记录失败:', error); - res.status(500).json({success: false, message: '获取佣金记录失败'}); - } -}); - -// 获取代理统计信息 -router.get('/stats/:agent_id', auth,async (req, res) => { - try { - const {agent_id} = req.params; - - // 获取统计数据 - const [stats] = await getDB().execute( - `SELECT (SELECT COUNT(*) FROM agent_merchants WHERE agent_id = ${parseInt(agent_id)}) as total_merchants, - (SELECT COUNT(*) - FROM agent_merchants am - JOIN users u ON am.merchant_id = u.id - WHERE am.agent_id = ${parseInt(agent_id)} - AND u.audit_status = 'approved') as approved_merchants, - (SELECT COALESCE(SUM(commission_amount), 0) - FROM agent_commission_records - WHERE agent_id = ${parseInt(agent_id)}) as total_commission, - (SELECT COALESCE(SUM(commission_amount), 0) - FROM agent_commission_records - WHERE agent_id = ${parseInt(agent_id)}) as paid_commission, - (SELECT COUNT(*) - FROM registration_codes rc - JOIN regional_agents ra ON rc.agent_id = ra.user_id - WHERE ra.id = ${parseInt(agent_id)} - AND rc.is_used = 1) as used_codes, - (SELECT COUNT(*) - FROM registration_codes rc - JOIN regional_agents ra ON rc.agent_id = ra.user_id - WHERE ra.id = ${parseInt(agent_id)} - AND rc.is_used = 0 - AND rc.expires_at > NOW()) as active_codes` - ); - - res.json({success: true, data: stats[0]}); - } catch (error) { - console.error('获取统计信息失败:', error); - res.status(500).json({success: false, message: '获取统计信息失败'}); - } -}); - -// 获取代理列表 -router.get('/list', auth,async (req, res) => { - try { - const {page = 1, limit = 10, status, region_id} = req.query; - const offset = (page - 1) * limit; - - let whereClause = '1=1'; - let params = []; - - if (status) { - whereClause += ' AND ra.status = ?'; - params.push(status); - } - - if (region_id) { - whereClause += ' AND ra.region_id = ?'; - params.push(region_id); - } - - // 获取代理列表 - const [agents] = await getDB().execute( - `SELECT ra.*, - u.username, - u.phone, - u.real_name, - u.created_at as user_created_at, - zr.city_name, - zr.district_name, - zr.region_code - FROM regional_agents ra - JOIN users u ON ra.user_id = u.id - JOIN zhejiang_regions zr ON ra.region_id = zr.id - WHERE ${whereClause} - ORDER BY ra.created_at DESC - LIMIT ${limit} OFFSET ${offset}` - ); - - // 获取总数 - const [countResult] = await getDB().execute( - `SELECT COUNT(*) as total - FROM regional_agents ra - JOIN users u ON ra.user_id = u.id - JOIN zhejiang_regions zr ON ra.region_id = zr.id - WHERE ${whereClause}` - ); - - const total = countResult[0].total; - const totalPages = Math.ceil(total / limit); - - res.json({ - success: true, - data: { - agents, - pagination: { - page: parseInt(page), - limit: parseInt(limit), - total, - totalPages - } - } - }); - } catch (error) { - console.error('获取代理列表失败:', error); - res.status(500).json({success: false, message: '获取代理列表失败'}); - } -}); - -/** - * 获取代理佣金趋势数据 - * @route GET /agents/commission-trend/:agent_id - * @param {string} agent_id - 代理ID - * @param {string} period - 时间周期 (7d, 30d, 3m) - * @returns {Object} 佣金趋势数据 - */ -router.get('/commission-trend/:agent_id',auth, async (req, res) => { - try { - console.log(req.params, 'req.params') - const {agent_id} = req.params; - let [agentUserInfo] = await getDB().execute(` - SELECT u.* - FROM regional_agents as rg - LEFT JOIN users u ON rg.user_id = u.id - WHERE rg.id = ${agent_id} - `) - let userId = agentUserInfo[0].id - console.log(userId, 'userId') - const {period = '7d'} = req.query; - let days; - switch (period) { - case '7d': - days = 7; - break; - case '30d': - days = 30; - break; - case '3m': - days = 90; - break; - default: - days = 7; - } - - console.log(period, 'period') - - const [trendData] = await getDB().execute(` - SELECT DATE(created_at) as date, - CAST(COALESCE(SUM(amount), 0) AS DECIMAL(10, 2)) as amount - FROM transfers - WHERE to_user_id = ? - AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) - AND source_type = 'user_to_agent' - GROUP BY DATE(created_at) - ORDER BY date ASC - `, [userId, parseInt(days)]); - - // 填充缺失的日期(佣金为0) - const result = []; - - for (let i = days - 1; i >= 0; i--) { - const date = dayjs().subtract(i, 'day'); - const dateStr = date.format('YYYY-MM-DD'); - - const existingData = trendData.find(item => { - const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); - return itemDateStr === dateStr; - }); - - result.push({ - date: date.format('MM-DD'), - amount: existingData ? parseFloat(existingData.amount) : 0 - }); - } - - res.json({ - success: true, - data: result - }); - - } catch (error) { - console.log(error); - - res.status(500).json({ - success: false, - message: '获取趋势数据失败' - }); - } -}); - -/** - * 获取代理商户状态分布数据 - * @route GET /agents/merchant-status/:agent_id - * @param {string} agent_id - 代理ID - * @returns {Object} 商户状态分布数据 - */ -router.get('/merchant-status/:agent_id',auth, async (req, res) => { - try { - const {agent_id} = req.params; - let [agent] = await getDB().execute(` - SELECT u.* - FROM users as u - LEFT JOIN regional_agents as rg ON rg.user_id = u.id - WHERE rg.id = ${agent_id} - `) - let userId = agent[0].id - // 获取商户状态分布 - const [statusData] = await getDB().execute( - `SELECT CASE - WHEN am.audit_status = 'approved' THEN '已审核' - WHEN am.audit_status = 'pending' THEN '待审核' - WHEN am.audit_status = 'rejected' THEN '已拒绝' - ELSE '未知状态' - END as status, - COUNT(*) as count - FROM users am - WHERE am.inviter = ? - GROUP BY am.audit_status - ORDER BY count DESC`, - [parseInt(userId)] - ); - - res.json({ - success: true, - data: statusData - }); - } catch (error) { - console.error('获取商户状态分布数据失败:', error); - res.status(500).json({success: false, message: '获取商户状态分布数据失败'}); - } -}); - -/** - * 获取代理详细统计数据(包含更多维度) - * @route GET /agents/detailed-stats/:agent_id - * @param {string} agent_id - 代理ID - * @returns {Object} 详细统计数据 - */ -router.get('/detailed-stats/:agent_id',auth, async (req, res) => { - try { - const {agent_id} = req.params; - let [agentUserInfo] = await getDB().execute(` - SELECT u.* - FROM users as u - LEFT JOIN regional_agents as ag ON ag.user_id = u.id - WHERE ag.id = ${agent_id} - `) - let agUserInfo = agentUserInfo[0]; - // 获取基础统计数据 - const [basicStats] = await getDB().execute( - `SELECT (SELECT COUNT(*) FROM users WHERE inviter = ?) as total_merchants, - (SELECT COUNT(*) - FROM users am - WHERE am.inviter = ? - AND am.audit_status = 'approved') as approved_merchants, - (SELECT COUNT(*) - FROM users am - WHERE am.inviter = ? - AND am.audit_status = 'pending') as pending_merchants, - (SELECT COUNT(*) - FROM users am - WHERE am.inviter = ? - AND am.audit_status = 'rejected') as rejected_merchants, - (SELECT COALESCE(SUM(amount), 0) - FROM transfers - WHERE transfer_type = 'user_to_agent' - AND to_user_id = ?) as total_commission - `, - [parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id), parseInt(agUserInfo.id)] - ); - - // 获取本月营收 - const [monthlyStats] = await getDB().execute( - `SELECT COALESCE(SUM(amount), 0) as monthly_commission, - COUNT(*) as monthly_commission_records - FROM transfers - WHERE to_user_id = ? - AND transfer_type = 'user_to_agent' - AND YEAR(created_at) = YEAR(CURDATE()) - AND MONTH(created_at) = MONTH(CURDATE())`, - [parseInt(agUserInfo.id)] - ); - - // 获取今日佣金 - const [dailyStats] = await getDB().execute( - `SELECT COALESCE(SUM(amount), 0) as daily_commission, - COUNT(*) as daily_commission_records - FROM transfers - WHERE to_user_id = ? - AND transfer_type = 'user_to_agent' - AND DATE(created_at) = CURDATE()`, - [parseInt(agUserInfo.id)] - ); - - // 获取最近7天新增商户数 - const [weeklyMerchants] = await getDB().execute( - `SELECT COUNT(*) as weekly_new_merchants - FROM users am - WHERE am.inviter = ? - AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`, - [parseInt(agUserInfo.id)] - ); - - - // 合并所有统计数据 - const stats = { - ...basicStats[0], - ...monthlyStats[0], - ...dailyStats[0], - ...weeklyMerchants[0] - }; - - res.json({ - success: true, - data: stats - }); - } catch (error) { - console.error('获取详细统计数据失败:', error); - res.status(500).json({success: false, message: '获取详细统计数据失败'}); - } -}); - -/** - * 获取代理商户的转账记录 - * @route GET /agents/merchants/:agent_id/transfers - * @param {string} agent_id - 代理ID - * @param {string} page - 页码 - * @param {string} limit - 每页数量 - * @returns {Object} 转账记录列表 - */ -router.get('/merchants/:agent_id/transfers', auth,async (req, res) => { - try { - const {agent_id} = req.params; - const {page = 1, limit = 10} = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 10; - const offset = (pageNum - 1) * limitNum; - - // 检查代理是否存在 - const [agentResult] = await getDB().execute( - 'SELECT * FROM regional_agents WHERE id = ?', - [parseInt(agent_id)] - ); - - if (agentResult.length === 0) { - return res.status(404).json({success: false, message: '代理不存在'}); - } - - // 查询商户转账记录 - const transferQuery = ` - SELECT t.id, - t.from_user_id, - t.to_user_id, - t.amount, - t.status, - t.transfer_type, - t.description, - t.created_at, - t.confirmed_at, - from_user.real_name as from_real_name, - from_user.phone as from_phone, - to_user.real_name as to_real_name, - to_user.phone as to_phone - FROM agent_merchants am - JOIN transfers t ON am.merchant_id = t.from_user_id - LEFT JOIN users from_user ON t.from_user_id = from_user.id - LEFT JOIN users to_user ON t.to_user_id = to_user.id - WHERE am.agent_id = ? - ORDER BY t.created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - const [transfers] = await getDB().execute(transferQuery, [parseInt(agent_id)]); - - // 查询总数 - const [totalResult] = await getDB().execute( - `SELECT COUNT(*) as total - FROM agent_merchants am - JOIN transfers t ON am.merchant_id = t.from_user_id - WHERE am.agent_id = ?`, - [parseInt(agent_id)] - ); - - const total = totalResult[0].total; - - res.json({ - success: true, - data: { - transfers, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - } - }); - } catch (error) { - console.error('获取代理商户转账记录失败:', error); - res.status(500).json({success: false, message: '获取代理商户转账记录失败,请稍后再试'}); - } -}); -/** - * 获取分销列表 - * @route GET /agents/distribution - * @returns {Object} 分销列表 - */ -router.get('/distribution', auth, async (req, res) => { - try { - - const {page = 1, size = 10, user_id} = req.query; - const {id} = user_id || req.user; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(size) || 10; - const offset = (page - 1) * size; - const [result] = await getDB().execute( - `SELECT real_name, phone, username, avatar, created_at, id as user_id - FROM users - WHERE inviter = ? - ORDER BY created_at DESC - LIMIT ${size} OFFSET ${offset}`, - [parseInt(id)] - ); - const [totalResult] = await getDB().execute( - `SELECT COUNT(*) as total - FROM users - WHERE inviter = ? `, - [parseInt(id)] - ); - result.forEach(item => { - item.created_at = dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss'); - }) - - const total = totalResult[0].total; - res.json({ - success: true, data: result, pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - }); - } catch (error) { - console.error('获取分销列表失败:', error); - res.status(500).json({success: false, message: '获取分销列表失败'}); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/agents/agents.js b/routes/agents/agents.js deleted file mode 100644 index 921e094..0000000 --- a/routes/agents/agents.js +++ /dev/null @@ -1,722 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../../database'); -const bcrypt = require('bcryptjs'); -const { auth, adminAuth } = require('../../middleware/auth'); -const dayjs = require('dayjs'); - -// 创建管理员认证中间件组合 -const authenticateAdmin = [auth, adminAuth]; - -// 获取数据库连接 -const db = { - query: async (sql, params = []) => { - const connection = getDB(); - const [rows] = await connection.execute(sql, params); - return rows; - } -}; - -// 获取代理列表和统计信息 -router.get('/', authenticateAdmin, async (req, res) => { - try { - const { page = 1, limit = 20, status, city, search,district } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 构建查询条件 - let whereConditions = []; - let queryParams = []; - - if (status) { - whereConditions.push('ra.status = ?'); - queryParams.push(status); - } - - if (district) { - whereConditions.push('zr.name = ?'); - queryParams.push(district); - } - - if (search) { - whereConditions.push('(u.real_name LIKE ? OR u.phone LIKE ?)'); - queryParams.push(`%${search}%`, `%${search}%`); - } - - const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; - - // 查询代理列表 - const agentsQuery = ` - SELECT ra.*, - u.real_name, - u.phone, - u.id_card, - province.name AS province_name, - city.name AS city_name, - zr.name AS district_name, - (SELECT COUNT(DISTINCT merchant_id) - FROM agent_merchants - WHERE agent_id = ra.id) as merchant_count, - (SELECT CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10, 2)) - FROM agent_commission_records - WHERE agent_id = ra.id) as total_commission, - 0 as paid_commission, - (SELECT CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10, 2)) - FROM agent_commission_records - WHERE agent_id = ra.id) as pending_commission - FROM regional_agents ra - LEFT JOIN users u ON ra.user_id = u.id - LEFT JOIN china_regions zr ON ra.region_id = zr.code -- 区 - LEFT JOIN china_regions city ON zr.parent_code = city.code -- 市 - LEFT JOIN china_regions province ON city.parent_code = province.code -- 省 - ${whereClause} - ORDER BY ra.created_at - DESC - LIMIT ${limitNum} OFFSET ${offset};`; - console.log(agentsQuery,queryParams) - const agents = await db.query(agentsQuery, queryParams); - - // 查询总数 - const countQuery = ` - SELECT COUNT(DISTINCT ra.id) as total - FROM regional_agents ra - LEFT JOIN users u ON ra.user_id = u.id - LEFT JOIN china_regions zr ON ra.region_id = zr.code - ${whereClause} - `; - - const totalResult = await db.query(countQuery, queryParams); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - // 查询统计信息 - const statsQuery = ` - SELECT - COUNT(*) as total_agents, - COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_agents, - COUNT(CASE WHEN status = 'active' THEN 1 END) as active_agents, - CAST(COALESCE(SUM(commission_stats.total_commission), 0) AS DECIMAL(10,2)) as total_commission - FROM regional_agents ra - LEFT JOIN ( - SELECT - agent_id, - SUM(commission_amount) as total_commission - FROM agent_commission_records - GROUP BY agent_id - ) commission_stats ON ra.id = commission_stats.agent_id - `; - - const statsResult = await db.query(statsQuery); - const stats = statsResult && statsResult.length > 0 ? statsResult[0] : { - total_agents: 0, - pending_agents: 0, - active_agents: 0, - total_commission: 0 - }; - - res.json({ - success: true, - data: { - agents, - total: parseInt(total), - stats - } - }); - } catch (error) { - console.error('获取代理列表失败:', error); - res.status(500).json({ success: false, message: '获取代理列表失败' }); - } -}); - -// 获取代理详情 -router.get('/:id', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - - const agentQuery = ` - SELECT - ra.*, - u.real_name as name, - u.phone, - u.id_card, - CONCAT(u.city, ' ', zr.district_name) as address, - zr.city_name, - zr.district_name, - ( - SELECT COUNT(DISTINCT merchant_id) - FROM agent_merchants - WHERE agent_id = ra.id - ) as merchant_count, - ( - SELECT COALESCE(SUM(commission_amount), 0) - FROM agent_commission_records - WHERE agent_id = ra.id - ) as total_commission, - 0 as paid_commission, - ( - SELECT COALESCE(SUM(commission_amount), 0) - FROM agent_commission_records - WHERE agent_id = ra.id - ) as pending_commission - FROM regional_agents ra - LEFT JOIN users u ON ra.user_id = u.id - LEFT JOIN zhejiang_regions zr ON ra.region_id = zr.id - WHERE ra.id = ? - `; - - const agentResult = await db.query(agentQuery, [id]); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在' }); - } - - const agent = agentResult[0]; - - res.json({ - success: true, - data: agent - }); - } catch (error) { - console.error('获取代理详情失败:', error); - res.status(500).json({ success: false, message: '获取代理详情失败' }); - } -}); - -// 审核通过代理申请 -router.put('/:id/approve', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { password } = req.body; - - if (!password || password.length < 6) { - return res.status(400).json({ success: false, message: '密码长度不能少于6位' }); - } - - // 检查代理是否存在且状态为待审核 - const agents = await db.query( - 'SELECT * FROM regional_agents WHERE id = ? AND status = "pending"', - [id] - ); - - if (!agents || agents.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在或状态不正确' }); - } - - const agent = agents[0]; - - // 检查该区域是否已有其他激活的代理 - const existingActiveAgents = await db.query( - 'SELECT id FROM regional_agents WHERE region_id = ? AND status = "active" AND id != ?', - [agent.region_id, id] - ); - - if (existingActiveAgents && existingActiveAgents.length > 0) { - return res.status(400).json({ success: false, message: '该区域已有激活的代理,每个区域只能有一个代理账号' }); - } - - // 加密密码并更新用户表 - const hashedPassword = await bcrypt.hash(password, 10); - - // 更新用户密码 - await db.query( - `UPDATE users SET password = ? WHERE id = ( - SELECT user_id FROM regional_agents WHERE id = ? - )`, - [hashedPassword, id] - ); - - // 更新代理状态 - await db.query( - `UPDATE regional_agents - SET status = 'active', approved_at = NOW(), approved_by_admin_id = ? - WHERE id = ?`, - [req.user.id, id] - ); - - res.json({ - success: true, - message: '代理申请已通过' - }); - } catch (error) { - console.error('审核代理申请失败:', error); - res.status(500).json({ success: false, message: '审核代理申请失败' }); - } -}); - -// 拒绝代理申请 -router.put('/:id/reject', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { reason } = req.body; - - if (!reason || reason.trim() === '') { - return res.status(400).json({ success: false, message: '请输入拒绝原因' }); - } - - // 检查代理是否存在且状态为待审核 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE id = ? AND status = "pending"', - [id] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在或状态不正确' }); - } - - const agent = agentResult[0]; - - // 更新代理状态 - await db.query( - `UPDATE regional_agents - SET status = 'rejected', reject_reason = ?, rejected_at = NOW(), rejected_by_admin_id = ? - WHERE id = ?`, - [reason.trim(), req.user.id, id] - ); - - res.json({ - success: true, - message: '代理申请已拒绝' - }); - } catch (error) { - console.error('拒绝代理申请失败:', error); - res.status(500).json({ success: false, message: '拒绝代理申请失败' }); - } -}); - -// 禁用代理 -router.put('/:id/disable', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - - // 检查代理是否存在且状态为激活 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE id = ? AND status = "active"', - [id] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在或状态不正确' }); - } - - const agent = agentResult[0]; - - // 更新代理状态 - await db.query( - 'UPDATE regional_agents SET status = "disabled", disabled_at = NOW() WHERE id = ?', - [id] - ); - - res.json({ - success: true, - message: '代理已禁用' - }); - } catch (error) { - console.error('禁用代理失败:', error); - res.status(500).json({ success: false, message: '禁用代理失败' }); - } -}); - -// 启用代理 -router.put('/:id/enable', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - - // 检查代理是否存在且状态为禁用 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE id = ? AND status = "disabled"', - [id] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在或状态不正确' }); - } - - const agent = agentResult[0]; - - // 检查该区域是否已有其他激活的代理 - const existingActiveAgentResult = await db.query( - 'SELECT id FROM regional_agents WHERE region_id = ? AND status = "active" AND id != ?', - [agent.region_id, id] - ); - - if (existingActiveAgentResult && existingActiveAgentResult.length > 0) { - return res.status(400).json({ success: false, message: '该区域已有激活的代理,每个区域只能有一个代理账号' }); - } - - // 更新代理状态 - await db.query( - 'UPDATE regional_agents SET status = "active", disabled_at = NULL WHERE id = ?', - [id] - ); - - res.json({ - success: true, - message: '代理已启用' - }); - } catch (error) { - console.error('启用代理失败:', error); - res.status(500).json({ success: false, message: '启用代理失败' }); - } -}); - -// 获取代理商户列表 -router.get('/:id/merchants', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { page = 1, limit = 20 } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 检查代理是否存在 - const agentResult = await db.query(`SELECT * FROM users WHERE id = ? AND (user_type='agent' OR user_type='agent_directly')`, [id]); - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在' }); - } - - // 查询代理的商户列表 - const merchantsQuery = ` - SELECT u.id, - u.real_name, - u.phone, - u.created_at, - u.created_at as joined_at - FROM users u - WHERE u.inviter = ${id} - GROUP BY u.id, u.created_at - ORDER BY u.created_at - DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - - const merchants = await db.query(merchantsQuery, [id]); - - // 查询总数 - const totalResult = await db.query( - 'SELECT COUNT(*) as total FROM users WHERE inviter = ?', - [id] - ); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - res.json({ - success: true, - data: { - merchants, - total: parseInt(total) - } - }); - } catch (error) { - console.error('获取代理商户列表失败:', error); - res.status(500).json({ success: false, message: '获取代理商户列表失败' }); - } -}); - -// 获取代理佣金记录 -router.get('/:id/commissions', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { page = 1, limit = 20 } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 检查代理是否存在 - const agentResult = await db.query('SELECT * FROM regional_agents WHERE id = ?', [id]); - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在' }); - } - const agent = agentResult[0]; - - // 查询佣金记录 - const commissionsQuery = ` - SELECT - acr.*, - u.phone as merchant_phone - FROM agent_commission_records acr - JOIN users u ON acr.merchant_id = u.id - WHERE acr.agent_id = ? - ORDER BY acr.created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - - const commissions = await db.query(commissionsQuery, [id]); - - // 查询总数 - const totalResult = await db.query( - 'SELECT COUNT(*) as total FROM agent_commission_records WHERE agent_id = ?', - [id] - ); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - res.json({ - success: true, - data: { - commissions, - total: parseInt(total) - } - }); - } catch (error) { - console.error('获取代理佣金记录失败:', error); - res.status(500).json({ success: false, message: '获取代理佣金记录失败' }); - } -}); - -// 获取代理商户的转账记录 -router.get('/:id/merchant-transfers', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { page = 1, limit = 20, merchant_id } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 检查代理是否存在 - const agentResult = await db.query(`SELECT * FROM users WHERE id = ? AND (user_type='agent' OR user_type='agent_directly')`, [id]); - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在' }); - } - - // 构建查询条件 - let whereConditions = ['am.inviter = ?']; - let queryParams = [id]; - - if (merchant_id) { - whereConditions.push('t.from_user_id = ?'); - queryParams.push(merchant_id); - } - - const whereClause = whereConditions.join(' AND '); - - // 查询商户转账记录 - const transfersQuery = ` - SELECT t.id, - t.from_user_id, - t.to_user_id, - t.amount, - t.status, - t.transfer_type, - t.description, - t.created_at, - t.confirmed_at, - from_user.real_name as from_real_name, - CONCAT(SUBSTRING(from_user.phone, 1, 3), '****', SUBSTRING(from_user.phone, -4)) as from_phone_masked, - to_user.real_name as to_real_name, - CONCAT(SUBSTRING(to_user.phone, 1, 3), '****', SUBSTRING(to_user.phone, -4)) as to_phone_masked - FROM users as am - JOIN transfers t ON am.id = t.from_user_id - LEFT JOIN users from_user ON t.from_user_id = from_user.id - LEFT JOIN users to_user ON t.to_user_id = to_user.id - WHERE ${whereClause} - ORDER BY t.created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - console.log(transfersQuery,queryParams); - const transfers = await db.query(transfersQuery, queryParams); - - // 查询总数 - const totalQuery = ` - SELECT COUNT(*) as total - FROM users am - JOIN transfers t ON am.id = t.from_user_id - WHERE ${whereClause} - `; - const totalResult = await db.query(totalQuery, queryParams); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - res.json({ - success: true, - data: { - transfers, - total: parseInt(total) - } - }); - } catch (error) { - console.error('1:', error); - res.status(500).json({ success: false, message: '1获取代理商户转账记录失败' }); - } -}); - -/** - * 修改代理密码 - */ -router.put('/:id/password', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { password } = req.body; - - if (!password || password.length < 6) { - return res.status(400).json({ success: false, message: '密码长度不能少于6位' }); - } - - // 检查代理是否存在且状态为激活 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE id = ? AND status = "active"', - [id] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在或状态不正确' }); - } - - // 加密新密码 - const hashedPassword = await bcrypt.hash(password, 10); - - // 更新用户表中的密码(与审核通过时的逻辑一致) - await db.query( - `UPDATE users SET password = ? WHERE id = ( - SELECT user_id FROM regional_agents WHERE id = ? - )`, - [hashedPassword, id] - ); - - res.json({ - success: true, - message: '代理密码修改成功' - }); - } catch (error) { - console.error('修改代理密码失败:', error); - res.status(500).json({ success: false, message: '修改代理密码失败' }); - } -}); - -/** - * 删除代理 - */ -router.delete('/:id', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { force = 'false' } = req.query; // 是否强制删除 - const forceDelete = force === 'true' - - // 检查代理是否存在 - const agentResult = await db.query( - 'SELECT * FROM regional_agents WHERE id = ?', - [id] - ); - - if (!agentResult || agentResult.length === 0) { - return res.status(404).json({ success: false, message: '代理不存在' }); - } - - const agent = agentResult[0]; - - // 检查代理是否有关联的商户 - const merchantCount = await db.query( - 'SELECT COUNT(*) as count FROM agent_merchants WHERE agent_id = ?', - [id] - ); - - const hasMerchants = merchantCount && merchantCount.length > 0 && merchantCount[0].count > 0; - - // 检查代理是否有佣金记录 - const commissionCount = await db.query( - 'SELECT COUNT(*) as count FROM agent_commission_records WHERE agent_id = ?', - [id] - ); - - const hasCommissions = commissionCount && commissionCount.length > 0 && commissionCount[0].count > 0; - - // 如果有关联数据且不是强制删除,则提示用户 - // if ((hasMerchants || hasCommissions) && !forceDelete) { - // return res.status(400).json({ - // success: false, - // message: '该代理存在关联数据(商户或佣金记录),请确认是否强制删除', - // data: { - // has_merchants: hasMerchants, - // has_commissions: hasCommissions, - // merchant_count: hasMerchants ? merchantCount[0].count : 0, - // commission_count: hasCommissions ? commissionCount[0].count : 0 - // }, - // require_force: true - // }); - // } - - // 开始事务删除 - const pool = getDB(); - const connection = await pool.getConnection(); - await connection.beginTransaction(); - - try { - // 删除代理商户关系 - if (hasMerchants) { - await connection.execute('DELETE FROM agent_merchants WHERE agent_id = ?', [id]); - } - - // 删除佣金记录(根据业务需求,可能需要保留历史记录) - if (hasCommissions && forceDelete) { - await connection.execute('DELETE FROM agent_commission_records WHERE agent_id = ?', [id]); - } - - // 删除代理记录 - await connection.execute('DELETE FROM regional_agents WHERE id = ?', [id]); - - // 获取区域信息用于日志 - const [regionResult] = await connection.execute( - 'SELECT * FROM zhejiang_regions WHERE id = ?', - [agent.region_id] - ); - const region = regionResult && regionResult.length > 0 ? regionResult[0] : null; - - await connection.commit(); - - // 记录操作日志 - const logMessage = `删除代理: ${agent.user_id} (区域: ${region ? `${region.city_name} ${region.district_name}` : '未知区域'})`; - console.log(`管理员 ${req.user.id} 执行操作: ${logMessage}`); - - res.json({ - success: true, - message: '代理删除成功', - data: { - deleted_agent_id: id, - deleted_merchants: hasMerchants ? merchantCount[0].count : 0, - deleted_commissions: (hasCommissions && forceDelete) ? commissionCount[0].count : 0 - } - }); - } catch (error) { - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - } catch (error) { - console.error('删除代理失败:', error); - res.status(500).json({ success: false, message: '删除代理失败' }); - } -}); - -/** - * 获取可用的城市区域列表(用于代理城市更换) - */ -router.get('/available-regions', authenticateAdmin, async (req, res) => { - try { - // 查询所有区域,并标记是否已有激活代理 - const regionsQuery = ` - SELECT - zr.id, - zr.city_name, - zr.district_name, - CASE - WHEN ra.id IS NOT NULL THEN 1 - ELSE 0 - END as has_active_agent, - ra.id as agent_id, - u.real_name as agent_name - FROM zhejiang_regions zr - LEFT JOIN regional_agents ra ON zr.id = ra.region_id AND ra.status = 'active' - LEFT JOIN users u ON ra.user_id = u.id - ORDER BY zr.city_name, zr.district_name - `; - - const regions = await db.query(regionsQuery); - - res.json({ - success: true, - data: regions - }); - } catch (error) { - console.error('获取可用区域列表失败:', error); - res.status(500).json({ success: false, message: '获取可用区域列表失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/agents/withdrawals.js b/routes/agents/withdrawals.js deleted file mode 100644 index e67a63f..0000000 --- a/routes/agents/withdrawals.js +++ /dev/null @@ -1,274 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../../database'); -const { auth, adminAuth } = require('../../middleware/auth'); - -// 创建管理员认证中间件组合 -const authenticateAdmin = [auth, adminAuth]; - -// 获取数据库连接 -const db = { - query: async (sql, params = []) => { - const connection = getDB(); - const [rows] = await connection.execute(sql, params); - return rows; - } -}; - -/** - * 获取提现申请列表 - */ -router.get('/', authenticateAdmin, async (req, res) => { - try { - const { page = 1, limit = 20, status, agent_id } = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 20; - const offset = (pageNum - 1) * limitNum; - - // 构建查询条件 - let whereConditions = []; - let queryParams = []; - - if (status) { - whereConditions.push('aw.status = ?'); - queryParams.push(status); - } - - if (agent_id) { - whereConditions.push('aw.agent_id = ?'); - queryParams.push(agent_id); - } - - const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; - - // 查询提现申请列表 - const withdrawalsQuery = ` - SELECT - aw.*, - ra.agent_code, - u.real_name as agent_name, - u.phone as agent_phone, - zr.city_name, - zr.district_name, - admin.real_name as processed_by_name - FROM agent_withdrawals aw - JOIN regional_agents ra ON aw.agent_id = ra.id - JOIN users u ON ra.user_id = u.id - LEFT JOIN zhejiang_regions zr ON ra.region_id = zr.id - LEFT JOIN users admin ON aw.processed_by = admin.id - ${whereClause} - ORDER BY aw.created_at DESC - LIMIT ${limitNum} OFFSET ${offset} - `; - - const withdrawals = await db.query(withdrawalsQuery, queryParams); - - // 查询总数 - const countQuery = ` - SELECT COUNT(*) as total - FROM agent_withdrawals aw - ${whereClause} - `; - - const totalResult = await db.query(countQuery, queryParams); - const total = totalResult && totalResult.length > 0 ? totalResult[0].total : 0; - - // 查询统计信息 - const statsQuery = ` - SELECT - COUNT(*) as total_applications, - COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_count, - COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_count, - COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_count, - COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected_count, - CAST(COALESCE(SUM(CASE WHEN status = 'pending' THEN amount END), 0) AS DECIMAL(10,2)) as pending_amount, - CAST(COALESCE(SUM(CASE WHEN status = 'completed' THEN amount END), 0) AS DECIMAL(10,2)) as completed_amount - FROM agent_withdrawals - `; - - const statsResult = await db.query(statsQuery); - const stats = statsResult && statsResult.length > 0 ? statsResult[0] : { - total_applications: 0, - pending_count: 0, - approved_count: 0, - completed_count: 0, - rejected_count: 0, - pending_amount: 0, - completed_amount: 0 - }; - - res.json({ - success: true, - data: { - withdrawals, - total: parseInt(total), - stats - } - }); - } catch (error) { - console.error('获取提现申请列表失败:', error); - res.status(500).json({ success: false, message: '获取提现申请列表失败' }); - } -}); - -/** - * 审核提现申请 - */ -router.put('/:id/review', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const { action, admin_note } = req.body; - const adminId = req.user.id; - - if (!['approve', 'reject'].includes(action)) { - return res.status(400).json({ success: false, message: '无效的审核操作' }); - } - - // 检查提现申请是否存在且状态为待审核 - const withdrawalResult = await db.query( - 'SELECT * FROM agent_withdrawals WHERE id = ? AND status = "pending"', - [id] - ); - - if (!withdrawalResult || withdrawalResult.length === 0) { - return res.status(404).json({ success: false, message: '提现申请不存在或已处理' }); - } - - const withdrawal = withdrawalResult[0]; - const newStatus = action === 'approve' ? 'approved' : 'rejected'; - - // 开始事务 - const pool = getDB(); - const connection = await pool.getConnection(); - await connection.beginTransaction(); - - try { - // 更新提现申请状态 - await connection.execute( - 'UPDATE agent_withdrawals SET status = ?, admin_note = ?, processed_by = ?, processed_at = NOW() WHERE id = ?', - [newStatus, admin_note || null, adminId, id] - ); - - // 如果是拒绝,需要恢复代理的待提现金额 - if (action === 'reject') { - await connection.execute( - 'UPDATE regional_agents SET pending_withdrawal = pending_withdrawal - ? WHERE id = ?', - [withdrawal.amount, withdrawal.agent_id] - ); - } - - await connection.commit(); - connection.release(); // 释放连接回连接池 - - res.json({ - success: true, - message: action === 'approve' ? '提现申请已通过审核' : '提现申请已拒绝' - }); - } catch (error) { - await connection.rollback(); - connection.release(); // 释放连接回连接池 - throw error; - } - } catch (error) { - console.error('审核提现申请失败:', error); - res.status(500).json({ success: false, message: '审核提现申请失败' }); - } -}); - -/** - * 标记提现完成 - */ -router.put('/:id/complete', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - const adminId = req.user.id; - - // 检查提现申请是否存在且状态为已审核 - const withdrawalResult = await db.query( - 'SELECT * FROM agent_withdrawals WHERE id = ? AND status = "approved"', - [id] - ); - - if (!withdrawalResult || withdrawalResult.length === 0) { - return res.status(404).json({ success: false, message: '提现申请不存在或状态不正确' }); - } - - const withdrawal = withdrawalResult[0]; - - // 开始事务 - const pool = getDB(); - const connection = await pool.getConnection(); - await connection.beginTransaction(); - - try { - // 更新提现申请状态为已完成 - await connection.execute( - 'UPDATE agent_withdrawals SET status = "completed", processed_by = ?, processed_at = NOW() WHERE id = ?', - [adminId, id] - ); - - // 更新代理的已提现金额和待提现金额 - await connection.execute( - 'UPDATE regional_agents SET withdrawn_amount = withdrawn_amount + ?, pending_withdrawal = pending_withdrawal - ? WHERE id = ?', - [withdrawal.amount, withdrawal.amount, withdrawal.agent_id] - ); - - await connection.commit(); - connection.release(); // 释放连接回连接池 - - res.json({ - success: true, - message: '提现已标记为完成' - }); - } catch (error) { - await connection.rollback(); - connection.release(); // 释放连接回连接池 - throw error; - } - } catch (error) { - console.error('标记提现完成失败:', error); - res.status(500).json({ success: false, message: '标记提现完成失败' }); - } -}); - -/** - * 获取提现申请详情 - */ -router.get('/:id', authenticateAdmin, async (req, res) => { - try { - const { id } = req.params; - - const withdrawalQuery = ` - SELECT - aw.*, - ra.agent_code, - u.real_name as agent_name, - u.phone as agent_phone, - zr.city_name, - zr.district_name, - admin.real_name as processed_by_name - FROM agent_withdrawals aw - JOIN regional_agents ra ON aw.agent_id = ra.id - JOIN users u ON ra.user_id = u.id - LEFT JOIN zhejiang_regions zr ON ra.region_id = zr.id - LEFT JOIN users admin ON aw.processed_by = admin.id - WHERE aw.id = ? - `; - - const withdrawalResult = await db.query(withdrawalQuery, [id]); - - if (!withdrawalResult || withdrawalResult.length === 0) { - return res.status(404).json({ success: false, message: '提现申请不存在' }); - } - - res.json({ - success: true, - data: withdrawalResult[0] - }); - } catch (error) { - console.error('获取提现申请详情失败:', error); - res.status(500).json({ success: false, message: '获取提现申请详情失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/announcements.js b/routes/announcements.js deleted file mode 100644 index ecbbb74..0000000 --- a/routes/announcements.js +++ /dev/null @@ -1,397 +0,0 @@ -const express = require('express'); -const { getDB } = require('../database'); -const { auth, adminAuth } = require('../middleware/auth'); -const dayjs = require('dayjs'); - -const router = express.Router(); - - - -router.get('/', auth, async (req, res) => { - try { - const db = getDB(); - const { page = 1, limit = 10, status, type, keyword } = req.query; - const offset = (page - 1) * limit; - - let whereClause = 'WHERE 1=1'; - const params = []; - - if (status) { - whereClause += ' AND status = ?'; - params.push(status); - } - - if (type) { - whereClause += ' AND type = ?'; - params.push(type); - } - - if (keyword) { - whereClause += ' AND (title LIKE ? OR content LIKE ?)'; - params.push(`%${keyword}%`, `%${keyword}%`); - } - - // 获取总数 - const countQuery = `SELECT COUNT(*) as total FROM announcements ${whereClause}`; - const [countResult] = await db.execute(countQuery, params); - const total = countResult[0].total; - - // 获取公告列表(包含用户阅读状态) - const limitValue = Math.max(1, Math.min(100, parseInt(limit))); - const offsetValue = Math.max(0, parseInt(offset)); - - const query = ` - SELECT a.*, u.username as creator_name, - uar.is_read, - uar.read_at, - CASE - WHEN a.expire_time IS NOT NULL AND a.expire_time < NOW() THEN 1 - ELSE 0 - END as is_expired - FROM announcements a - LEFT JOIN users u ON a.created_by = u.id - LEFT JOIN user_announcement_reads uar ON a.id = uar.announcement_id AND uar.user_id = ? - ${whereClause} - ORDER BY a.is_pinned DESC, a.created_at DESC - LIMIT ${limitValue} OFFSET ${offsetValue} - `; - - const [announcements] = await db.execute(query, [req.user.id, ...params]); - - - - const expiredUnreadAnnouncements = announcements.filter(a => a.is_expired && !a.is_read); - - if (expiredUnreadAnnouncements.length > 0) { - const expiredIds = expiredUnreadAnnouncements.map(a => a.id); - await db.execute(` - INSERT INTO user_announcement_reads (user_id, announcement_id, is_read, read_at) - VALUES ${expiredIds.map(() => '(?, ?, TRUE, NOW())').join(', ')} - ON DUPLICATE KEY UPDATE is_read = TRUE, read_at = NOW() - `, expiredIds.flatMap(id => [req.user.id, id])); - - // 更新返回数据中的阅读状态 - expiredUnreadAnnouncements.forEach(a => { - a.is_read = true; - a.read_at = new Date(); - }); - } - - res.json({ - success: true, - data: { - announcements, - total, - page: parseInt(page), - limit: parseInt(limit), - totalPages: Math.ceil(total / limit) - } - }); - } catch (error) { - console.error('获取公告列表失败:', error); - res.status(500).json({ success: false, message: '获取公告列表失败' }); - } -}); - - -router.get('/:id', auth, async (req, res) => { - try { - const db = getDB(); - const { id } = req.params; - - const query = ` - SELECT a.*, u.username as creator_name, - uar.is_read, - uar.read_at, - CASE - WHEN a.expire_time IS NOT NULL AND a.expire_time < NOW() THEN 1 - ELSE 0 - END as is_expired - FROM announcements a - LEFT JOIN users u ON a.created_by = u.id - LEFT JOIN user_announcement_reads uar ON a.id = uar.announcement_id AND uar.user_id = ? - WHERE a.id = ? - `; - - const [result] = await db.execute(query, [req.user.id, id]); - - if (result.length === 0) { - return res.status(404).json({ success: false, message: '公告不存在' }); - } - - const announcement = result[0]; - - // 如果公告未读或已过期但未标记为已读,则标记为已读 - if (!announcement.is_read || (announcement.is_expired && !announcement.is_read)) { - await db.execute(` - INSERT INTO user_announcement_reads (user_id, announcement_id, is_read, read_at) - VALUES (?, ?, TRUE, NOW()) - ON DUPLICATE KEY UPDATE is_read = TRUE, read_at = NOW() - `, [req.user.id, id]); - - announcement.is_read = true; - announcement.read_at = new Date(); - } - - res.json({ success: true, data: announcement }); - } catch (error) { - console.error('获取公告详情失败:', error); - res.status(500).json({ success: false, message: '获取公告详情失败' }); - } -}); - - -router.post('/', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const { - title, - content, - type = 'system', - priority = 'medium', - status = 'draft', - is_pinned = false, - publish_time, - expire_time - } = req.body; - - if (!title || !content) { - return res.status(400).json({ success: false, message: '标题和内容不能为空' }); - } - - const query = ` - INSERT INTO announcements ( - title, content, type, priority, status, is_pinned, - publish_time, expire_time, created_by, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) - `; - - const [result] = await db.execute(query, [ - title, - content, - type, - priority, - status, - is_pinned, - publish_time || null, - expire_time || null, - req.user.id - ]); - - res.status(201).json({ - success: true, - message: '公告创建成功', - data: { id: result.insertId } - }); - } catch (error) { - console.error('创建公告失败:', error); - res.status(500).json({ success: false, message: '创建公告失败' }); - } -}); - - -router.put('/:id', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const { id } = req.params; - let { - title, - content, - type, - priority, - status, - is_pinned, - publish_time, - expire_time - } = req.body; - - // 检查公告是否存在 - const [existing] = await db.execute('SELECT id FROM announcements WHERE id = ?', [id]); - if (existing.length === 0) { - return res.status(404).json({ success: false, message: '公告不存在' }); - } - - const updates = []; - const params = []; - - if (title !== undefined) { - updates.push('title = ?'); - params.push(title); - } - if (content !== undefined) { - updates.push('content = ?'); - params.push(content); - } - if (type !== undefined) { - updates.push('type = ?'); - params.push(type); - } - if (priority !== undefined) { - updates.push('priority = ?'); - params.push(priority); - } - if (status !== undefined) { - updates.push('status = ?'); - params.push(status); - } - if (is_pinned !== undefined) { - updates.push('is_pinned = ?'); - params.push(is_pinned); - } - if (publish_time !== undefined) { - updates.push('publish_time = ?'); - publish_time = dayjs(publish_time).format('YYYY-MM-DD'); - params.push(publish_time); - } - if (expire_time !== undefined) { - updates.push('expire_time = ?'); - params.push(expire_time); - } - - if (updates.length === 0) { - return res.status(400).json({ success: false, message: '没有要更新的字段' }); - } - - updates.push('updated_at = NOW()'); - params.push(id); - - const query = `UPDATE announcements SET ${updates.join(', ')} WHERE id = ?`; - await db.execute(query, params); - - res.json({ success: true, message: '公告更新成功' }); - } catch (error) { - console.error('更新公告失败:', error); - res.status(500).json({ success: false, message: '更新公告失败' }); - } -}); - - -router.delete('/:id', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const { id } = req.params; - - // 检查公告是否存在 - const [existing] = await db.execute('SELECT id FROM announcements WHERE id = ?', [id]); - if (existing.length === 0) { - return res.status(404).json({ success: false, message: '公告不存在' }); - } - - await db.execute('DELETE FROM announcements WHERE id = ?', [id]); - - res.json({ success: true, message: '公告删除成功' }); - } catch (error) { - console.error('删除公告失败:', error); - res.status(500).json({ success: false, message: '删除公告失败' }); - } -}); - - -router.get('/public/list', async (req, res) => { - try { - const db = getDB(); - const { limit = 5 } = req.query; - const limitValue = Math.max(1, Math.min(50, parseInt(limit))); - - const query = ` - SELECT id, title, content, type, priority, publish_time, created_at - FROM announcements - WHERE status = 'published' - AND (expire_time IS NULL OR expire_time > NOW()) - AND (publish_time IS NULL OR publish_time <= NOW()) - ORDER BY is_pinned DESC, created_at DESC - LIMIT ${limitValue} - `; - - const [announcements] = await db.execute(query, []); - - res.json({ success: true, data: announcements }); - } catch (error) { - console.error('获取公开公告失败:', error); - res.status(500).json({ success: false, message: '获取公开公告失败' }); - } -}); - -// 标记公告为已读 -router.post('/:id/read', auth, async (req, res) => { - try { - const db = getDB(); - const { id } = req.params; - - // 检查公告是否存在 - const [existing] = await db.execute('SELECT id FROM announcements WHERE id = ?', [id]); - if (existing.length === 0) { - return res.status(404).json({ success: false, message: '公告不存在' }); - } - - // 标记为已读 - await db.execute(` - INSERT INTO user_announcement_reads (user_id, announcement_id, is_read, read_at) - VALUES (?, ?, TRUE, NOW()) - ON DUPLICATE KEY UPDATE is_read = TRUE, read_at = NOW() - `, [req.user.id, id]); - - res.json({ success: true, message: '已标记为已读' }); - } catch (error) { - console.error('标记公告已读失败:', error); - res.status(500).json({ success: false, message: '标记公告已读失败' }); - } -}); - -// 获取用户未读公告数量 -router.get('/unread/count', auth, async (req, res) => { - try { - const db = getDB(); - - const query = ` - SELECT COUNT(*) as unread_count - FROM announcements a - LEFT JOIN user_announcement_reads uar ON a.id = uar.announcement_id AND uar.user_id = ? - WHERE a.status = 'published' - AND (a.publish_time IS NULL OR a.publish_time <= NOW()) - AND (a.expire_time IS NULL OR a.expire_time > NOW()) - AND (uar.is_read IS NULL OR uar.is_read = FALSE) - `; - - const [result] = await db.execute(query, [req.user.id]); - - res.json({ - success: true, - data: { - unread_count: result[0].unread_count - } - }); - } catch (error) { - console.error('获取未读公告数量失败:', error); - res.status(500).json({ success: false, message: '获取未读公告数量失败' }); - } -}); - -// 批量标记公告为已读 -router.post('/batch/read', auth, async (req, res) => { - try { - const db = getDB(); - const { announcement_ids } = req.body; - - if (!announcement_ids || !Array.isArray(announcement_ids) || announcement_ids.length === 0) { - return res.status(400).json({ success: false, message: '请提供有效的公告ID列表' }); - } - - // 批量标记为已读 - const values = announcement_ids.map(() => '(?, ?, TRUE, NOW())').join(', '); - const params = announcement_ids.flatMap(id => [req.user.id, id]); - - await db.execute(` - INSERT INTO user_announcement_reads (user_id, announcement_id, is_read, read_at) - VALUES ${values} - ON DUPLICATE KEY UPDATE is_read = TRUE, read_at = NOW() - `, params); - - res.json({ success: true, message: '批量标记已读成功' }); - } catch (error) { - console.error('批量标记公告已读失败:', error); - res.status(500).json({ success: false, message: '批量标记公告已读失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js deleted file mode 100644 index d65b95d..0000000 --- a/routes/auth.js +++ /dev/null @@ -1,345 +0,0 @@ -const express = require('express'); -const bcrypt = require('bcryptjs'); -const jwt = require('jsonwebtoken'); -const {getDB} = require('../database'); - -const router = express.Router(); -const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; -router.post('/register', async (req, res) => { - try { - const db = getDB(); - await db.query('START TRANSACTION'); - - const { - username, - phone, - password, - city, - district_id: district, - province, - inviter = null, - captchaId, - captchaText, - smsCode, // 短信验证码 - role = 'user' - } = req.body; - - if (!username || !phone || !password || !city || !district || !province) { - return res.status(400).json({success: false, message: '用户名、手机号、密码、城市和区域不能为空'}); - } - - if (!captchaId || !captchaText) { - return res.status(400).json({success: false, message: '图形验证码不能为空'}); - } - const storedCaptcha = global.captchaStore.get(captchaId); - console.log(storedCaptcha); - - if (!storedCaptcha) { - return res.status(400).json({ - success: false, - message: '验证码不存在或已过期' - }); - } - - // 检查是否过期 - if (Date.now() > storedCaptcha.expires) { - global.captchaStore.delete(captchaId); - return res.status(400).json({ - success: false, - message: '验证码已过期' - }); - } - - // 验证验证码(不区分大小写) - const isValid = storedCaptcha.text === captchaText.toLowerCase(); - - // 删除已验证的验证码 - global.captchaStore.delete(captchaId); - - if (!isValid) { - return res.status(400).json({ - success: false, - message: '验证码错误' - }); - } - if (!smsCode) { - return res.status(400).json({success: false, message: '短信验证码不能为空'}); - } - // 验证短信验证码 - const smsAPI = require('./sms'); - const smsValid = smsAPI.verifySMSCode(phone, smsCode); - if (!smsValid) { - return res.status(400).json({success: false, message: '短信验证码错误或已过期'}); - } - - // 验证手机号格式 - const phoneRegex = /^1[3-9]\d{9}$/; - if (!phoneRegex.test(phone)) { - return res.status(400).json({success: false, message: '手机号格式不正确'}); - } - - - // 检查用户是否已存在 - const [existingUsers] = await db.execute( - 'SELECT id, payment_status FROM users WHERE username = ? OR phone = ?', - [username, phone] - ); - - if (existingUsers.length > 0) { - return res.status(400).json({success: false, message: '用户名或手机号已存在'}); - } - - // 加密密码 - const hashedPassword = await bcrypt.hash(password, 10); - - // 创建用户(初始状态为未支付) - const [result] = await db.execute( - 'INSERT INTO users (username, phone, password, role, points, audit_status, city, district_id, payment_status, province, inviter) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "unpaid", ?, ?)', - [username, phone, hashedPassword, role, 0, 'pending', city, district, province, inviter] - ); - - const userId = result.insertId; - await db.query('COMMIT'); - - // 生成JWT token(用于支付流程) - const token = jwt.sign( - {userId: userId, username, role}, - JWT_SECRET, - {expiresIn: '24h'} - ); - - res.status(201).json({ - success: true, - message: '用户信息创建成功,请完成支付以激活账户', - token, - user: { - id: userId, - username, - phone, - role, - points: 0, - audit_status: 'pending', - city, - district, - paymentStatus: 'unpaid' - }, - needPayment: true - }); - } catch (error) { - try { - // await getDB().query('ROLLBACK'); - } catch (rollbackError) { - console.error('回滚错误:', rollbackError); - } - console.error('注册错误详情:', error); - console.error('错误堆栈:', error.stack); - res.status(500).json({ - success: false, - message: '注册失败', - error: process.env.NODE_ENV === 'development' ? error.message : undefined - }); - } -}); - - -router.post('/login', async (req, res) => { - try { - const db = getDB(); - const {username, password, captchaId, captchaText} = req.body; - - if (!username || !password) { - return res.status(400).json({success: false, message: '用户名和密码不能为空'}); - } - - if (!captchaId || !captchaText) { - return res.status(400).json({success: false, message: '验证码不能为空'}); - } - // 获取存储的验证码 - const storedCaptcha = global.captchaStore.get(captchaId); - console.log(storedCaptcha); - - if (!storedCaptcha) { - return res.status(400).json({ - success: false, - message: '验证码不存在或已过期' - }); - } - - // 检查是否过期 - if (Date.now() > storedCaptcha.expires) { - global.captchaStore.delete(captchaId); - return res.status(400).json({ - success: false, - message: '验证码已过期' - }); - } - - // 验证验证码(不区分大小写) - const isValid = storedCaptcha.text === captchaText.toLowerCase(); - - // 删除已验证的验证码 - global.captchaStore.delete(captchaId); - - if (!isValid) { - return res.status(400).json({ - success: false, - message: '验证码错误' - }); - } - - // 注意:验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证 - - // 查找用户(包含支付状态) - console.log('登录尝试 - 用户名:', username); - const [users] = await db.execute( - 'SELECT * FROM users WHERE username = ?', - [username] - ); - - console.log('查找到的用户数量:', users.length); - if (users.length === 0) { - console.log('用户不存在:', username); - return res.status(401).json({success: false, message: '用户名或密码错误'}); - } - - const user = users[0]; - console.log('找到用户:', user.username, '密码长度:', user.password ? user.password.length : 'null'); - - // 验证密码 - console.log('验证密码 - 输入密码:', password, '数据库密码前10位:', user.password ? user.password.substring(0, 10) : 'null'); - const isValidPassword = await bcrypt.compare(password, user.password); - console.log('密码验证结果:', isValidPassword); - - if (!isValidPassword) { - console.log('密码验证失败'); - return res.status(401).json({success: false, message: '用户名或密码错误'}); - } - - // 检查支付状态(管理员除外) - if (user.role !== 'admin' && user.payment_status === 'unpaid') { - const token = jwt.sign( - {userId: user.id, username: user.username, role: user.role}, - JWT_SECRET, - {expiresIn: '5m'} - ); - return res.status(200).json({ - success: false, - message: '您的账户尚未激活,请完成支付后再登录', - needPayment: true, - user: user[0], - token - }); - } - - // 检查用户审核状态(管理员除外,只阻止被拒绝的用户) - if (user.role !== 'admin' && user.audit_status === 'rejected') { - return res.status(403).json({success: false, message: '您的账户审核未通过,请联系管理员'}); - } - // 待审核用户可以正常登录使用系统,但匹配功能会有限制 - - // 生成JWT token - const token = jwt.sign( - {userId: user.id, username: user.username, role: user.role}, - JWT_SECRET, - {expiresIn: '24h'} - ); - const [is_distribution] = await db.execute(` - SELECT * - FROM distribution - WHERE user_id = ?`, [user.id]); - user.distribution = is_distribution.length > 0 ? true : false; - res.json({ - success: true, - message: '登录成功', - token, - user - }); - } catch (error) { - console.error('登录错误:', error); - res.status(500).json({success: false, message: '登录失败'}); - } -}); - -// 验证token中间件 -const authenticateToken = (req, res, next) => { - const authHeader = req.headers['authorization']; - const token = authHeader && authHeader.split(' ')[1]; - - if (!token) { - return res.status(401).json({success: false, message: '访问令牌缺失'}); - } - - jwt.verify(token, JWT_SECRET, (err, user) => { - if (err) { - return res.status(403).json({success: false, message: '访问令牌无效'}); - } - req.user = user; - next(); - }); -}; - -// 获取当前用户信息 -router.get('/me', authenticateToken, async (req, res) => { - try { - const db = getDB(); - const [users] = await db.execute( - 'SELECT id, username, role, avatar, points, created_at FROM users WHERE id = ?', - [req.user.userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - res.json({success: true, user: users[0]}); - } catch (error) { - console.error('获取用户信息错误:', error); - res.status(500).json({success: false, message: '获取用户信息失败'}); - } -}); - -// 修改密码 -router.put('/change-password', authenticateToken, async (req, res) => { - try { - const db = getDB(); - const {currentPassword, newPassword} = req.body; - - if (!currentPassword || !newPassword) { - return res.status(400).json({success: false, message: '旧密码和新密码不能为空'}); - } - - // 获取用户当前密码 - const [users] = await db.execute( - 'SELECT password FROM users WHERE id = ?', - [req.user.userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - // 验证旧密码 - const isValidPassword = await bcrypt.compare(currentPassword, users[0].password); - - if (!isValidPassword) { - return res.status(400).json({success: false, message: '旧密码错误'}); - } - - // 加密新密码 - const hashedNewPassword = await bcrypt.hash(newPassword, 10); - - // 更新密码 - await db.execute( - 'UPDATE users SET password = ? WHERE id = ?', - [hashedNewPassword, req.user.userId] - ); - - res.json({success: true, message: '密码修改成功'}); - } catch (error) { - console.error('修改密码错误:', error); - res.status(500).json({success: false, message: '修改密码失败'}); - } -}); - -module.exports = router; -module.exports.authenticateToken = authenticateToken; \ No newline at end of file diff --git a/routes/captcha.js b/routes/captcha.js deleted file mode 100644 index 4fb4f11..0000000 --- a/routes/captcha.js +++ /dev/null @@ -1,220 +0,0 @@ -const express = require('express'); -const crypto = require('crypto'); -const router = express.Router(); - - - -// 内存存储验证码(生产环境建议使用Redis) - - -/** - * 生成随机验证码字符串 - * @param {number} length 验证码长度 - * @returns {string} 验证码字符串 - */ -function generateCaptchaText(length = 4) { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; -} - -/** - * 生成SVG验证码图片 - * @param {string} text 验证码文本 - * @returns {string} SVG字符串 - */ -function generateCaptchaSVG(text) { - const width = 120; - const height = 40; - const fontSize = 18; - - // 生成随机颜色 - const getRandomColor = () => { - const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8']; - return colors[Math.floor(Math.random() * colors.length)]; - }; - - // 生成干扰线 - const generateNoise = () => { - let noise = ''; - for (let i = 0; i < 3; i++) { - const x1 = Math.random() * width; - const y1 = Math.random() * height; - const x2 = Math.random() * width; - const y2 = Math.random() * height; - noise += ``; - } - return noise; - }; - - // 生成干扰点 - const generateDots = () => { - let dots = ''; - for (let i = 0; i < 20; i++) { - const x = Math.random() * width; - const y = Math.random() * height; - const r = Math.random() * 2 + 1; - dots += ``; - } - return dots; - }; - - // 生成文字 - let textElements = ''; - const charWidth = width / text.length; - - for (let i = 0; i < text.length; i++) { - const char = text[i]; - const x = charWidth * i + charWidth / 2; - const y = height / 2 + fontSize / 3; - const rotation = (Math.random() - 0.5) * 30; // 随机旋转角度 - const color = getRandomColor(); - - textElements += ` - - ${char} - `; - } - - const svg = ` - - - - - - - - - ${generateNoise()} - ${generateDots()} - ${textElements} - `; - - return svg; -} - - -router.get('/generate', (req, res) => { - try { - // 生成验证码文本 - const captchaText = generateCaptchaText(); - - // 生成唯一ID - const captchaId = crypto.randomUUID(); - - // 存储验证码(5分钟过期) - global.captchaStore.set(captchaId, { - text: captchaText.toLowerCase(), // 存储小写用于比较 - expires: Date.now() + 5 * 60 * 1000 // 5分钟过期 - }); - - // 生成SVG图片 - const svgImage = generateCaptchaSVG(captchaText); - res.json({ - success: true, - data: { - captchaId, - image: `data:image/svg+xml;base64,${Buffer.from(svgImage).toString('base64')}` - } - }); - } catch (error) { - console.error('生成验证码失败:', error); - res.status(500).json({ - success: false, - message: '生成验证码失败' - }); - } -}); - - -router.post('/verify', (req, res) => { - try { - const { captchaId, captchaText } = req.body; - - if (!captchaId || !captchaText) { - return res.status(400).json({ - success: false, - message: '验证码ID和验证码不能为空' - }); - } - - // 获取存储的验证码 - const storedCaptcha = global.captchaStore.get(captchaId); - - if (!storedCaptcha) { - return res.status(400).json({ - success: false, - message: '验证码不存在或已过期' - }); - } - - // 检查是否过期 - if (Date.now() > storedCaptcha.expires) { - global.captchaStore.delete(captchaId); - return res.status(400).json({ - success: false, - message: '验证码已过期' - }); - } - - // 验证验证码(不区分大小写) - const isValid = storedCaptcha.text === captchaText.toLowerCase(); - - // 验证后删除验证码(无论成功失败) - global.captchaStore.delete(captchaId); - - if (isValid) { - res.json({ - success: true, - message: '验证码验证成功' - }); - } else { - res.status(400).json({ - success: false, - message: '验证码错误' - }); - } - } catch (error) { - console.error('验证验证码失败:', error); - res.status(500).json({ - success: false, - message: '验证验证码失败' - }); - } -}); - -// 清理过期验证码的定时任务 -setInterval(() => { - const now = Date.now(); - for (const [id, captcha] of global.captchaStore.entries()) { - if (now > captcha.expires) { - global.captchaStore.delete(id); - } - } -}, 60 * 1000); // 每分钟清理一次 - -// 导出验证函数供其他模块使用 -module.exports = router; -module.exports.verifyCaptcha = (captchaId, captchaText) => { - const captcha = global.captchaStore.get(captchaId); - if (!captcha) { - return false; // 验证码不存在或已过期 - } - - if (captcha.text.toLowerCase() !== captchaText.toLowerCase()) { - return false; // 验证码错误 - } - - // 验证成功后删除验证码(一次性使用) - global.captchaStore.delete(captchaId); - return true; -}; \ No newline at end of file diff --git a/routes/cart.js b/routes/cart.js deleted file mode 100644 index 6cb109f..0000000 --- a/routes/cart.js +++ /dev/null @@ -1,935 +0,0 @@ -const express = require('express'); -const { getDB } = require('../database'); -const { auth } = require('../middleware/auth'); - -const router = express.Router(); - -/** - * @swagger - * tags: - * name: Cart - * description: 购物车管理相关接口 - */ - -/** - * @swagger - * components: - * schemas: - * CartItem: - * type: object - * properties: - * id: - * type: integer - * description: 购物车项ID - * user_id: - * type: integer - * description: 用户ID - * product_id: - * type: integer - * description: 商品ID - * quantity: - * type: integer - * description: 商品数量 - * spec_combination_id: - * type: integer - * description: 商品规格组合ID - * created_at: - * type: string - * format: date-time - * description: 创建时间 - * updated_at: - * type: string - * format: date-time - * description: 更新时间 - * product: - * type: object - * properties: - * id: - * type: integer - * name: - * type: string - * price: - * type: integer - * points_price: - * type: integer - * rongdou_price: - * type: integer - * image_url: - * type: string - * stock: - * type: integer - * status: - * type: string - */ - -/** - * @swagger - * /api/cart: - * get: - * summary: 获取购物车列表 - * tags: [Cart] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 获取购物车成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * items: - * type: array - * items: - * $ref: '#/components/schemas/CartItem' - * total_count: - * type: integer - * description: 购物车商品总数量 - * total_amount: - * type: integer - * description: 购物车总金额 - * total_points: - * type: integer - * description: 购物车总积分 - * total_rongdou: - * type: integer - * description: 购物车总融豆 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.get('/', auth, async (req, res) => { - try { - const userId = req.user.id; - - // 获取购物车商品列表 - const query = ` - SELECT - c.id, c.user_id, c.product_id, c.quantity, c.specification_id, - c.created_at, c.updated_at, - p.name, p.price, p.points_price, p.rongdou_price, p.image_url, - p.stock, p.status, p.shop_name, p.shop_avatar, - psc.combination_key, psc.price_adjustment, - psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock, - GROUP_CONCAT(CONCAT(sn.display_name, ':', sv.display_value) ORDER BY sn.sort_order SEPARATOR ' | ') as spec_display - FROM cart_items c - LEFT JOIN products p ON c.product_id = p.id - LEFT JOIN product_spec_combinations psc ON c.specification_id = psc.id - LEFT JOIN JSON_TABLE(psc.spec_values, '$[*]' COLUMNS (spec_value_id INT PATH '$')) jt ON psc.id IS NOT NULL - LEFT JOIN spec_values sv ON jt.spec_value_id = sv.id - LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id - WHERE c.user_id = ? AND p.status = 'active' - GROUP BY c.id - ORDER BY c.created_at DESC - `; - - const [cartItems] = await getDB().execute(query, [userId]); - - // 计算总计信息 - let totalCount = 0; - let totalAmount = 0; - let totalPoints = 0; - let totalRongdou = 0; - - const items = cartItems.map(item => { - const finalPrice = item.price + (item.price_adjustment || 0); - const finalPointsPrice = item.points_price + (item.points_adjustment || 0); - const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); - - totalCount += item.quantity; - totalAmount += finalPrice * item.quantity; - totalPoints += finalPointsPrice * item.quantity; - totalRongdou += finalRongdouPrice * item.quantity; - - return { - id: item.id, - user_id: item.user_id, - product_id: item.product_id, - quantity: item.quantity, - spec_combination_id: item.spec_combination_id, - created_at: item.created_at, - updated_at: item.updated_at, - product: { - id: item.product_id, - name: item.name, - price: finalPrice, - points_price: finalPointsPrice, - rongdou_price: finalRongdouPrice, - image_url: item.image_url, - stock: item.spec_combination_id ? item.spec_stock : item.stock, - status: item.status, - shop_name: item.shop_name, - shop_avatar: item.shop_avatar - }, - specification: item.spec_combination_id ? { - id: item.spec_combination_id, - combination_key: item.combination_key, - spec_display: item.spec_display, - price_adjustment: item.price_adjustment, - points_adjustment: item.points_adjustment, - rongdou_adjustment: item.rongdou_adjustment - } : null - }; - }); - - res.json({ - success: true, - data: { - items, - total_count: totalCount, - total_amount: totalAmount, - total_points: totalPoints, - total_rongdou: totalRongdou - } - }); - } catch (error) { - console.error('获取购物车失败:', error); - res.status(500).json({ success: false, message: '获取购物车失败' }); - } -}); - -/** - * @swagger - * /api/cart: - * post: - * summary: 添加商品到购物车 - * tags: [Cart] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * product_id: - * type: integer - * description: 商品ID - * quantity: - * type: integer - * description: 商品数量 - * minimum: 1 - * spec_combination_id: - * type: integer - * description: 商品规格组合ID(可选) - * required: - * - product_id - * - quantity - * responses: - * 201: - * description: 添加到购物车成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * cart_item_id: - * type: integer - * 400: - * description: 参数错误或库存不足 - * 401: - * description: 未授权 - * 404: - * description: 商品不存在或已下架 - * 500: - * description: 服务器错误 - */ -router.post('/add', auth, async (req, res) => { - const db = getDB(); - await db.query('START TRANSACTION'); - - try { - const { productId, quantity, specificationId } = req.body; - const userId = req.user.id; - - - // 验证必填字段 - if (!productId || !quantity || quantity < 1) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '请填写正确的商品信息和数量' }); - } - - // 检查商品是否存在且有效 - const [products] = await db.execute( - 'SELECT id, name, stock, status FROM products WHERE id = ?', - [productId] - ); - - if (products.length === 0 || products[0].status !== 'active') { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '商品不存在或已下架' }); - } - - const product = products[0]; - let availableStock = product.stock; - - // 如果指定了规格组合,检查规格组合库存 - if (specificationId) { - const [specs] = await db.execute( - 'SELECT id, stock, status FROM product_spec_combinations WHERE id = ? AND product_id = ?', - [specificationId, productId] - ); - - if (specs.length === 0 || specs[0].status !== 'active') { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '商品规格组合不存在或已下架' }); - } - - availableStock = specs[0].stock; - } - - // 检查购物车中是否已存在相同商品和规格组合 - const [existingItems] = await db.execute( - 'SELECT id, quantity FROM cart_items WHERE user_id = ? AND product_id = ? AND (specification_id = ? OR (specification_id IS NULL AND ? IS NULL))', - [userId, productId, specificationId, specificationId] - ); - - let finalQuantity = quantity; - if (existingItems.length > 0) { - finalQuantity += existingItems[0].quantity; - } - - // 检查库存是否足够 - if (availableStock < finalQuantity) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '库存不足' }); - } - - let cartItemId; - - if (existingItems.length > 0) { - // 更新现有购物车项的数量 - await db.execute( - 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', - [finalQuantity, existingItems[0].id] - ); - cartItemId = existingItems[0].id; - } else { - // 添加新的购物车项 - const [result] = await db.execute( - 'INSERT INTO cart_items (user_id, product_id, quantity, specification_id, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())', - [userId, productId, quantity, specificationId] - ); - cartItemId = result.insertId; - } - - await db.query('COMMIT'); - - res.status(201).json({ - success: true, - message: '添加到购物车成功', - data: { cart_item_id: cartItemId } - }); - } catch (error) { - await db.query('ROLLBACK'); - console.error('添加到购物车失败:', error); - res.status(500).json({ success: false, message: '添加到购物车失败' }); - } -}); - -/** - * @swagger - * /api/cart/{id}: - * put: - * summary: 更新购物车商品数量 - * tags: [Cart] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 购物车项ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * quantity: - * type: integer - * description: 新的商品数量 - * minimum: 1 - * required: - * - quantity - * responses: - * 200: - * description: 更新购物车成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 400: - * description: 参数错误或库存不足 - * 401: - * description: 未授权 - * 404: - * description: 购物车项不存在 - * 500: - * description: 服务器错误 - */ -router.put('/:id', auth, async (req, res) => { - const db = getDB(); - await db.query('START TRANSACTION'); - - try { - const cartItemId = req.params.id; - const { quantity } = req.body; - const userId = req.user.id; - - // 验证数量 - if (!quantity || quantity < 1) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '商品数量必须大于0' }); - } - - // 检查购物车项是否存在且属于当前用户 - const [cartItems] = await db.execute( - 'SELECT id, product_id, specification_id FROM cart_items WHERE id = ? AND user_id = ?', - [cartItemId, userId] - ); - console.log(cartItems,'cartItems'); - - - if (cartItems.length === 0) { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '购物车项不存在' }); - } - - const cartItem = cartItems[0]; - - // 检查商品库存 - const [products] = await db.execute( - 'SELECT stock, status FROM products WHERE id = ?', - [cartItem.product_id] - ); - - if (products.length === 0 || products[0].status !== 'active') { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '商品不存在或已下架' }); - } - - let availableStock = products[0].stock; - - // 如果有规格,检查规格库存 - if (cartItem.specification_id) { - const [specs] = await db.execute( - 'SELECT stock, status FROM product_spec_combinations WHERE id = ?', - [cartItem.specification_id] - ); - - if (specs.length === 0 || specs[0].status !== 'active') { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '商品规格不存在或已下架' }); - } - - availableStock = specs[0].stock; - } - - // 检查库存是否足够 - if (availableStock < quantity) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '库存不足' }); - } - - // 更新购物车项数量 - await db.execute( - 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', - [quantity, cartItemId] - ); - - await db.query('COMMIT'); - - res.json({ - success: true, - message: '更新购物车成功' - }); - } catch (error) { - await db.query('ROLLBACK'); - console.error('更新购物车失败:', error); - res.status(500).json({ success: false, message: '更新购物车失败' }); - } -}); - -/** - * @swagger - * /api/cart/{id}: - * delete: - * summary: 删除购物车商品 - * tags: [Cart] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 购物车项ID - * responses: - * 200: - * description: 删除购物车商品成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 404: - * description: 购物车项不存在 - * 500: - * description: 服务器错误 - */ -router.delete('/:id', auth, async (req, res) => { - try { - const cartItemId = req.params.id; - const userId = req.user.id; - - // 检查购物车项是否存在且属于当前用户 - const [cartItems] = await getDB().execute( - 'SELECT id FROM cart_items WHERE id = ? AND user_id = ?', - [cartItemId, userId] - ); - - if (cartItems.length === 0) { - return res.status(404).json({ success: false, message: '购物车项不存在' }); - } - - // 删除购物车项 - await getDB().execute( - 'DELETE FROM cart_items WHERE id = ?', - [cartItemId] - ); - - res.json({ - success: true, - message: '删除购物车商品成功' - }); - } catch (error) { - console.error('删除购物车商品失败:', error); - res.status(500).json({ success: false, message: '删除购物车商品失败' }); - } -}); - -/** - * @swagger - * /api/cart/batch: - * delete: - * summary: 批量删除购物车商品 - * tags: [Cart] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * cart_item_ids: - * type: array - * items: - * type: integer - * description: 购物车项ID数组 - * required: - * - cart_item_ids - * responses: - * 200: - * description: 批量删除购物车商品成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * deleted_count: - * type: integer - * description: 删除的商品数量 - * 400: - * description: 参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.delete('/batch', auth, async (req, res) => { - try { - const { cart_item_ids } = req.body; - const userId = req.user.id; - - // 验证参数 - if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) { - return res.status(400).json({ success: false, message: '请选择要删除的商品' }); - } - - // 构建删除条件 - const placeholders = cart_item_ids.map(() => '?').join(','); - const query = `DELETE FROM cart_items WHERE id IN (${placeholders}) AND user_id = ?`; - const params = [...cart_item_ids, userId]; - - const [result] = await getDB().execute(query, params); - - res.json({ - success: true, - message: '批量删除购物车商品成功', - data: { - deleted_count: result.affectedRows - } - }); - } catch (error) { - console.error('批量删除购物车商品失败:', error); - res.status(500).json({ success: false, message: '批量删除购物车商品失败' }); - } -}); - -/** - * @swagger - * /api/cart/clear: - * delete: - * summary: 清空购物车 - * tags: [Cart] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 清空购物车成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.delete('/clear', auth, async (req, res) => { - try { - const userId = req.user.id; - - // 清空用户购物车 - await getDB().execute( - 'DELETE FROM cart_items WHERE user_id = ?', - [userId] - ); - - res.json({ - success: true, - message: '清空购物车成功' - }); - } catch (error) { - console.error('清空购物车失败:', error); - res.status(500).json({ success: false, message: '清空购物车失败' }); - } -}); - -/** - * @swagger - * /api/cart/count: - * get: - * summary: 获取购物车商品数量 - * tags: [Cart] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 获取购物车商品数量成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * count: - * type: integer - * description: 购物车商品总数量 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.get('/count', auth, async (req, res) => { - try { - const userId = req.user.id; - - // 获取购物车商品总数量 - const [result] = await getDB().execute( - 'SELECT SUM(quantity) as count FROM cart_items WHERE user_id = ?', - [userId] - ); - - const count = result[0].count || 0; - - res.json({ - success: true, - data: { count } - }); - } catch (error) { - console.error('获取购物车商品数量失败:', error); - res.status(500).json({ success: false, message: '获取购物车商品数量失败' }); - } -}); - -/** - * @swagger - * /api/cart/checkout: - * post: - * summary: 购物车结账 - * tags: [Cart] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * cart_item_ids: - * type: array - * items: - * type: integer - * description: 要结账的购物车项ID数组 - * shipping_address: - * type: string - * description: 收货地址 - * required: - * - cart_item_ids - * - shipping_address - * responses: - * 201: - * description: 结账成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * order_id: - * type: integer - * order_no: - * type: string - * total_amount: - * type: integer - * total_points: - * type: integer - * total_rongdou: - * type: integer - * 400: - * description: 参数错误或库存不足 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.post('/checkout', auth, async (req, res) => { - const db = getDB(); - await db.query('START TRANSACTION'); - - try { - const { cart_item_ids, shipping_address } = req.body; - const userId = req.user.id; - - // 验证参数 - if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '请选择要结账的商品' }); - } - - if (!shipping_address) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '请填写收货地址' }); - } - - // 获取购物车商品信息 - const placeholders = cart_item_ids.map(() => '?').join(','); - const cartQuery = ` - SELECT - c.id, c.product_id, c.quantity, c.spec_combination_id, - p.name, p.price, p.points_price, p.rongdou_price, p.stock, p.status, - psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock - FROM cart_items c - LEFT JOIN products p ON c.product_id = p.id - LEFT JOIN product_spec_combinations psc ON c.spec_combination_id = psc.id - WHERE c.id IN (${placeholders}) AND c.user_id = ? - `; - - const [cartItems] = await db.execute(cartQuery, [...cart_item_ids, userId]); - - if (cartItems.length === 0) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '购物车商品不存在' }); - } - - // 验证商品状态和库存 - let totalAmount = 0; - let totalPoints = 0; - let totalRongdou = 0; - - for (const item of cartItems) { - if (item.status !== 'active') { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: `商品 ${item.name} 已下架` }); - } - - const availableStock = item.spec_combination_id ? item.spec_stock : item.stock; - if (availableStock < item.quantity) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` }); - } - - const finalPrice = item.price + (item.price_adjustment || 0); - const finalPointsPrice = item.points_price + (item.points_adjustment || 0); - const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); - - totalAmount += finalPrice * item.quantity; - totalPoints += finalPointsPrice * item.quantity; - totalRongdou += finalRongdouPrice * item.quantity; - } - - // 检查用户积分和融豆是否足够 - const [users] = await db.execute( - 'SELECT points, rongdou FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - await db.query('ROLLBACK'); - return res.status(404).json({ success: false, message: '用户不存在' }); - } - - const user = users[0]; - - if (user.points < totalPoints) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '积分不足' }); - } - - if (user.rongdou < totalRongdou) { - await db.query('ROLLBACK'); - return res.status(400).json({ success: false, message: '融豆不足' }); - } - - // 生成订单号 - const orderNo = 'ORD' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase(); - - // 创建订单 - const [orderResult] = await db.execute( - `INSERT INTO orders (order_no, user_id, total_amount, total_points, total_rongdou, - status, shipping_address, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, 'pending', ?, NOW(), NOW())`, - [orderNo, userId, totalAmount, totalPoints, totalRongdou, shipping_address] - ); - - const orderId = orderResult.insertId; - - // 创建订单项 - for (const item of cartItems) { - const finalPrice = item.price + (item.price_adjustment || 0); - const finalPointsPrice = item.points_price + (item.points_adjustment || 0); - const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); - - await db.execute( - `INSERT INTO order_items (order_id, product_id, spec_combination_id, quantity, - price, points_price, rongdou_price, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`, - [orderId, item.product_id, item.spec_combination_id, item.quantity, - finalPrice, finalPointsPrice, finalRongdouPrice] - ); - - // 更新库存 - if (item.spec_combination_id) { - await db.execute( - 'UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?', - [item.quantity, item.spec_combination_id] - ); - } else { - await db.execute( - 'UPDATE products SET stock = stock - ? WHERE id = ?', - [item.quantity, item.product_id] - ); - } - } - - // 扣除用户积分和融豆 - await db.execute( - 'UPDATE users SET points = points - ?, rongdou = rongdou - ? WHERE id = ?', - [totalPoints, totalRongdou, userId] - ); - - // 删除已结账的购物车项 - const deletePlaceholders = cart_item_ids.map(() => '?').join(','); - await db.execute( - `DELETE FROM cart_items WHERE id IN (${deletePlaceholders}) AND user_id = ?`, - [...cart_item_ids, userId] - ); - - await db.query('COMMIT'); - - res.status(201).json({ - success: true, - message: '结账成功', - data: { - order_id: orderId, - order_no: orderNo, - total_amount: totalAmount, - total_points: totalPoints, - total_rongdou: totalRongdou - } - }); - - } catch (error) { - await db.query('ROLLBACK'); - console.error('购物车结账失败:', error); - res.status(500).json({ success: false, message: '结账失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/matching.js b/routes/matching.js deleted file mode 100644 index d9e1db5..0000000 --- a/routes/matching.js +++ /dev/null @@ -1,626 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../database'); -const matchingService = require('../services/matchingService'); -const { auth } = require('../middleware/auth'); -const { default: axios } = require('axios'); - - -router.post('/create', auth, async (req, res) => { - try { - console.log('匹配订单创建请求 - 用户ID:', req.user.id); - console.log('请求体:', req.body); - const userId = req.user.id; - const { matchingType = 'small', customAmount } = req.body; - const [user_type] = await getDB().query(`SELECT count(*) as total FROM users WHERE id=${userId} and user_type='directly_operated'`); - if(user_type[0].total > 0){ - return res.status(400).json({message: '平台暂不支持直营用户获得融豆'}) - } - // 验证匹配类型 - if (!['small', 'large'].includes(matchingType)) { - return res.status(400).json({ message: '无效的匹配类型' }); - } - - // 验证大额匹配的金额 - if (matchingType === 'large') { - if (!customAmount || typeof customAmount !== 'number') { - return res.status(400).json({ message: '大额匹配需要指定金额' }); - } - if (customAmount < 3000 || customAmount > 50000) { - return res.status(400).json({ message: '大额匹配金额必须在3000-50000之间' }); - } - } - - // 检查用户是否有未完成的匹配订单(排除已失败的订单) - const [existingOrders] = await getDB().execute( - 'SELECT COUNT(*) as count FROM matching_orders WHERE initiator_id = ? AND status IN ("pending", "matching")', - [userId] - ); - - if (existingOrders[0].count > 0) { - return res.status(400).json({ message: '您有未完成的匹配订单,请等待完成后再创建新订单' }); - } - - // 校验用户是否已上传必要的证件和收款码 - const [userInfo] = await getDB().execute( - 'SELECT business_license, id_card_front, id_card_back, wechat_qr, alipay_qr, bank_card, unionpay_qr FROM users WHERE id = ?', - [userId] - ); - - if (userInfo.length === 0) { - return res.status(404).json({ message: '用户不存在' }); - } - - const user = userInfo[0]; - - // 检查证件是否已上传 - if (!user.business_license || !user.id_card_front || !user.id_card_back) { - return res.status(400).json({ - message: '开始匹配前,请先在个人中心上传营业执照和身份证正反面', - code: 'MISSING_DOCUMENTS' - }); - } - - // 检查收款码是否已上传(至少需要一种收款方式) - if (!user.wechat_qr && !user.alipay_qr && !user.bank_card && !user.unionpay_qr) { - return res.status(400).json({ - message: '开始匹配前,请先在个人中心设置至少一种收款方式(微信、支付宝、银行卡或云闪付)', - code: 'MISSING_PAYMENT_METHODS' - }); - } - - // 创建匹配订单 - const result = await matchingService.createMatchingOrder(userId, matchingType, customAmount); - - const message = matchingType === 'small' - ? '小额匹配成功!已为您生成3笔转账分配' - : `大额匹配成功!已为您生成${result.totalAmount}笔转账分配`; - - res.json({ - success: true, - message, - data: { - matchingOrderId: result.orderId, - amounts: result.amounts, - matchingType: result.matchingType, - totalAmount: result.totalAmount - } - }); - - } catch (error) { - console.error('创建匹配订单失败:', error); - res.status(500).json({ message: error.message || '匹配失败,请稍后重试' }); - } -}); - - -router.get('/my-orders', auth, async (req, res) => { - try { - const userId = req.user.id; - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 10; - - const orders = await matchingService.getUserMatchingOrders(userId, page, limit); - - res.json({ - success: true, - data: orders - }); - - } catch (error) { - console.error('获取匹配订单失败:', error); - res.status(500).json({ message: '获取匹配订单失败' }); - } -}); - -/** - * @swagger - * /api/matching/pending-allocations: - * get: - * summary: 获取用户待处理的分配 - * tags: [Matching] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取待处理分配 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/Allocation' - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.get('/pending-allocations', auth, async (req, res) => { - try { - const userId = req.user.id; - - const allocations = await matchingService.getUserPendingAllocations(userId); - - res.json({ - success: true, - data: allocations - }); - - } catch (error) { - console.error('获取待处理分配失败:', error); - res.status(500).json({ message: '获取待处理分配失败' }); - } -}); - -/** - * @swagger - * /api/matching/allocation/{id}: - * get: - * summary: 获取分配详情 - * tags: [Matching] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 分配ID - * responses: - * 200: - * description: 成功获取分配详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * $ref: '#/components/schemas/Allocation' - * 401: - * description: 未授权 - * 403: - * description: 无权限访问 - * 404: - * description: 分配不存在 - * 500: - * description: 服务器错误 - */ -router.get('/allocation/:id', auth, async (req, res) => { - try { - const db = getDB(); - const allocationId = req.params.id; - const userId = req.user.id; - - // 首先获取分配信息 - const [allocations] = await db.execute(` - SELECT - oa.id, - oa.from_user_id, - oa.to_user_id, - oa.cycle_number, - oa.amount, - oa.status, - oa.created_at, - from_user.username as from_user_name, - to_user.username as to_user_name - FROM transfers oa - JOIN users from_user ON oa.from_user_id = from_user.id - JOIN users to_user ON oa.to_user_id = to_user.id - WHERE oa.id = ? - `, [allocationId]); - - if (allocations.length === 0) { - return res.status(404).json({ success: false, message: '分配不存在' }); - } - - const allocation = allocations[0]; - - // 检查权限:只有分配的发起人或接收人可以查看 - if (allocation.from_user_id !== userId && allocation.to_user_id !== userId) { - return res.status(403).json({ success: false, message: '无权限访问此分配' }); - } - - res.json({ - success: true, - data: allocation - }); - } catch (error) { - console.error('获取分配详情错误:', error); - res.status(500).json({ success: false, message: '获取分配详情失败' }); - } -}); - -/** - * @swagger - * /api/matching/confirm-allocation/{allocationId}: - * post: - * summary: 确认分配(创建转账) - * tags: [Matching] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: allocationId - * required: true - * schema: - * type: integer - * description: 分配ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * transferAmount: - * type: number - * description: 转账金额 - * description: - * type: string - * description: 转账描述 - * voucher: - * type: string - * description: 转账凭证(图片URL) - * required: - * - voucher - * responses: - * 200: - * description: 转账凭证提交成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * transferId: - * type: integer - * 400: - * description: 参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.post('/confirm-allocation/:allocationId', auth, async (req, res) => { - try { - const { allocationId } = req.params; - const userId = req.user.id; - const { transferAmount, description, voucher } = req.body; // 获取转账信息 - - // 校验转账凭证是否存在 - if (!voucher) { - return res.status(400).json({ - success: false, - message: '请上传转账凭证' - }); - } - - // 调用服务层方法,传递完整的转账信息 - const transferId = await matchingService.confirmAllocation( - allocationId, - userId, - transferAmount, - description, - voucher - ); - - res.json({ - success: true, - message: '转账凭证已提交,转账记录已创建', - data: { transferId } - }); - - axios.post('http://localhost:5000/ocr',{ - id: allocationId - }).then(res => { - console.log(res.data) - }) - - } catch (error) { - console.error('确认分配失败:', error); - res.status(500).json({ message: error.message || '确认分配失败' }); - } -}); - -/** - * @swagger - * /api/matching/reject-allocation/{allocationId}: - * post: - * summary: 拒绝分配 - * tags: [Matching] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: allocationId - * required: true - * schema: - * type: integer - * description: 分配ID - * requestBody: - * content: - * application/json: - * schema: - * type: object - * properties: - * reason: - * type: string - * description: 拒绝原因 - * responses: - * 200: - * description: 拒绝分配成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 404: - * description: 分配不存在或无权限 - * 500: - * description: 服务器错误 - */ -router.post('/reject-allocation/:allocationId', auth, async (req, res) => { - try { - const { allocationId } = req.params; - const userId = req.user.id; - const { reason } = req.body; - - const db = getDB(); - - // 获取分配信息 - const [allocations] = await db.execute( - 'SELECT * FROM transfers WHERE id = ? AND from_user_id = ?', - [allocationId, userId] - ); - - if (allocations.length === 0) { - return res.status(404).json({ message: '分配不存在或无权限' }); - } - - const allocation = allocations[0]; - - // 更新分配状态 - await db.execute( - 'UPDATE transfers SET status = "rejected" WHERE id = ?', - [allocationId] - ); - - // 记录拒绝动作 - await db.execute( - 'INSERT INTO matching_records (matching_order_id, user_id, action, note) VALUES (?, ?, "reject", ?)', - [allocation.matching_order_id, userId, reason || '用户拒绝'] - ); - - // 检查订单状态是否需要更新 - const statusResult = await matchingService.checkOrderStatusAfterRejection( - allocation.matching_order_id, - allocation.cycle_number - ); - - let message = '已拒绝分配'; - if (statusResult === 'failed') { - message = '已拒绝分配,该轮次所有分配均被拒绝,匹配订单已失败'; - } - - res.json({ - success: true, - message - }); - - } catch (error) { - console.error('拒绝分配失败:', error); - res.status(500).json({ message: '拒绝分配失败' }); - } -}); - -/** - * @swagger - * /api/matching/order/{orderId}: - * get: - * summary: 获取匹配订单详情 - * tags: [Matching] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: orderId - * required: true - * schema: - * type: integer - * description: 订单ID - * responses: - * 200: - * description: 成功获取订单详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * order: - * $ref: '#/components/schemas/MatchingOrder' - * allocations: - * type: array - * items: - * $ref: '#/components/schemas/Allocation' - * records: - * type: array - * items: - * type: object - * 401: - * description: 未授权 - * 403: - * description: 无权限查看 - * 404: - * description: 订单不存在 - * 500: - * description: 服务器错误 - */ -router.get('/order/:orderId', auth, async (req, res) => { - try { - const { orderId } = req.params; - const userId = req.user.id; - - const db = getDB(); - - // 获取订单基本信息 - const [orders] = await db.execute( - `SELECT mo.*, u.username as initiator_name,u.real_name as initiator_real_name - FROM matching_orders mo - JOIN users u ON mo.initiator_id = u.id - WHERE mo.id = ?`, - [orderId] - ); - - if (orders.length === 0) { - return res.status(404).json({ message: '匹配订单不存在' }); - } - - const order = orders[0]; - - // 检查权限(订单发起人或参与者) - const [userCheck] = await db.execute( - `SELECT COUNT(*) as count FROM ( - SELECT initiator_id as user_id FROM matching_orders WHERE id = ? - UNION - SELECT from_user_id as user_id FROM transfers WHERE id = ? - ) as participants WHERE user_id = ?`, - [orderId, orderId, userId] - ); - - if (userCheck[0].count === 0) { - return res.status(403).json({ message: '无权限查看此订单' }); - } - - // 获取分配信息 - const [allocations] = await db.execute( - `SELECT oa.*, - uf.username as from_user_name, - ut.username as to_user_name - FROM transfers oa - JOIN users uf ON oa.from_user_id = uf.id - JOIN users ut ON oa.to_user_id = ut.id - WHERE oa.id = ? - ORDER BY oa.cycle_number, oa.created_at`, - [orderId] - ); - - // 获取匹配记录 - const [records] = await db.execute( - `SELECT mr.*, u.username - FROM matching_records mr - JOIN users u ON mr.user_id = u.id - WHERE mr.matching_order_id = ? - ORDER BY mr.created_at`, - [orderId] - ); - - res.json({ - success: true, - data: { - order, - allocations, - records - } - }); - - } catch (error) { - console.error('获取匹配订单详情失败:', error); - res.status(500).json({ message: '获取匹配订单详情失败' }); - } -}); - -/** - * @swagger - * /api/matching/stats: - * get: - * summary: 获取匹配统计信息 - * tags: [Matching] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取统计信息 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * userStats: - * type: object - * properties: - * initiated_orders: - * type: integer - * participated_allocations: - * type: integer - * total_initiated_amount: - * type: number - * total_participated_amount: - * type: number - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.get('/stats', auth, async (req, res) => { - try { - const userId = req.user.id; - const db = getDB(); - - // 获取用户统计 - const [userStats] = await db.execute( - `SELECT - COUNT(CASE WHEN mo.initiator_id = ? THEN 1 END) as initiated_orders, - COUNT(CASE WHEN oa.from_user_id = ? THEN 1 END) as participated_allocations, - SUM(CASE WHEN mo.initiator_id = ? AND mo.status = 'completed' THEN mo.amount ELSE 0 END) as total_initiated_amount, - SUM(CASE WHEN oa.from_user_id = ? AND oa.status = 'completed' THEN oa.amount ELSE 0 END) as total_participated_amount - FROM matching_orders mo - LEFT JOIN transfers oa ON mo.id = oa.id`, - [userId, userId, userId, userId] - ); - - res.json({ - success: true, - data: { - userStats: userStats[0] - } - }); - - } catch (error) { - console.error('获取匹配统计失败:', error); - res.status(500).json({ message: '获取匹配统计失败' }); - } -}); - - - -module.exports = router; \ No newline at end of file diff --git a/routes/matchingAdmin.js b/routes/matchingAdmin.js deleted file mode 100644 index cd47b51..0000000 --- a/routes/matchingAdmin.js +++ /dev/null @@ -1,621 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../database'); -const { auth, adminAuth } = require('../middleware/auth'); -const logger = require('../config/logger'); -const matchingService = require('../services/matchingService'); -const dayjs = require('dayjs'); - -/** - * @swagger - * tags: - * name: MatchingAdmin - * description: 匹配订单管理员相关接口 - */ - -/** - * @swagger - * components: - * schemas: - * UnreasonableMatch: - * type: object - * properties: - * allocation_id: - * type: integer - * description: 分配ID - * from_user_id: - * type: integer - * description: 发送方用户ID - * to_user_id: - * type: integer - * description: 接收方用户ID - * amount: - * type: number - * description: 分配金额 - * status: - * type: string - * enum: [pending, confirmed, rejected, cancelled] - * description: 分配状态 - * to_username: - * type: string - * description: 接收方用户名 - * to_user_balance: - * type: number - * description: 接收方用户余额 - * from_username: - * type: string - * description: 发送方用户名 - * from_user_balance: - * type: number - * description: 发送方用户余额 - */ - -/** - * @swagger - * /api/matching-admin/unreasonable-matches: - * get: - * summary: 获取不合理的匹配记录(正余额用户被匹配的情况) - * tags: [MatchingAdmin] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 20 - * description: 每页数量 - * responses: - * 200: - * description: 成功获取不合理匹配记录 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * matches: - * type: array - * items: - * $ref: '#/components/schemas/UnreasonableMatch' - * pagination: - * type: object - * properties: - * page: - * type: integer - * limit: - * type: integer - * total: - * type: integer - * totalPages: - * type: integer - * 401: - * description: 未授权 - * 403: - * description: 无管理员权限 - * 500: - * description: 服务器错误 - */ -router.get('/unreasonable-matches', auth, adminAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 20; - const offset = (page - 1) * limit; - - // 查找正余额用户被匹配的情况 - const query = `SELECT - oa.id as allocation_id, - oa.from_user_id, - oa.to_user_id, - oa.amount, - oa.status, - oa.outbound_date, - oa.created_at, - u_to.username as to_username, - u_to.balance as to_user_balance, - u_from.username as from_username, - u_from.balance as from_user_balance, - mo.amount as total_order_amount - FROM transfers oa - JOIN users u_to ON oa.to_user_id = u_to.id - JOIN users u_from ON oa.from_user_id = u_from.id - JOIN matching_orders mo ON oa.id = mo.id - WHERE oa.source_type = 'allocation' - AND u_to.balance > 0 - AND u_to.is_system_account = FALSE - AND oa.status IN ('pending', 'confirmed') - ORDER BY oa.created_at DESC - LIMIT ${offset}, ${limit}`; - - const countQuery = `SELECT COUNT(*) as total - FROM transfers oa - JOIN users u_to ON oa.to_user_id = u_to.id - WHERE oa.source_type = 'allocation' - AND u_to.balance > 0 - AND u_to.is_system_account = FALSE - AND oa.status IN ('pending', 'confirmed')`; - - const unreasonableMatches = await db.executeQuery(query); - - // 获取总数 - const countResult = await db.executeQuery(countQuery); - - const total = countResult[0].total; - - res.json({ - success: true, - data: { - matches: unreasonableMatches, - pagination: { - page, - limit, - total, - totalPages: Math.ceil(total / limit) - } - } - }); - - } catch (error) { - console.error('获取不合理匹配记录失败:', error); - res.status(500).json({ message: '获取不合理匹配记录失败' }); - } -}); - - -/** - * @swagger - * /api/matching-admin/matching-stats: - * get: - * summary: 获取匹配统计信息 - * tags: [MatchingAdmin] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取匹配统计信息 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * currentStats: - * type: object - * properties: - * unreasonable_matches: - * type: integer - * reasonable_matches: - * type: integer - * system_matches: - * type: integer - * unreasonable_amount: - * type: number - * reasonable_amount: - * type: number - * yesterdayStats: - * type: object - * properties: - * total_outbound: - * type: number - * unique_amounts: - * type: integer - * 401: - * description: 未授权 - * 403: - * description: 无管理员权限 - * 500: - * description: 服务器错误 - */ -router.get('/matching-stats', auth, adminAuth, async (req, res) => { - try { - - // 获取各种统计数据 - const stats = await db.executeQuery( - `SELECT - COUNT(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as unreasonable_matches, - COUNT(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as reasonable_matches, - COUNT(CASE WHEN u_to.is_system_account = TRUE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as system_matches, - SUM(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as unreasonable_amount, - SUM(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as reasonable_amount - FROM transfers oa - JOIN users u_to ON oa.to_user_id = u_to.id - WHERE oa.source_type = 'allocation'` - ); - - // 获取昨天的匹配验证统计 - const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); - - const yesterdayStats = await db.executeQuery( - `SELECT - SUM(oa.amount) as total_outbound, - COUNT(DISTINCT oa.amount) as unique_amounts - FROM transfers oa - JOIN users u ON oa.from_user_id = u.id - WHERE oa.source_type = 'allocation' AND DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`, - [yesterdayStr] - ); - - res.json({ - success: true, - data: { - currentStats: stats[0], - yesterdayStats: yesterdayStats[0] - } - }); - - } catch (error) { - console.error('获取匹配统计失败:', error); - res.status(500).json({ message: '获取匹配统计失败' }); - } -}); - -/** - * @swagger - * /api/matching-admin/fix-all-unreasonable: - * post: - * summary: 批量修复所有不合理匹配 - * tags: [MatchingAdmin] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 批量修复完成 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * data: - * type: object - * properties: - * fixedCount: - * type: integer - * description: 成功修复的记录数 - * errorCount: - * type: integer - * description: 修复失败的记录数 - * errors: - * type: array - * items: - * type: string - * description: 错误信息列表(最多10条) - * 401: - * description: 未授权 - * 403: - * description: 无管理员权限 - * 500: - * description: 服务器错误 - */ -router.post('/fix-all-unreasonable', auth, adminAuth, async (req, res) => { - try { - let fixedCount = 0; - let errorCount = 0; - const errors = []; - - // 获取所有不合理的匹配记录 - const unreasonableMatches = await db.executeQuery( - `SELECT oa.id, oa.from_user_id, oa.to_user_id, oa.amount, u_to.username, u_to.balance - FROM transfers oa - JOIN users u_to ON oa.to_user_id = u_to.id - WHERE oa.source_type = 'allocation' - AND u_to.balance > 0 - AND u_to.is_system_account = FALSE - AND oa.status IN ('pending', 'confirmed') - ORDER BY u_to.balance DESC` - ); - - for (const match of unreasonableMatches) { - const connection = await db.getDB().getConnection(); - try { - await connection.query('START TRANSACTION'); - - // 尝试重新分配给负余额用户 - const usedTargetUsers = new Set([match.to_user_id]); - const newTargetUser = await matchingService.getMatchingTargetExcluding(match.from_user_id, usedTargetUsers); - - // 获取当前时间 - const currentTime = new Date(); - - if (newTargetUser) { - // 更新分配目标 - await connection.execute( - 'UPDATE transfers SET to_user_id = ?, updated_at = ? WHERE id = ?', - [newTargetUser, currentTime, match.id] - ); - - // 记录操作日志 - await connection.execute( - 'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)', - [req.user.id, match.id, `批量修复:从正余额用户${match.username}(余额${match.balance}元)重新分配${match.amount}元给负余额用户`, currentTime] - ); - - fixedCount++; - } else { - // 如果没有可用的负余额用户,取消分配 - await connection.execute( - 'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?', - [currentTime, match.id] - ); - - await connection.execute( - 'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)', - [req.user.id, match.id, `批量修复:取消正余额用户${match.username}(余额${match.balance}元)的${match.amount}元分配`, currentTime] - ); - - fixedCount++; - } - - await connection.query('COMMIT'); - connection.release(); - - } catch (error) { - await connection.query('ROLLBACK'); - connection.release(); - errorCount++; - errors.push(`分配ID ${match.id}: ${error.message}`); - console.error(`修复分配${match.id}失败:`, error); - } - } - - res.json({ - success: true, - message: `批量修复完成:成功修复${fixedCount}条记录,失败${errorCount}条记录`, - data: { - fixedCount, - errorCount, - errors: errors.slice(0, 10) // 只返回前10个错误 - } - }); - - } catch (error) { - console.error('批量修复不合理匹配失败:', error); - res.status(500).json({ message: '批量修复不合理匹配失败' }); - } -}); - -/** - * @swagger - * /api/matching-admin/confirm-allocation/{allocationId}: - * post: - * summary: 管理员确认分配 - * tags: [MatchingAdmin] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: allocationId - * required: true - * schema: - * type: integer - * description: 分配ID - * responses: - * 200: - * description: 分配确认成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 403: - * description: 无管理员权限 - * 404: - * description: 分配不存在或状态不是待处理 - * 500: - * description: 服务器错误 - */ -router.post('/confirm-allocation/:allocationId', auth, adminAuth, async (req, res) => { - try { - const { allocationId } = req.params; - const adminId = req.user.id; - - const connection = await db.getDB().getConnection(); - - try { - await connection.query('START TRANSACTION'); - - // 检查分配是否存在且状态为pending - const [allocations] = await connection.execute( - `SELECT oa.*, u_from.username as from_username, u_to.username as to_username - FROM transfers oa - JOIN users u_from ON oa.from_user_id = u_from.id - JOIN users u_to ON oa.to_user_id = u_to.id - WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`, - [allocationId] - ); - - if (allocations.length === 0) { - await connection.query('ROLLBACK'); - connection.release(); - return res.status(404).json({ message: '分配不存在或状态不是待处理' }); - } - - const allocation = allocations[0]; - - // 获取当前时间 - const currentTime = new Date(); - - // 计算3小时后的截止时间 - const deadline = new Date(); - deadline.setHours(deadline.getHours() + 3); - - // 创建转账记录,直接设置为confirmed状态 - const transferDescription = `匹配订单 ${allocation.matching_order_id} 第 ${allocation.cycle_number} 轮转账(管理员确认)`; - const [transferResult] = await connection.execute( - `INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, status, description, deadline_at, confirmed_at, source_type) VALUES (?, ?, ?, "user_to_user", "confirmed", ?, ?, ?, 'allocation')`, - [ - allocation.from_user_id, - allocation.to_user_id, - allocation.amount, - transferDescription, - deadline, - currentTime - ] - ); - - // 更新分配状态为已确认,并关联转账记录 - await connection.execute( - 'UPDATE transfers SET status = "confirmed", transfer_id = ?, confirmed_at = ?, updated_at = ? WHERE id = ?', - [transferResult.insertId, currentTime, currentTime, allocationId] - ); - - // 记录管理员操作日志 - await connection.execute( - 'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "confirm_allocation", "allocation", ?, ?, ?)', - [adminId, allocationId, `管理员确认分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}元`, currentTime] - ); - - // 记录确认动作到匹配记录 - await connection.execute( - 'INSERT INTO matching_records (matching_order_id, user_id, action, amount, note) VALUES (?, ?, "confirm", ?, ?)', - [ - allocation.matching_order_id, - adminId, - allocation.amount, - '管理员确认分配' - ] - ); - - await connection.query('COMMIT'); - connection.release(); - - res.json({ - success: true, - message: '分配已确认' - }); - - } catch (innerError) { - await connection.query('ROLLBACK'); - connection.release(); - throw innerError; - } - - } catch (error) { - console.error('确认分配失败:', error); - res.status(500).json({ message: error.message || '确认分配失败' }); - } - }); - - /** - * @swagger - * /api/matching-admin/cancel-allocation/{allocationId}: - * post: - * summary: 管理员取消分配 - * tags: [MatchingAdmin] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: allocationId - * required: true - * schema: - * type: integer - * description: 分配ID - * responses: - * 200: - * description: 分配取消成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 401: - * description: 未授权 - * 403: - * description: 无管理员权限 - * 404: - * description: 分配不存在或状态不是待处理 - * 500: - * description: 服务器错误 - */ -router.post('/cancel-allocation/:allocationId', auth, adminAuth, async (req, res) => { - try { - const { allocationId } = req.params; - const adminId = req.user.id; - - const connection = await db.getDB().getConnection(); - - try { - await connection.query('START TRANSACTION'); - - // 检查分配是否存在且状态为pending - const [allocations] = await connection.execute( - `SELECT oa.*, u_from.username as from_username, u_to.username as to_username - FROM transfers oa - JOIN users u_from ON oa.from_user_id = u_from.id - JOIN users u_to ON oa.to_user_id = u_to.id - WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`, - [allocationId] - ); - - if (allocations.length === 0) { - await connection.query('ROLLBACK'); - connection.release(); - return res.status(404).json({ message: '分配不存在或状态不是待处理' }); - } - - const allocation = allocations[0]; - - // 获取当前时间 - const currentTime = new Date(); - - // 更新分配状态为已取消 - await connection.execute( - 'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?', - [currentTime, allocationId] - ); - - // 记录管理员操作日志 - await connection.execute( - 'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "cancel_allocation", "allocation", ?, ?, ?)', - [adminId, allocationId, `管理员取消分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}元`, currentTime] - ); - - await connection.query('COMMIT'); - connection.release(); - - res.json({ - success: true, - message: '分配已取消' - }); - - } catch (innerError) { - await connection.query('ROLLBACK'); - connection.release(); - throw innerError; - } - - } catch (error) { - console.error('取消分配失败:', error); - res.status(500).json({ message: error.message || '取消分配失败' }); - } - }); - - module.exports = router; \ No newline at end of file diff --git a/routes/payment.js b/routes/payment.js deleted file mode 100644 index 72a82bd..0000000 --- a/routes/payment.js +++ /dev/null @@ -1,400 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const WechatPayService = require('../services/wechatPayService'); -const AlipayService = require('../services/alipayservice'); -const { getDB } = require('../database'); -const { auth, paymentAuth } = require('../middleware/auth'); - -// 创建支付服务实例 -const wechatPayService = new WechatPayService(); -const alipayService = new AlipayService(); - -/** - * 获取支持的支付方式 - * GET /api/payment/methods - */ -router.get('/methods', (req, res) => { - res.json({ - success: true, - data: { - methods: [ - { - code: 'wechat_h5', - name: '微信支付', - description: '微信H5支付', - icon: 'wechat', - enabled: true - }, - { - code: 'alipay_wap', - name: '支付宝支付', - description: '支付宝手机网站支付', - icon: 'alipay', - enabled: true - } - ] - } - }); -}); - -/** - * 创建统一支付订单 - * POST /api/payment/create-order - */ -router.post('/create-order', paymentAuth, async (req, res) => { - try { - const { paymentMethod } = req.body; - const userId = req.user.id; - const username = req.user.username; - const phone = req.user.phone; - - // 验证支付方式 - if (!paymentMethod || !['wechat_h5', 'alipay_wap'].includes(paymentMethod)) { - return res.status(400).json({ - success: false, - message: '不支持的支付方式' - }); - } - - // 检查用户是否已经支付过 - const db = getDB(); - const [existingOrders] = await db.execute( - 'SELECT id FROM payment_orders WHERE user_id = ? AND status = "paid"', - [userId] - ); - - if (existingOrders.length > 0) { - return res.status(400).json({ - success: false, - message: '用户已完成支付,无需重复支付' - }); - } - - let result; - - // 获取客户端IP - const clientIp = req.headers['x-forwarded-for'] || - req.headers['x-real-ip'] || - req.connection.remoteAddress || - req.socket.remoteAddress || - (req.connection.socket ? req.connection.socket.remoteAddress : null) || - '127.0.0.1'; - - // 根据支付方式创建订单 - if (paymentMethod === 'wechat_h5') { - // 创建微信支付订单 - result = await wechatPayService.createRegistrationPayOrder({ - userId, - username, - phone, - clientIp - }); - } else if (paymentMethod === 'alipay_wap') { - // 创建支付宝支付订单 - result = await alipayService.createRegistrationPayOrder({ - userId, - username, - phone, - clientIp - }); - } - - if (result && result.success) { - res.json({ - success: true, - data: { - outTradeNo: result.data.outTradeNo, - payUrl: result.data.h5Url || result.data.payUrl, - paymentType: result.data.paymentType, - paymentMethod - } - }); - } else { - res.status(500).json({ - success: false, - message: '创建支付订单失败' - }); - } - } catch (error) { - console.error('创建统一支付订单异常:', error); - res.status(500).json({ - success: false, - message: error.message || '服务器内部错误' - }); - } -}); - -/** - * 查询支付状态 - * GET /api/payment/query-status/:outTradeNo - */ -router.get('/query-status/:outTradeNo', paymentAuth, async (req, res) => { - try { - const { outTradeNo } = req.params; - const userId = req.user.id; - - // 验证订单是否属于当前用户 - const db = getDB(); - const [orders] = await db.execute( - 'SELECT id FROM payment_orders WHERE out_trade_no = ? AND user_id = ?', - [outTradeNo, userId] - ); - - if (orders.length === 0) { - return res.status(404).json({ - success: false, - message: '订单不存在或无权限访问' - }); - } - - // 获取订单详细信息,包括trade_type - const [orderDetails] = await db.execute( - 'SELECT id, trade_type FROM payment_orders WHERE id = ?', - [orders[0].id] - ); - - if (orderDetails.length === 0) { - return res.status(404).json({ - success: false, - message: '订单详情不存在' - }); - } - - let result; - const tradeType = orderDetails[0].trade_type; - - // 根据交易类型查询支付状态 - if (tradeType === 'WECHAT_H5') { - // 查询微信支付状态 - result = await wechatPayService.queryPaymentStatus(outTradeNo); - } else if (tradeType === 'ALIPAY_WAP') { - // 查询支付宝支付状态 - result = await alipayService.queryPaymentStatus(outTradeNo); - } else { - return res.status(400).json({ - success: false, - message: '不支持的支付方式' - }); - } - - res.json(result); - } catch (error) { - console.error('查询支付状态失败:', error); - res.status(500).json({ - success: false, - message: error.message || '查询支付状态失败' - }); - } -}); - -/** - * 获取用户支付记录 - * GET /api/payment/orders - */ -router.get('/orders', paymentAuth, async (req, res) => { - try { - const userId = req.user.id; - const { page = 1, limit = 10, status } = req.query; - - const offset = (page - 1) * limit; - const db = getDB(); - - let whereClause = 'WHERE user_id = ?'; - let params = [userId]; - - if (status) { - whereClause += ' AND status = ?'; - params.push(status); - } - - // 查询订单列表 - const [orders] = await db.execute( - `SELECT id, out_trade_no, transaction_id, total_fee, body, trade_type, - status, paid_at, created_at - FROM payment_orders - ${whereClause} - ORDER BY created_at DESC - LIMIT ? OFFSET ?`, - [...params, parseInt(limit), parseInt(offset)] - ); - - // 查询总数 - const [countResult] = await db.execute( - `SELECT COUNT(*) as total FROM payment_orders ${whereClause}`, - params - ); - - const total = countResult[0].total; - - res.json({ - success: true, - data: { - orders: orders.map(order => ({ - ...order, - total_fee: order.total_fee / 100, // 转换为元 - payment_method_name: order.trade_type && order.trade_type.startsWith('ALIPAY') ? '支付宝支付' : '微信支付' - })), - pagination: { - page: parseInt(page), - limit: parseInt(limit), - total, - pages: Math.ceil(total / limit) - } - } - }); - } catch (error) { - console.error('获取支付记录失败:', error); - res.status(500).json({ - success: false, - message: '获取支付记录失败' - }); - } -}); - -/** - * 检查用户支付状态 - * GET /api/payment/check-status - */ -router.get('/check-status', auth, async (req, res) => { - try { - const userId = req.user.id; - const db = getDB(); - - // 查询用户支付状态 - const [users] = await db.execute( - 'SELECT payment_status FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({ - success: false, - message: '用户不存在' - }); - } - - const paymentStatus = users[0].payment_status; - - // 查询最近的支付订单 - const [recentOrders] = await db.execute( - `SELECT out_trade_no, trade_type, status, total_fee, paid_at - FROM payment_orders - WHERE user_id = ? - ORDER BY created_at DESC - LIMIT 1`, - [userId] - ); - - res.json({ - success: true, - data: { - paymentStatus, - isPaid: paymentStatus === 'paid', - recentOrder: recentOrders.length > 0 ? { - ...recentOrders[0], - total_fee: recentOrders[0].total_fee / 100, - payment_method_name: recentOrders[0].trade_type.startsWith('ALIPAY') ? '支付宝支付' : '微信支付' - } : null - } - }); - } catch (error) { - console.error('检查用户支付状态失败:', error); - res.status(500).json({ - success: false, - message: '检查支付状态失败' - }); - } -}); - -/** - * 支付宝支付回调通知 - * POST /api/payment/alipay/notify - */ -router.post('/alipay/notify', async (req, res) => { - try { - console.log('收到支付宝支付回调:', req.body); - - // 验证签名 - const isValid = alipayService.verifyNotifySign(req.body); - if (!isValid) { - console.error('支付宝回调签名验证失败'); - return res.status(400).send('FAIL'); - } - - const { - out_trade_no: outTradeNo, - trade_no: transactionId, - trade_status: tradeStatus, - total_amount: totalAmount - } = req.body; - - // 只处理支付成功的回调 - if (tradeStatus === 'TRADE_SUCCESS') { - const db = getDB(); - - // 检查订单是否存在 - const [orders] = await db.execute( - 'SELECT id, user_id, status FROM payment_orders WHERE out_trade_no = ?', - [outTradeNo] - ); - - if (orders.length === 0) { - console.error('支付宝回调:订单不存在', outTradeNo); - return res.status(400).send('FAIL'); - } - - const order = orders[0]; - - // 如果订单已经处理过,直接返回成功 - if (order.status === 'paid') { - console.log('支付宝回调:订单已处理', outTradeNo); - return res.send('SUCCESS'); - } - - // 更新订单状态 - await alipayService.updatePaymentStatus(outTradeNo, { - status: 'paid', - transactionId, - paidAt: new Date() - }); - - console.log('支付宝支付成功处理完成:', { - outTradeNo, - transactionId, - userId: order.user_id - }); - } - - res.send('SUCCESS'); - } catch (error) { - console.error('处理支付宝支付回调失败:', error); - res.status(500).send('FAIL'); - } -}); - -/** - * 支付宝支付返回页面处理 - * GET /api/payment/alipay/return - */ -router.get('/alipay/return', async (req, res) => { - try { - console.log('支付宝支付返回:', req.query); - - // 验证签名 - const isValid = alipayService.verifyNotifySign(req.query); - if (!isValid) { - console.error('支付宝返回签名验证失败'); - return res.redirect('/payment/failed'); - } - - const { out_trade_no: outTradeNo } = req.query; - - // 重定向到支付成功页面 - res.redirect(`/payment/success?outTradeNo=${outTradeNo}`); - } catch (error) { - console.error('处理支付宝支付返回失败:', error); - res.redirect('/payment/failed'); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/points.js b/routes/points.js deleted file mode 100644 index 99315d3..0000000 --- a/routes/points.js +++ /dev/null @@ -1,697 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { getDB } = require('../database'); -const { auth, adminAuth } = require('../middleware/auth'); - -/** - * @swagger - * tags: - * name: Points - * description: 积分管理相关接口 - */ - -/** - * @swagger - * components: - * schemas: - * PointsHistory: - * type: object - * properties: - * id: - * type: integer - * description: 积分历史记录ID - * points_change: - * type: integer - * description: 积分变动数量 - * type: - * type: string - * description: 积分变动类型(earn-获得, spend-消费, admin_adjust-管理员调整) - * description: - * type: string - * description: 积分变动描述 - * created_at: - * type: string - * format: date-time - * description: 创建时间 - */ - -/** - * @swagger - * /api/points/balance: - * get: - * summary: 获取用户当前积分余额 - * tags: [Points] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取积分余额 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * points: - * type: integer - * description: 用户当前积分 - * 401: - * description: 未授权,需要登录 - * 404: - * description: 用户不存在 - * 500: - * description: 服务器错误 - */ -router.get('/balance', auth, async (req, res) => { - try { - const userId = req.user.id; - - const [users] = await getDB().execute( - 'SELECT points FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({ success: false, message: '用户不存在' }); - } - - res.json({ - success: true, - data: { - points: users[0].points - } - }); - } catch (error) { - console.error('获取积分余额失败:', error); - res.status(500).json({ success: false, message: '获取积分余额失败' }); - } -}); - -/** - * @swagger - * /api/points/history: - * get: - * summary: 获取用户积分历史记录 - * tags: [Points] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页记录数 - * - in: query - * name: type - * schema: - * type: string - * enum: [earn, spend, admin_adjust] - * description: 积分变动类型 - * - in: query - * name: username - * schema: - * type: string - * description: 用户名(仅管理员可用) - * - in: query - * name: change - * schema: - * type: string - * enum: [positive, negative] - * description: 积分变动方向(仅管理员可用) - * - in: query - * name: startDate - * schema: - * type: string - * format: date - * description: 开始日期(仅管理员可用) - * - in: query - * name: endDate - * schema: - * type: string - * format: date - * description: 结束日期(仅管理员可用) - * responses: - * 200: - * description: 成功获取积分历史 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * records: - * type: array - * items: - * $ref: '#/components/schemas/PointsHistory' - * pagination: - * type: object - * properties: - * page: - * type: integer - * limit: - * type: integer - * total: - * type: integer - * totalPages: - * type: integer - * 401: - * description: 未授权,需要登录 - * 500: - * description: 服务器错误 - */ -router.get('/history', auth, async (req, res) => { - try { - const { page = 1, limit = 10, type, username, change, startDate, endDate } = req.query; - - // 确保参数为有效数字 - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 10; - const offset = (pageNum - 1) * limitNum; - - let whereClause = ''; - let queryParams = []; - - // 如果是管理员,可以查看所有用户的积分历史 - if (req.user.role === 'admin') { - whereClause = 'WHERE 1=1'; - - // 按用户名筛选 - if (username) { - whereClause += ' AND u.username LIKE ?'; - queryParams.push(`%${username}%`); - } - - // 按类型筛选 - if (type) { - whereClause += ' AND ph.type = ?'; - queryParams.push(type); - } - - // 按积分变化筛选 - if (change === 'positive') { - whereClause += ' AND ph.amount > 0'; - } else if (change === 'negative') { - whereClause += ' AND ph.amount < 0'; - } - - // 按时间范围筛选 - if (startDate) { - whereClause += ' AND DATE(ph.created_at) >= ?'; - queryParams.push(startDate); - } - if (endDate) { - whereClause += ' AND DATE(ph.created_at) <= ?'; - queryParams.push(endDate); - } - } else { - // 普通用户只能查看自己的积分历史 - whereClause = 'WHERE ph.user_id = ?'; - queryParams.push(req.user.id); - - if (type && ['earn', 'spend'].includes(type)) { - whereClause += ' AND ph.type = ?'; - queryParams.push(type); - } - } - - // 获取总数 - const countQuery = req.user.role === 'admin' - ? `SELECT COUNT(*) as total FROM points_history ph JOIN users u ON ph.user_id = u.id ${whereClause}` - : `SELECT COUNT(*) as total FROM points_history ph ${whereClause}`; - - const [countResult] = await getDB().execute(countQuery, queryParams); - - // 获取历史记录 - const historyQuery = req.user.role === 'admin' - ? `SELECT ph.id, ph.amount as points, ph.type, ph.description, ph.created_at, - u.username, - (SELECT points FROM users WHERE id = ph.user_id) as balance_after - FROM points_history ph - JOIN users u ON ph.user_id = u.id - ${whereClause} - ORDER BY ph.created_at DESC - LIMIT ${limitNum} OFFSET ${offset}` - : `SELECT id, amount as points_change, type, description, created_at - FROM points_history ph - ${whereClause} - ORDER BY created_at DESC - LIMIT ${limitNum} OFFSET ${offset}`; - - const [records] = await getDB().execute(historyQuery, queryParams); - - const responseData = req.user.role === 'admin' - ? { - history: records, - total: countResult[0].total - } - : { - records, - pagination: { - page: pageNum, - limit: limitNum, - total: countResult[0].total, - totalPages: Math.ceil(countResult[0].total / limitNum) - } - }; - - res.json({ - success: true, - data: responseData - }); - } catch (error) { - console.error('获取积分历史失败:', error); - res.status(500).json({ success: false, message: '获取积分历史失败' }); - } -}); - -/** - * @swagger - * /api/points/adjust: - * post: - * summary: 管理员调整用户积分 - * tags: [Points] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - userId - * - points - * - reason - * properties: - * userId: - * type: integer - * description: 用户ID - * points: - * type: integer - * description: 调整的积分数量(正数为增加,负数为减少) - * reason: - * type: string - * description: 调整原因 - * responses: - * 200: - * description: 积分调整成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 积分调整成功 - * data: - * type: object - * properties: - * userId: - * type: integer - * pointsChanged: - * type: integer - * newBalance: - * type: integer - * 400: - * description: 参数错误或积分不足 - * 401: - * description: 未授权,需要管理员权限 - * 404: - * description: 用户不存在 - * 500: - * description: 服务器错误 - */ -router.post('/adjust', auth, adminAuth, async (req, res) => { - const connection = await getDB().getConnection(); - - try { - await connection.beginTransaction(); - - const { userId, points, reason } = req.body; - - if (!userId || points === undefined || points === null || !reason) { - await connection.rollback(); - return res.status(400).json({ success: false, message: '请提供有效的用户ID、积分数量和调整原因' }); - } - - // 检查用户是否存在 - const [users] = await connection.execute( - 'SELECT id, username, points FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - await connection.rollback(); - return res.status(404).json({ success: false, message: '用户不存在' }); - } - - const currentPoints = users[0].points; - const newPoints = currentPoints + points; - - // 检查积分是否会变为负数 - if (newPoints < 0) { - await connection.rollback(); - return res.status(400).json({ success: false, message: '用户积分不足,无法扣除' }); - } - - // 更新用户积分 - await connection.execute( - 'UPDATE users SET points = ? WHERE id = ?', - [newPoints, userId] - ); - - // 记录积分历史 - await connection.execute( - `INSERT INTO points_history (user_id, amount, type, description, created_at) - VALUES (?, ?, 'admin_adjust', ?, NOW())`, - [userId, points, reason] - ); - - await connection.commit(); - - res.json({ - success: true, - message: '积分调整成功', - data: { - userId: userId, - pointsChanged: points, - newBalance: newPoints - } - }); - } catch (error) { - await connection.rollback(); - console.error('积分调整失败:', error); - res.status(500).json({ success: false, message: '积分调整失败' }); - } finally { - connection.release(); - } -}); - -/** - * @swagger - * /api/points/recharge: - * post: - * summary: 管理员给用户充值积分 - * tags: [Points] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - user_id - * - points - * properties: - * user_id: - * type: integer - * description: 用户ID - * points: - * type: integer - * description: 充值的积分数量(必须为正数) - * description: - * type: string - * description: 充值描述 - * default: 管理员充值 - * responses: - * 200: - * description: 积分充值成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 积分充值成功 - * data: - * type: object - * properties: - * userId: - * type: integer - * pointsAdded: - * type: integer - * 400: - * description: 参数错误 - * 401: - * description: 未授权,需要管理员权限 - * 404: - * description: 用户不存在 - * 500: - * description: 服务器错误 - */ -router.post('/recharge', auth, adminAuth, async (req, res) => { - const connection = await getDB().getConnection(); - - try { - await connection.beginTransaction(); - - const { user_id, points, description = '管理员充值' } = req.body; - - if (!user_id || !points || points <= 0) { - await connection.rollback(); - return res.status(400).json({ success: false, message: '请提供有效的用户ID和积分数量' }); - } - - // 检查用户是否存在 - const [users] = await connection.execute( - 'SELECT id, username FROM users WHERE id = ?', - [user_id] - ); - - if (users.length === 0) { - await connection.rollback(); - return res.status(404).json({ success: false, message: '用户不存在' }); - } - - // 增加用户积分 - await connection.execute( - 'UPDATE users SET points = points + ? WHERE id = ?', - [points, user_id] - ); - - // 记录积分历史 - await connection.execute( - `INSERT INTO points_history (user_id, amount, type, description, created_at) - VALUES (?, ?, 'earn', ?, NOW())`, - [user_id, points, description] - ); - - await connection.commit(); - - res.json({ - success: true, - message: '积分充值成功', - data: { - userId: user_id, - pointsAdded: points - } - }); - } catch (error) { - await connection.rollback(); - console.error('积分充值失败:', error); - res.status(500).json({ success: false, message: '积分充值失败' }); - } finally { - connection.release(); - } -}); - - - -/** - * @swagger - * /api/points/leaderboard: - * get: - * summary: 获取积分排行榜 - * tags: [Points] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 返回的排行榜数量 - * responses: - * 200: - * description: 成功获取积分排行榜 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * leaderboard: - * type: array - * items: - * type: object - * properties: - * rank: - * type: integer - * userId: - * type: integer - * username: - * type: string - * points: - * type: integer - * 401: - * description: 未授权,需要登录 - * 500: - * description: 服务器错误 - */ -router.get('/leaderboard', auth, async (req, res) => { - try { - const { limit = 10 } = req.query; - - const [users] = await getDB().execute( - `SELECT id, username, points - FROM users - WHERE points > 0 - ORDER BY points DESC - LIMIT ?`, - [parseInt(limit)] - ); - - res.json({ - success: true, - data: { - leaderboard: users.map((user, index) => ({ - rank: index + 1, - userId: user.id, - username: user.username, - points: user.points - })) - } - }); - } catch (error) { - console.error('获取积分排行榜失败:', error); - res.status(500).json({ success: false, message: '获取积分排行榜失败' }); - } -}); - -/** - * @swagger - * /api/points/stats: - * get: - * summary: 获取积分统计信息(管理员权限) - * tags: [Points] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: 成功获取积分统计信息 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * stats: - * type: object - * properties: - * totalPoints: - * type: integer - * description: 系统中总积分数量 - * totalEarned: - * type: integer - * description: 总积分发放量 - * totalSpent: - * type: integer - * description: 总积分消费量 - * activeUsers: - * type: integer - * description: 活跃用户数 - * 401: - * description: 未授权,需要管理员权限 - * 500: - * description: 服务器错误 - */ -router.get('/stats', auth, adminAuth, async (req, res) => { - try { - // 总积分发放量 - const [totalEarned] = await getDB().execute( - 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn"' - ); - - // 总积分消费量 - const [totalConsumed] = await getDB().execute( - 'SELECT SUM(ABS(amount)) as total FROM points_history WHERE type = "spend"' - ); - - // 本月积分发放 - const [monthEarned] = await getDB().execute( - 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn" AND YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())' - ); - - // 上月积分发放(用于计算增长率) - const [lastMonthEarned] = await getDB().execute( - 'SELECT SUM(amount) as total FROM points_history WHERE type = "earn" AND YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))' - ); - - // 计算月增长率 - const lastMonthTotal = lastMonthEarned[0].total || 0; - const currentMonthTotal = monthEarned[0].total || 0; - let monthGrowthRate = 0; - if (lastMonthTotal > 0) { - monthGrowthRate = ((currentMonthTotal - lastMonthTotal) / lastMonthTotal * 100).toFixed(1); - } - - // 活跃用户数(有积分记录的用户) - const [activeUsers] = await getDB().execute( - 'SELECT COUNT(DISTINCT user_id) as count FROM points_history' - ); - - res.json({ - success: true, - data: { - stats: { - totalPoints: totalEarned[0].total || 0, - totalEarned: totalEarned[0].total || 0, - totalSpent: totalConsumed[0].total || 0, - activeUsers: activeUsers[0].count - } - } - }); - } catch (error) { - console.error('获取积分统计失败:', error); - res.status(500).json({ success: false, message: '获取积分统计失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/products.js b/routes/products.js index 1b6880f..287a3d2 100644 --- a/routes/products.js +++ b/routes/products.js @@ -43,7 +43,7 @@ router.get('/', async (req, res) => { // 获取商品列表 const query = ` - SELECT id, name, shop_name, rongdou_price, category, points_price, stock, sales, image_url as image, description, status, payment_methods, created_at, updated_at + SELECT id, name, rongdou_price, category, points_price, stock, image_url as image, description, status, payment_methods, created_at, updated_at FROM products ${whereClause} ORDER BY created_at DESC @@ -130,30 +130,6 @@ router.get('/hot', async (req, res) => { } }); -/** - * @swagger - * /products/flash-sale: - * get: - * summary: 获取秒杀商品 - * tags: [Products] - * responses: - * 200: - * description: 成功获取秒杀商品 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * products: - * type: array - * items: - * $ref: '#/components/schemas/Product' - */ router.get('/cheap', async (req, res) => { try { // 从活跃商品中随机获取2个商品作为秒杀商品 @@ -193,143 +169,6 @@ router.get('/cheap', async (req, res) => { res.status(500).json({ success: false, message: '获取秒杀商品失败' }); } }); - -/** - * @swagger - * /products/{id}: - * get: - * summary: 获取单个商品详情(包含增强规格信息) - * tags: [Products] - * parameters: - * - in: path - * name: id - * schema: - * type: integer - * required: true - * description: 商品ID - * responses: - * 200: - * description: 成功获取商品详情,包含完整的规格信息 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * product: - * type: object - * properties: - * id: - * type: integer - * name: - * type: string - * category: - * type: string - * price: - * type: number - * points_price: - * type: number - * rongdou_price: - * type: number - * stock: - * type: integer - * specifications: - * type: array - * description: 商品规格组合列表(笛卡尔积规格系统) - * items: - * type: object - * properties: - * id: - * type: integer - * description: 规格组合ID - * combination_key: - * type: string - * description: 规格组合键(如:1-3-5) - * spec_display: - * type: string - * description: 规格显示文本(如:颜色:红色 | 尺寸:XL) - * spec_details: - * type: array - * description: 规格详细信息 - * items: - * type: object - * properties: - * id: - * type: integer - * spec_name: - * type: string - * description: 规格名称 - * spec_display_name: - * type: string - * description: 规格显示名称 - * value: - * type: string - * description: 规格值 - * display_value: - * type: string - * description: 规格显示值 - * color_code: - * type: string - * description: 颜色代码 - * image_url: - * type: string - * description: 规格图片 - * price_adjustment: - * type: number - * description: 价格调整 - * points_adjustment: - * type: number - * description: 积分调整 - * rongdou_adjustment: - * type: number - * description: 融豆调整 - * stock: - * type: integer - * description: 规格库存 - * sku_code: - * type: string - * description: SKU编码 - * barcode: - * type: string - * description: 条形码 - * weight: - * type: number - * description: 重量 - * volume: - * type: number - * description: 体积 - * actual_price: - * type: number - * description: 实际价格(基础价格+调整) - * actual_points_price: - * type: number - * description: 实际积分价格 - * actual_rongdou_price: - * type: number - * description: 实际融豆价格 - * is_available: - * type: boolean - * description: 是否有库存 - * specification_count: - * type: integer - * description: 规格总数 - * available_specifications: - * type: integer - * description: 有库存的规格数量 - * attributes: - * type: array - * description: 商品属性 - * isFavorited: - * type: boolean - * description: 是否已收藏 - * 404: - * description: 商品不存在 - */ router.get('/:id', async (req, res) => { try { const { id } = req.params; @@ -1048,14 +887,6 @@ router.get('/favorites', auth, async (req, res) => { } }); - - - - - - - - // 获取商品属性 router.get('/:id/attributes', async (req, res) => { try { diff --git a/routes/regions.js b/routes/regions.js deleted file mode 100644 index 6bded1a..0000000 --- a/routes/regions.js +++ /dev/null @@ -1,436 +0,0 @@ -const express = require('express') -const router = express.Router() -const {getDB} = require('../database') - -/** - * @swagger - * tags: - * name: Regions - * description: 地区数据API - */ - -/** - * @swagger - * components: - * schemas: - * Region: - * type: object - * properties: - * code: - * type: string - * description: 地区编码 - * name: - * type: string - * description: 地区名称 - * ZhejiangRegion: - * type: object - * properties: - * id: - * type: integer - * description: 地区ID - * city_name: - * type: string - * description: 城市名称 - * district_name: - * type: string - * description: 区县名称 - * region_code: - * type: string - * description: 地区编码 - * is_available: - * type: integer - * description: 是否可用(1:可用 0:不可用) - */ - -/** - * @swagger - * /regions/zhejiang: - * get: - * summary: 获取浙江省所有地区数据 - * tags: [Regions] - * responses: - * 200: - * description: 成功获取浙江省地区数据 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: array - * items: - * $ref: '#/components/schemas/ZhejiangRegion' - * message: - * type: string - * example: 获取地区数据成功 - * 500: - * description: 服务器错误 - */ -router.get('/zhejiang', async (req, res) => { - try { - const query = ` - SELECT id, city_name, district_name, region_code, is_available - FROM zhejiang_regions - WHERE is_available = 1 - ORDER BY city_name, district_name - ` - - const [rows] = await getDB().execute(query) - - res.json({ - success: true, - data: rows, - message: '获取地区数据成功' - }) - } catch (error) { - console.error('获取浙江省地区数据失败:', error) - res.status(500).json({ - success: false, - message: '获取地区数据失败' - }) - } -}) - -/** - * @swagger - * /regions/provinces: - * get: - * summary: 获取所有省份 - * tags: [Regions] - * responses: - * 200: - * description: 成功获取省份列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: array - * items: - * $ref: '#/components/schemas/Region' - * 500: - * description: 服务器错误 - */ -router.get('/provinces', async (req, res) => { - try { - // 按level分组数据 - const regionsByLevel = { - 1: [], // 省份 - 2: [], // 城市 - 3: [] // 区县 - }; - if (!global.provinces) { - // 一次性获取所有区域数据(省、市、区县) - const [allRegions] = await getDB().execute( - `SELECT code, name as label, level, parent_code - FROM china_regions - WHERE level <= 3 - ORDER BY level, code` - ); - - - // 创建code到region的映射,便于快速查找 - const regionMap = {}; - - // 分组并建立映射 - allRegions.forEach(region => { - region.children = []; // 初始化children数组 - regionsByLevel[region.level].push(region); - regionMap[region.code] = region; - }); - - // 构建层级关系:先处理区县到城市的关系 - regionsByLevel[3].forEach(district => { - const parentCity = regionMap[district.parent_code]; - if (parentCity) { - parentCity.children.push(district); - } - }); - - // 再处理城市到省份的关系 - regionsByLevel[2].forEach(city => { - const parentProvince = regionMap[city.parent_code]; - if (parentProvince) { - parentProvince.children.push(city); - } - }); - global.provinces = regionsByLevel[1]; - }else { - console.log('1111') - regionsByLevel[1] = global.provinces; - } - - - // 返回省份数据(已包含完整的层级结构) - res.json({ - success: true, - data: regionsByLevel[1] - }); - } catch (error) { - console.error('获取省份列表错误:', error); - res.status(500).json({message: '获取省份列表失败'}); - } -}); - -/** - * @swagger - * /regions/cities/{provinceCode}: - * get: - * summary: 根据省份代码获取城市列表 - * tags: [Regions] - * parameters: - * - in: path - * name: provinceCode - * required: true - * schema: - * type: string - * description: 省份代码 - * responses: - * 200: - * description: 成功获取城市列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: array - * items: - * $ref: '#/components/schemas/Region' - * 500: - * description: 服务器错误 - */ -router.get('/cities/:provinceCode', async (req, res) => { - try { - const provinceCode = req.params.provinceCode; - - const [cities] = await getDB().execute( - `SELECT code, name - FROM china_regions - WHERE level = 2 - AND parent_code = ? - ORDER BY code`, - [provinceCode] - ); - - res.json({ - success: true, - data: cities - }); - } catch (error) { - console.error('获取城市列表错误:', error); - res.status(500).json({message: '获取城市列表失败'}); - } -}); - -/** - * @swagger - * /regions/districts/{cityCode}: - * get: - * summary: 根据城市代码获取区县列表 - * tags: [Regions] - * parameters: - * - in: path - * name: cityCode - * required: true - * schema: - * type: string - * description: 城市代码 - * responses: - * 200: - * description: 成功获取区县列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: array - * items: - * $ref: '#/components/schemas/Region' - * 500: - * description: 服务器错误 - */ -router.get('/districts/:cityCode', async (req, res) => { - try { - const cityCode = req.params.cityCode; - - const [districts] = await getDB().execute( - `SELECT code, name - FROM china_regions - WHERE level = 3 - AND parent_code = ? - ORDER BY code`, - [cityCode] - ); - - res.json({ - success: true, - data: districts - }); - } catch (error) { - console.error('获取区县列表错误:', error); - res.status(500).json({message: '获取区县列表失败'}); - } -}); - -/** - * @swagger - * /regions/path/{regionCode}: - * get: - * summary: 根据区域代码获取完整路径(省-市-区) - * tags: [Regions] - * parameters: - * - in: path - * name: regionCode - * required: true - * schema: - * type: string - * description: 区域代码 - * responses: - * 200: - * description: 成功获取区域完整路径 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * data: - * type: object - * properties: - * province: - * $ref: '#/components/schemas/Region' - * city: - * $ref: '#/components/schemas/Region' - * district: - * $ref: '#/components/schemas/Region' - * 404: - * description: 区域不存在 - * 500: - * description: 服务器错误 - */ -router.get('/path/:regionCode', async (req, res) => { - try { - const regionCode = req.params.regionCode; - - // 获取当前区域信息 - const [currentRegion] = await getDB().execute( - 'SELECT code, name, level, parent_code FROM china_regions WHERE code = ?', - [regionCode] - ); - - if (currentRegion.length === 0) { - return res.status(404).json({message: '区域不存在'}); - } - - const region = currentRegion[0]; - const path = [region]; - - // 递归获取父级区域 - let parentCode = region.parent_code; - while (parentCode) { - const [parentRegion] = await getDB().execute( - 'SELECT code, name, level, parent_code FROM china_regions WHERE code = ? AND status = "active"', - [parentCode] - ); - - if (parentRegion.length > 0) { - path.unshift(parentRegion[0]); - parentCode = parentRegion[0].parent_code; - } else { - break; - } - } - - res.json({ - success: true, - data: { - path, - province: path.find(r => r.level === 1) || null, - city: path.find(r => r.level === 2) || null, - district: path.find(r => r.level === 3) || null - } - }); - } catch (error) { - console.error('获取区域路径错误:', error); - res.status(500).json({message: '获取区域路径失败'}); - } -}); - -// 搜索区域(支持模糊搜索) -router.get('/search', async (req, res) => { - try { - const {keyword, level} = req.query; - - if (!keyword || keyword.trim() === '') { - return res.status(400).json({message: '搜索关键词不能为空'}); - } - - let sql = `SELECT code, name, level, parent_code - FROM china_regions - WHERE name LIKE ?`; - const params = [`%${keyword.trim()}%`]; - - if (level) { - sql += ' AND level = ?'; - params.push(parseInt(level)); - } - - sql += ' ORDER BY level, code LIMIT 50'; - - const [regions] = await getDB().execute(sql, params); - - // 为每个搜索结果获取完整路径 - const results = []; - for (const region of regions) { - const path = [region]; - let parentCode = region.parent_code; - - while (parentCode) { - const [parentRegion] = await getDB().execute( - 'SELECT code, name, level, parent_code FROM china_regions WHERE code = ? AND status = "active"', - [parentCode] - ); - - if (parentRegion.length > 0) { - path.unshift(parentRegion[0]); - parentCode = parentRegion[0].parent_code; - } else { - break; - } - } - - results.push({ - ...region, - path, - fullName: path.map(r => r.name).join(' - ') - }); - } - - res.json({ - success: true, - data: results - }); - } catch (error) { - console.error('搜索区域错误:', error); - res.status(500).json({message: '搜索区域失败'}); - } -}); - -module.exports = router \ No newline at end of file diff --git a/routes/riskManagement.js b/routes/riskManagement.js deleted file mode 100644 index 967ecb6..0000000 --- a/routes/riskManagement.js +++ /dev/null @@ -1,440 +0,0 @@ -const express = require('express'); -router = express.Router(); - -/** - * @swagger - * tags: - * name: RiskManagement - * description: 风险管理API - */ -const { auth } = require('../middleware/auth'); -const timeoutService = require('../services/timeoutService'); -const { getDB } = require('../database'); - -/** - * 检查管理员权限 - */ -const requireAdmin = (req, res, next) => { - if (req.user.role !== 'admin') { - return res.status(403).json({ success: false, message: '需要管理员权限' }); - } - next(); -}; - -/** - * @swagger - * /risk-management/users: - * get: - * summary: 获取风险用户列表 - * tags: [RiskManagement] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * - in: query - * name: is_blacklisted - * schema: - * type: integer - * enum: [0, 1] - * description: 是否被拉黑 - * - in: query - * name: username - * schema: - * type: string - * description: 用户名 - * responses: - * 200: - * description: 成功获取风险用户列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * users: - * type: array - * items: - * type: object - * properties: - * id: - * type: integer - * username: - * type: string - * real_name: - * type: string - * is_blacklisted: - * type: boolean - * blacklist_reason: - * type: string - * blacklisted_at: - * type: string - * format: date-time - * pagination: - * type: object - * properties: - * total: - * type: integer - * page: - * type: integer - * limit: - * type: integer - * pages: - * type: integer - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ -router.get('/users', auth, requireAdmin, async (req, res) => { - try { - const { page = 1, limit = 10, is_blacklisted, username } = req.query; - - const filters = {}; - if (is_blacklisted !== undefined) { - filters.is_blacklisted = parseInt(is_blacklisted); - } - if (username) { - filters.username = username; - } - - const result = await timeoutService.getRiskUsers(filters, { page, limit }); - - res.json({ - success: true, - data: result - }); - } catch (error) { - console.error('获取风险用户列表失败:', error); - res.status(500).json({ success: false, message: '获取风险用户列表失败' }); - } -}); - -/** - * @swagger - * /risk-management/blacklist/{userId}: - * post: - * summary: 拉黑用户 - * tags: [RiskManagement] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * schema: - * type: integer - * required: true - * description: 用户ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - reason - * properties: - * reason: - * type: string - * description: 拉黑原因 - * responses: - * 200: - * description: 用户已被拉黑 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 用户已被拉黑 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ -router.post('/blacklist/:userId', auth, requireAdmin, async (req, res) => { - try { - const { userId } = req.params; - const { reason } = req.body; - const operatorId = req.user.id; - - if (!reason || reason.trim() === '') { - return res.status(400).json({ success: false, message: '请提供拉黑原因' }); - } - - await timeoutService.blacklistUser(parseInt(userId), reason.trim(), operatorId); - - res.json({ - success: true, - message: '用户已被拉黑' - }); - } catch (error) { - console.error('拉黑用户失败:', error); - res.status(500).json({ success: false, message: error.message || '拉黑用户失败' }); - } -}); - -/** - * @swagger - * /risk-management/unblacklist/{userId}: - * post: - * summary: 解除拉黑 - * tags: [RiskManagement] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * schema: - * type: integer - * required: true - * description: 用户ID - * responses: - * 200: - * description: 已解除拉黑 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 已解除拉黑 - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ -router.post('/unblacklist/:userId', auth, requireAdmin, async (req, res) => { - try { - const { userId } = req.params; - const operatorId = req.user.id; - - await timeoutService.unblacklistUser(parseInt(userId), operatorId); - - res.json({ - success: true, - message: '已解除拉黑' - }); - } catch (error) { - console.error('解除拉黑失败:', error); - res.status(500).json({ success: false, message: error.message || '解除拉黑失败' }); - } -}); - -/** - * @swagger - * /risk-management/overdue-transfers: - * get: - * summary: 获取超时转账列表 - * tags: [RiskManagement] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * description: 页码 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * description: 每页数量 - * responses: - * 200: - * description: 成功获取超时转账列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * transfers: - * type: array - * items: - * type: object - * properties: - * id: - * type: integer - * user_id: - * type: integer - * recipient_id: - * type: integer - * amount: - * type: number - * status: - * type: string - * created_at: - * type: string - * format: date-time - * username: - * type: string - * recipient_name: - * type: string - * overdue_hours: - * type: number - * pagination: - * type: object - * properties: - * total: - * type: integer - * page: - * type: integer - * limit: - * type: integer - * pages: - * type: integer - * 401: - * description: 未授权 - * 403: - * description: 权限不足 - * 500: - * description: 服务器错误 - */ -router.get('/overdue-transfers', auth, requireAdmin, async (req, res) => { - try { - const { page = 1, limit = 10 } = req.query; - const pageNum = parseInt(page, 10) || 1; - const limitNum = parseInt(limit, 10) || 10; - const offset = (pageNum - 1) * limitNum; - - const db = getDB(); - - // 获取总数 - const [countResult] = await db.execute( - 'SELECT COUNT(*) as total FROM transfers WHERE is_overdue = 1' - ); - const total = countResult[0].total; - - // 获取数据 - const [transfers] = await db.execute( - `SELECT t.*, - fu.username as from_username, fu.real_name as from_real_name, - tu.username as to_username, tu.real_name as to_real_name - FROM transfers t - LEFT JOIN users fu ON t.from_user_id = fu.id - LEFT JOIN users tu ON t.to_user_id = tu.id - WHERE t.is_overdue = 1 - ORDER BY t.overdue_at DESC - LIMIT ${limitNum} OFFSET ${offset}` - ); - - res.json({ - success: true, - data: { - transfers, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - } - }); - } catch (error) { - console.error('获取超时转账列表失败:', error); - res.status(500).json({ success: false, message: '获取超时转账列表失败' }); - } -}); - -/** - * 手动检查转账超时 - */ -router.post('/check-timeouts', auth, requireAdmin, async (req, res) => { - try { - await timeoutService.checkTransferTimeouts(); - - res.json({ - success: true, - message: '转账超时检查已完成' - }); - } catch (error) { - console.error('手动检查转账超时失败:', error); - res.status(500).json({ success: false, message: '检查转账超时失败' }); - } -}); - -/** - * 获取风险管理统计信息 - */ -router.get('/stats', auth, requireAdmin, async (req, res) => { - try { - const db = getDB(); - - // 获取统计数据 - const [stats] = await db.execute( - `SELECT - COUNT(CASE WHEN is_risk_user = 1 THEN 1 END) as risk_users_count, - COUNT(CASE WHEN is_blacklisted = 1 THEN 1 END) as blacklisted_users_count, - COUNT(CASE WHEN is_risk_user = 1 AND is_blacklisted = 0 THEN 1 END) as risk_not_blacklisted_count - FROM users` - ); - - const [overdueStats] = await db.execute( - `SELECT - COUNT(*) as overdue_transfers_count, - SUM(amount) as overdue_amount_total - FROM transfers - WHERE is_overdue = 1` - ); - - const [todayOverdue] = await db.execute( - `SELECT COUNT(*) as today_overdue_count - FROM transfers - WHERE is_overdue = 1 AND DATE(overdue_at) = CURDATE()` - ); - - res.json({ - success: true, - data: { - riskUsersCount: stats[0].risk_users_count, - blacklistedUsersCount: stats[0].blacklisted_users_count, - riskNotBlacklistedCount: stats[0].risk_not_blacklisted_count, - overdueTransfersCount: overdueStats[0].overdue_transfers_count, - overdueAmountTotal: overdueStats[0].overdue_amount_total || 0, - todayOverdueCount: todayOverdue[0].today_overdue_count - } - }); - } catch (error) { - console.error('获取风险管理统计失败:', error); - res.status(500).json({ success: false, message: '获取统计信息失败' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/sms.js b/routes/sms.js deleted file mode 100644 index 787bd56..0000000 --- a/routes/sms.js +++ /dev/null @@ -1,340 +0,0 @@ -const express = require('express') -const router = express.Router() -const { getDB } = require('../database') -const Dysmsapi20170525 = require('@alicloud/dysmsapi20170525') -const OpenApi = require('@alicloud/openapi-client') -const { Config } = require('@alicloud/openapi-client') - -/** - * @swagger - * tags: - * name: SMS - * description: 短信验证码相关接口 - */ - -/** - * @swagger - * components: - * schemas: - * SMSVerification: - * type: object - * properties: - * phone: - * type: string - * description: 手机号码 - * code: - * type: string - * description: 验证码 - */ - -// 阿里云短信配置 -const config = new Config({ - // 您的AccessKey ID - accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID || 'your_access_key_id', - // 您的AccessKey Secret - accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET || 'your_access_key_secret', - // 访问的域名 - endpoint: 'dysmsapi.aliyuncs.com' -}) - -// 创建短信客户端 -const client = new Dysmsapi20170525.default(config) - -// 短信模板配置 -const SMS_CONFIG = { - signName: process.env.ALIYUN_SMS_SIGN_NAME || '您的签名', // 短信签名 - templateCode: process.env.ALIYUN_SMS_TEMPLATE_CODE || 'SMS_XXXXXX', // 短信模板CODE - // 开发环境标识 - isDevelopment: process.env.NODE_ENV !== 'production' -} - -// 存储验证码的内存对象(生产环境建议使用Redis) -const smsCodeStore = new Map() - -// 验证码有效期(5分钟) -const CODE_EXPIRE_TIME = 5 * 60 * 1000 -// 最大尝试次数 -const MAX_ATTEMPTS = 3 -// 发送频率限制(60秒) -const SEND_INTERVAL = 60 * 1000 - -/** - * 生成6位数字验证码 - * @returns {string} 验证码 - */ -function generateSMSCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); -} - -/** - * @swagger - * /api/sms/send: - * post: - * summary: 发送短信验证码 - * tags: [SMS] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - phone - * properties: - * phone: - * type: string - * description: 手机号码 - * responses: - * 200: - * description: 验证码发送成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 验证码发送成功 - * 400: - * description: 参数错误或发送频率限制 - * 500: - * description: 服务器错误 - */ -router.post('/send', async (req, res) => { - try { - const { phone } = req.body - console.log(phone) - // 验证手机号格式 - const phoneRegex = /^1[3-9]\d{9}$/ - if (!phoneRegex.test(phone)) { - return res.json({ - success: false, - message: '手机号格式不正确' - }) - } - - // 检查发送频率限制 - const lastSendTime = smsCodeStore.get(`last_send_${phone}`) - if (lastSendTime && Date.now() - lastSendTime < SEND_INTERVAL) { - const remainingTime = Math.ceil((SEND_INTERVAL - (Date.now() - lastSendTime)) / 1000) - return res.json({ - success: false, - message: `请等待${remainingTime}秒后再发送` - }) - } - - // 生成6位数字验证码 - const code = Math.random().toString().slice(-6) - - // 存储验证码信息 - smsCodeStore.set(phone, { - code, - timestamp: Date.now(), - attempts: 0 - }) - - // 记录发送时间 - smsCodeStore.set(`last_send_${phone}`, Date.now()) - // 生产环境发送真实短信 - try { - console.log(code); - res.json({ - success: true, - message: '验证码发送成功' - }) - return - const sendSmsRequest = new Dysmsapi20170525.SendSmsRequest({ - phoneNumbers: phone, - signName: SMS_CONFIG.signName, - templateCode: SMS_CONFIG.templateCode, - templateParam: JSON.stringify({ code }) - }) - - const response = await client.sendSms(sendSmsRequest) - console.log(response.body); - - if (response.body.code === 'OK') { - res.json({ - success: true, - message: '验证码发送成功' - }) - } else { - console.error('阿里云短信发送失败:', response.body) - res.json({ - success: false, - message: '发送失败,请稍后重试' - }) - } - } catch (smsError) { - console.error('阿里云短信API调用失败:', smsError) - res.json({ - success: false, - message: '发送失败,请稍后重试' - }) - } - - } catch (error) { - console.error('发送短信验证码失败:', error) - res.status(500).json({ - success: false, - message: '发送失败,请稍后重试' - }) - } -}); - -/** - * @swagger - * /api/sms/verify: - * post: - * summary: 验证短信验证码 - * tags: [SMS] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - phone - * - code - * properties: - * phone: - * type: string - * description: 手机号码 - * code: - * type: string - * description: 验证码 - * responses: - * 200: - * description: 验证码验证成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 手机号验证成功 - * data: - * type: object - * properties: - * phone: - * type: string - * verified: - * type: boolean - * 400: - * description: 参数错误或验证码错误 - * 500: - * description: 服务器错误 - */ -router.post('/verify', async (req, res) => { - try { - const { phone, code } = req.body; - - if (!phone || !code) { - return res.status(400).json({ success: false, message: '手机号和验证码不能为空' }); - } - - const storedData = smsCodeStore.get(phone); - - if (!storedData) { - return res.status(400).json({ success: false, message: '验证码不存在或已过期' }); - } - - // 检查验证码是否过期(5分钟) - if (Date.now() - storedData.timestamp > 300000) { - smsCodeStore.delete(phone); - return res.status(400).json({ success: false, message: '验证码已过期' }); - } - - // 检查尝试次数(最多3次) - if (storedData.attempts >= 3) { - smsCodeStore.delete(phone); - return res.status(400).json({ success: false, message: '验证码错误次数过多,请重新获取' }); - } - - // 验证验证码 - if (storedData.code !== code) { - storedData.attempts++; - smsCodeStore.set(phone, storedData); - return res.status(400).json({ - success: false, - message: `验证码错误,还可尝试${3 - storedData.attempts}次` - }); - } - - // 验证成功,删除验证码 - smsCodeStore.delete(phone); - smsCodeStore.delete(`time_${phone}`); - - res.json({ - success: true, - message: '手机号验证成功', - data: { - phone: phone, - verified: true - } - }); - - } catch (error) { - console.error('验证短信验证码错误:', error); - res.status(500).json({ success: false, message: '验证失败' }); - } -}); - -/** - * 导出验证手机号的函数供其他模块使用 - * @param {string} phone 手机号 - * @param {string} code 验证码 - * @returns {boolean} 验证结果 - */ -function verifySMSCode(phone, code) { - const storedData = smsCodeStore.get(phone); - - if (!storedData) { - return false; - } - - // 检查是否过期 - if (Date.now() - storedData.timestamp > 300000) { - smsCodeStore.delete(phone); - return false; - } - - // 检查尝试次数 - if (storedData.attempts >= 3) { - smsCodeStore.delete(phone); - return false; - } - - // 验证验证码 - if (storedData.code === code) { - smsCodeStore.delete(phone); - smsCodeStore.delete(`time_${phone}`); - return true; - } - - return false; -} - -// 清理过期验证码的定时任务 -setInterval(() => { - const now = Date.now(); - for (const [key, value] of smsCodeStore.entries()) { - if (key.startsWith('time_')) continue; - - if (value.timestamp && now - value.timestamp > 300000) { - smsCodeStore.delete(key); - smsCodeStore.delete(`time_${key}`); - } - } -}, 60000); // 每分钟清理一次 - -module.exports = router; -module.exports.verifySMSCode = verifySMSCode; \ No newline at end of file diff --git a/routes/specifications.js b/routes/specifications.js index 3563d10..4304168 100644 --- a/routes/specifications.js +++ b/routes/specifications.js @@ -1,1096 +1,654 @@ const express = require('express'); const router = express.Router(); -const { getDB } = require('../database'); -const { auth, adminAuth } = require('../middleware/auth'); +const {getDB} = require('../database'); +const {auth, adminAuth} = require('../middleware/auth'); +const {SelectBuilder} = require('../config/dbv2') +const {db} = require("../server"); +const sql = require("../config/config"); -/** - * @swagger - * components: - * schemas: - * SpecName: - * type: object - * properties: - * id: - * type: integer - * name: - * type: string - * description: 规格名称(如:颜色、尺寸) - * display_name: - * type: string - * description: 显示名称 - * sort_order: - * type: integer - * description: 排序 - * status: - * type: string - * enum: [active, inactive] - * SpecValue: - * type: object - * properties: - * id: - * type: integer - * spec_name_id: - * type: integer - * value: - * type: string - * description: 规格值(如:红色、XL) - * display_value: - * type: string - * color_code: - * type: string - * description: 颜色代码 - * image_url: - * type: string - * sort_order: - * type: integer - * status: - * type: string - * enum: [active, inactive] - */ -/** - * @swagger - * /specifications/names: - * get: - * summary: 获取所有规格名称 - * tags: [Specifications] - * parameters: - * - in: query - * name: status - * schema: - * type: string - * enum: [active, inactive] - * description: 状态筛选 - * responses: - * 200: - * description: 成功获取规格名称列表 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/SpecName' - */ -router.get('/names', async (req, res) => { - try { - const { status = 'active' } = req.query; - - let query = 'SELECT * FROM spec_names'; - const params = []; - - if (status) { - query += ' WHERE status = ?'; - params.push(status); - } - - query += ' ORDER BY sort_order, id'; - - const [specNames] = await getDB().execute(query, params); - - res.json({ - success: true, - data: specNames - }); - } catch (error) { - console.error('获取规格名称失败:', error); - res.status(500).json({ success: false, message: '获取规格名称失败' }); - } -}); - -/** - * @swagger - * /specifications/names: - * post: - * summary: 创建规格名称 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - name - * - display_name - * properties: - * name: - * type: string - * description: 规格名称 - * display_name: - * type: string - * description: 显示名称 - * sort_order: - * type: integer - * default: 0 - * responses: - * 201: - * description: 规格名称创建成功 - */ -router.post('/names', auth, adminAuth, async (req, res) => { - try { - const { name, display_name, sort_order = 0 } = req.body; - - if (!name || !display_name) { - return res.status(400).json({ success: false, message: '规格名称和显示名称不能为空' }); - } - - const [result] = await getDB().execute( - 'INSERT INTO spec_names (name, display_name, sort_order) VALUES (?, ?, ?)', - [name, display_name, sort_order] - ); - - res.status(201).json({ - success: true, - message: '规格名称创建成功', - data: { id: result.insertId } - }); - } catch (error) { - if (error.code === 'ER_DUP_ENTRY') { - return res.status(400).json({ success: false, message: '规格名称已存在' }); - } - console.error('创建规格名称失败:', error); - res.status(500).json({ success: false, message: '创建规格名称失败' }); - } -}); - -/** - * @swagger - * /specifications/names/{id}: - * delete: - * summary: 删除规格名称 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 规格名称ID - * responses: - * 200: - * description: 删除成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * example: 规格名称删除成功 - * 400: - * description: 该规格名称下还有规格值,无法删除 - * 404: - * description: 规格名称不存在 - * 500: - * description: 服务器错误 - */ -router.delete('/names/:id', auth, adminAuth, async (req, res) => { - try { - const { id } = req.params; - - // 检查规格名称是否存在 - const [existingName] = await getDB().execute( - 'SELECT id FROM spec_names WHERE id = ?', - [id] - ); - - if (existingName.length === 0) { - return res.status(404).json({ success: false, message: '规格名称不存在' }); - } - - // 检查该规格名称下是否还有规格值 - const [specValues] = await getDB().execute( - 'SELECT COUNT(*) as count FROM spec_values WHERE spec_name_id = ?', - [id] - ); - - if (specValues[0].count > 0) { - return res.status(400).json({ success: false, message: '该规格名称下还有规格值,请先删除所有规格值' }); - } - - // 删除规格名称 - await getDB().execute( - 'DELETE FROM spec_names WHERE id = ?', - [id] - ); - - res.json({ - success: true, - message: '规格名称删除成功' - }); - } catch (error) { - console.error('删除规格名称失败:', error); - res.status(500).json({ success: false, message: '删除规格名称失败' }); - } -}); - -/** - * @swagger - * /specifications/values: - * get: - * summary: 获取规格值列表 - * tags: [Specifications] - * parameters: - * - in: query - * name: spec_name_id - * schema: - * type: integer - * description: 规格名称ID - * - in: query - * name: status - * schema: - * type: string - * enum: [active, inactive] - * description: 状态筛选 - * responses: - * 200: - * description: 成功获取规格值列表 - */ -router.get('/values', async (req, res) => { - try { - const { spec_name_id, status = 'active' } = req.query; - - let query = ` - SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name - FROM spec_values sv - LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id - WHERE 1=1 - `; - const params = []; - - if (spec_name_id) { - query += ' AND sv.spec_name_id = ?'; - params.push(spec_name_id); - } - - if (status) { - query += ' AND sv.status = ?'; - params.push(status); - } - - query += ' ORDER BY sv.spec_name_id, sv.sort_order, sv.id'; - - const [specValues] = await getDB().execute(query, params); - - res.json({ - success: true, - data: specValues - }); - } catch (error) { - console.error('获取规格值失败:', error); - res.status(500).json({ success: false, message: '获取规格值失败' }); - } -}); - -/** - * @swagger - * /specifications/values: - * post: - * summary: 创建规格值 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - spec_name_id - * - value - * - display_value - * properties: - * spec_name_id: - * type: integer - * value: - * type: string - * display_value: - * type: string - * color_code: - * type: string - * image_url: - * type: string - * sort_order: - * type: integer - * default: 0 - * responses: - * 201: - * description: 规格值创建成功 - */ -router.post('/values', auth, adminAuth, async (req, res) => { - try { - const { spec_name_id, value, display_value, color_code, image_url, sort_order = 0 } = req.body; - - if (!spec_name_id || !value || !display_value) { - return res.status(400).json({ success: false, message: '规格名称ID、规格值和显示值不能为空' }); - } - - const [result] = await getDB().execute( - `INSERT INTO spec_values (spec_name_id, value, display_value, color_code, image_url, sort_order) - VALUES (?, ?, ?, ?, ?, ?)`, - [spec_name_id, value, display_value, color_code || null, image_url || null, sort_order] - ); - - res.status(201).json({ - success: true, - message: '规格值创建成功', - data: { id: result.insertId } - }); - } catch (error) { - if (error.code === 'ER_DUP_ENTRY') { - return res.status(400).json({ success: false, message: '该规格名称下的规格值已存在' }); - } - console.error('创建规格值失败:', error); - res.status(500).json({ success: false, message: '创建规格值失败' }); - } -}); - -/** - * @swagger - * /specifications/combinations/{productId}: - * get: - * summary: 获取商品的规格组合 - * tags: [Specifications] - * parameters: - * - in: path - * name: productId - * required: true - * schema: - * type: integer - * description: 商品ID - * - in: query - * name: status - * schema: - * type: string - * enum: [active, inactive] - * description: 状态筛选 - * responses: - * 200: - * description: 成功获取规格组合 - */ -router.get('/combinations/:productId', async (req, res) => { - try { - const { productId } = req.params; - const { status = 'active' } = req.query; - - // 获取商品的规格组合 - let query = ` - SELECT psc.*, p.name as product_name, p.price as base_price, - p.points_price as base_points_price, p.rongdou_price as base_rongdou_price - FROM product_spec_combinations psc - LEFT JOIN products p ON psc.product_id = p.id - WHERE psc.product_id = ? - `; - const params = [productId]; - - if (status) { - query += ' AND psc.status = ?'; - params.push(status); - } - - query += ' ORDER BY psc.combination_key'; - - const [combinations] = await getDB().execute(query, params); - - // 为每个组合获取详细的规格值信息 - for (let combination of combinations) { - let specValueIds; - try { - // 处理不同的数据格式 - if (!combination.spec_values) { - specValueIds = []; - } else if (typeof combination.spec_values === 'string') { - // 如果是字符串,尝试JSON解析,失败则按逗号分隔处理 - try { - specValueIds = JSON.parse(combination.spec_values); - } catch { - // 按逗号分隔的字符串处理 - specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); - } - } else if (Buffer.isBuffer(combination.spec_values)) { - // 如果是Buffer,转换为字符串后处理 - const strValue = combination.spec_values.toString(); - try { - specValueIds = JSON.parse(strValue); - } catch { - specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); - } - } else { - // 其他情况,尝试直接使用 - specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : []; - } - } catch (error) { - console.error('解析规格值失败:', combination.spec_values, error); - specValueIds = []; - } - - if (specValueIds && specValueIds.length > 0) { - const placeholders = specValueIds.map(() => '?').join(','); - const [specDetails] = await getDB().execute( - `SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name - FROM spec_values sv - LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id - WHERE sv.id IN (${placeholders}) - ORDER BY sn.sort_order, sv.sort_order`, - specValueIds - ); - - combination.spec_details = specDetails; - } else { - combination.spec_details = []; - } - - // 计算实际价格 - combination.actual_price = combination.base_price + (combination.price_adjustment || 0); - combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0); - combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0); - combination.is_available = combination.stock > 0; - } - - res.json({ - success: true, - data: combinations - }); - } catch (error) { - console.error('获取规格组合失败:', error); - res.status(500).json({ success: false, message: '获取规格组合失败' }); - } -}); - -/** - * @swagger - * /specifications/combinations/{id}: - * get: - * summary: 获取单个规格组合详情 - * tags: [Specifications] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 规格组合ID - * responses: - * 200: - * description: 成功获取规格组合详情 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: object - * properties: - * id: - * type: integer - * product_id: - * type: integer - * combination_key: - * type: string - * spec_values: - * type: array - * items: - * type: integer - * price_adjustment: - * type: integer - * points_adjustment: - * type: integer - * rongdou_adjustment: - * type: integer - * stock: - * type: integer - * sku_code: - * type: string - * barcode: - * type: string - * weight: - * type: number - * volume: - * type: number - * status: - * type: string - * spec_details: - * type: array - * items: - * type: object - * actual_price: - * type: number - * actual_points_price: - * type: number - * actual_rongdou_price: - * type: number - * is_available: - * type: boolean - * 404: - * description: 规格组合不存在 - * 500: - * description: 服务器错误 - * delete: - * summary: 删除规格组合 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 规格组合ID - * responses: - * 200: - * description: 删除成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * example: 规格组合删除成功 - * 404: - * description: 规格组合不存在 - * 500: - * description: 服务器错误 - */ -router.get('/combinations/:id', async (req, res) => { - try { - const { id } = req.params; - - // 获取规格组合详情 - const [combinations] = await getDB().execute( - `SELECT psc.*, p.name as product_name, p.price as base_price, - p.points_price as base_points_price, p.rongdou_price as base_rongdou_price - FROM product_spec_combinations psc - LEFT JOIN products p ON psc.product_id = p.id - WHERE psc.id = ?`, - [id] - ); - - if (combinations.length === 0) { - return res.status(404).json({ success: false, message: '规格组合不存在' }); - } - - const combination = combinations[0]; - - // 解析规格值并获取详细信息 - let specValueIds; +router.get('/names', auth, async (req, res) => { try { - if (!combination.spec_values) { - specValueIds = []; - } else if (typeof combination.spec_values === 'string') { - try { - specValueIds = JSON.parse(combination.spec_values); - } catch { - specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + const {status = 'active'} = req.query; + const {id: created_id} = req.user; + let query = `SELECT * + FROM spec_names + WHERE created_id = ${created_id}`; + const params = []; + + if (status) { + query += ' and status = ?'; + params.push(status); } - } else if (Buffer.isBuffer(combination.spec_values)) { - const strValue = combination.spec_values.toString(); - try { - specValueIds = JSON.parse(strValue); - } catch { - specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); - } - } else { - specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : []; - } + + query += ' ORDER BY sort_order, id'; + + const [specNames] = await getDB().execute(query, params); + + res.json({ + success: true, + data: specNames + }); } catch (error) { - console.error('解析规格值失败:', combination.spec_values, error); - specValueIds = []; + console.error('获取规格名称失败:', error); + res.status(500).json({success: false, message: '获取规格名称失败'}); } - - if (specValueIds && specValueIds.length > 0) { - const placeholders = specValueIds.map(() => '?').join(','); - const [specDetails] = await getDB().execute( - `SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name - FROM spec_values sv - LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id - WHERE sv.id IN (${placeholders}) - ORDER BY sn.sort_order, sv.sort_order`, - specValueIds - ); - - combination.spec_details = specDetails; - } else { - combination.spec_details = []; - } - - // 计算实际价格 - combination.actual_price = combination.base_price + (combination.price_adjustment || 0); - combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0); - combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0); - combination.is_available = combination.stock > 0; - - res.json({ - success: true, - data: combination - }); - } catch (error) { - console.error('获取规格组合详情失败:', error); - res.status(500).json({ success: false, message: '获取规格组合详情失败' }); - } }); -/** - * @swagger - * /specifications/combinations/{id}: - * delete: - * summary: 删除规格组合 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 规格组合ID - * responses: - * 200: - * description: 删除成功 - * 404: - * description: 规格组合不存在 - */ -router.delete('/combinations/:id', auth, adminAuth, async (req, res) => { - try { - const { id } = req.params; - - // 检查规格组合是否存在 - const [existingCombination] = await getDB().execute( - 'SELECT id FROM product_spec_combinations WHERE id = ?', - [id] - ); - - if (existingCombination.length === 0) { - return res.status(404).json({ success: false, message: '规格组合不存在' }); - } - - // 删除规格组合 - await getDB().execute( - 'DELETE FROM product_spec_combinations WHERE id = ?', - [id] - ); - - res.json({ - success: true, - message: '规格组合删除成功' - }); - } catch (error) { - console.error('删除规格组合失败:', error); - res.status(500).json({ success: false, message: '删除规格组合失败' }); - } -}); -/** - * @swagger - * /specifications/combinations/{id}: - * put: - * summary: 更新规格组合 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: 规格组合ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * price_adjustment: - * type: integer - * points_adjustment: - * type: integer - * rongdou_adjustment: - * type: integer - * stock: - * type: integer - * sku_code: - * type: string - * barcode: - * type: string - * weight: - * type: number - * volume: - * type: number - * status: - * type: string - * enum: [active, inactive] - * responses: - * 200: - * description: 规格组合更新成功 - * 404: - * description: 规格组合不存在 - */ -router.put('/combinations/:id', auth, adminAuth, async (req, res) => { - try { - const { id } = req.params; - const { - price_adjustment, - points_adjustment, - rongdou_adjustment, - stock, - sku_code, - barcode, - weight, - volume, - status - } = req.body; - - // 检查规格组合是否存在 - const [existing] = await getDB().execute( - 'SELECT id FROM product_spec_combinations WHERE id = ?', - [id] - ); - - if (existing.length === 0) { - return res.status(404).json({ success: false, message: '规格组合不存在' }); - } - - // 构建更新字段 - const updateFields = []; - const updateValues = []; - - if (price_adjustment !== undefined) { - updateFields.push('price_adjustment = ?'); - updateValues.push(price_adjustment); - } - - if (points_adjustment !== undefined) { - updateFields.push('points_adjustment = ?'); - updateValues.push(points_adjustment); - } - - if (rongdou_adjustment !== undefined) { - updateFields.push('rongdou_adjustment = ?'); - updateValues.push(rongdou_adjustment); - } - - if (stock !== undefined) { - updateFields.push('stock = ?'); - updateValues.push(stock); - } - - if (sku_code !== undefined) { - updateFields.push('sku_code = ?'); - updateValues.push(sku_code); - } - - if (barcode !== undefined) { - updateFields.push('barcode = ?'); - updateValues.push(barcode); - } - - if (weight !== undefined) { - updateFields.push('weight = ?'); - updateValues.push(weight); - } - - if (volume !== undefined) { - updateFields.push('volume = ?'); - updateValues.push(volume); - } - - if (status !== undefined) { - updateFields.push('status = ?'); - updateValues.push(status); - } - - if (updateFields.length === 0) { - return res.status(400).json({ success: false, message: '没有提供要更新的字段' }); - } - - updateFields.push('updated_at = NOW()'); - updateValues.push(id); - - const updateQuery = `UPDATE product_spec_combinations SET ${updateFields.join(', ')} WHERE id = ?`; - - await getDB().execute(updateQuery, updateValues); - - res.json({ - success: true, - message: '规格组合更新成功' - }); - } catch (error) { - console.error('更新规格组合失败:', error); - res.status(500).json({ success: false, message: '更新规格组合失败' }); - } -}); - -/** - * @swagger - * /specifications/combinations: - * post: - * summary: 创建商品规格组合 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - product_id - * - spec_values - * properties: - * product_id: - * type: integer - * spec_values: - * type: array - * items: - * type: integer - * description: 规格值ID数组 - * price_adjustment: - * type: integer - * default: 0 - * points_adjustment: - * type: integer - * default: 0 - * rongdou_adjustment: - * type: integer - * default: 0 - * stock: - * type: integer - * default: 0 - * sku_code: - * type: string - * barcode: - * type: string - * weight: - * type: number - * volume: - * type: number - * responses: - * 201: - * description: 规格组合创建成功 - */ -router.post('/combinations', auth, adminAuth, async (req, res) => { - try { - const { - product_id, - spec_values, - price_adjustment = 0, - points_adjustment = 0, - rongdou_adjustment = 0, - stock = 0, - sku_code, - barcode, - weight, - volume - } = req.body; - - if (!product_id || !spec_values || !Array.isArray(spec_values) || spec_values.length === 0) { - return res.status(400).json({ success: false, message: '商品ID和规格值数组不能为空' }); - } - - // 生成组合键 - const sortedSpecValues = [...spec_values].sort((a, b) => a - b); - const combinationKey = sortedSpecValues.join('-'); - - const [result] = await getDB().execute( - `INSERT INTO product_spec_combinations - (product_id, combination_key, spec_values, price_adjustment, points_adjustment, - rongdou_adjustment, stock, sku_code, barcode, weight, volume) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [ - product_id, - combinationKey, - JSON.stringify(sortedSpecValues), - price_adjustment, - points_adjustment, - rongdou_adjustment, - stock, - sku_code, - barcode, - weight, - volume - ] - ); - - res.status(201).json({ - success: true, - message: '规格组合创建成功', - data: { id: result.insertId, combination_key: combinationKey } - }); - } catch (error) { - if (error.code === 'ER_DUP_ENTRY') { - return res.status(400).json({ success: false, message: '该规格组合已存在' }); - } - console.error('创建规格组合失败:', error); - res.status(500).json({ success: false, message: '创建规格组合失败' }); - } -}); - -/** - * @swagger - * /specifications/generate-combinations: - * post: - * summary: 为商品生成笛卡尔积规格组合 - * tags: [Specifications] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - product_id - * - spec_name_ids - * properties: - * product_id: - * type: integer - * spec_name_ids: - * type: array - * items: - * type: integer - * description: 规格名称ID数组 - * default_stock: - * type: integer - * default: 0 - * description: 默认库存 - * responses: - * 201: - * description: 规格组合生成成功 - */ -router.post('/generate-combinations', auth, adminAuth, async (req, res) => { - try { - const { product_id, spec_name_ids, default_stock = 0 } = req.body; - - if (!product_id || !spec_name_ids || !Array.isArray(spec_name_ids) || spec_name_ids.length === 0) { - return res.status(400).json({ success: false, message: '商品ID和规格名称ID数组不能为空' }); - } - - // 获取每个规格名称下的所有活跃规格值 - const specValueGroups = []; - - for (const specNameId of spec_name_ids) { - const [specValues] = await getDB().execute( - 'SELECT id FROM spec_values WHERE spec_name_id = ? AND status = "active" ORDER BY sort_order, id', - [specNameId] - ); - - if (specValues.length === 0) { - return res.status(400).json({ - success: false, - message: `规格名称ID ${specNameId} 下没有活跃的规格值` - }); - } - - specValueGroups.push(specValues.map(sv => sv.id)); - } - - // 生成笛卡尔积 - function cartesianProduct(arrays) { - return arrays.reduce((acc, curr) => { - const result = []; - acc.forEach(a => { - curr.forEach(c => { - result.push([...a, c]); - }); - }); - return result; - }, [[]]); - } - - const combinations = cartesianProduct(specValueGroups); - - // 生成所有组合键 - const combinationData = combinations.map(combination => { - const sortedCombination = [...combination].sort((a, b) => a - b); - const combinationKey = sortedCombination.join('-'); - return { - combination: sortedCombination, - key: combinationKey - }; - }); - - // 批量检查已存在的组合 - const existingKeys = new Set(); - if (combinationData.length > 0) { - const keys = combinationData.map(item => item.key); - const placeholders = keys.map(() => '?').join(','); - - const [existingCombinations] = await getDB().execute( - `SELECT combination_key FROM product_spec_combinations - WHERE product_id = ? AND combination_key IN (${placeholders})`, - [product_id, ...keys] - ); - - existingCombinations.forEach(row => { - existingKeys.add(row.combination_key); - }); - } - - // 过滤出需要插入的新组合 - const newCombinations = combinationData.filter(item => !existingKeys.has(item.key)); - - // 批量插入新的规格组合 - let createdCount = 0; - const skippedCount = combinationData.length - newCombinations.length; - - if (newCombinations.length > 0) { - // 使用批量插入提高性能 - const values = []; - const placeholders = []; - - newCombinations.forEach(item => { - values.push( - product_id, - item.key, - JSON.stringify(item.combination), - default_stock +router.post('/names', auth, adminAuth, async (req, res) => { + const db = getDB(); + try { + const {name, display_name, sort_order = 0} = req.body; + const {id: created_id} = req.user; + if (!name || !display_name) { + return res.status(400).json({success: false, message: '规格名称和显示名称不能为空'}); + } + let spec_name = new SelectBuilder().from() + const [result] = await getDB().execute( + 'INSERT INTO spec_names (name, display_name, sort_order,created_id) VALUES (?, ?, ?,?)', + [name, display_name, sort_order,created_id] ); - placeholders.push('(?, ?, ?, ?)'); - }); - - const sql = `INSERT INTO product_spec_combinations - (product_id, combination_key, spec_values, stock) - VALUES ${placeholders.join(', ')}`; - - const [result] = await getDB().execute(sql, values); - createdCount = result.affectedRows; + + res.status(201).json({ + success: true, + message: '规格名称创建成功', + data: {id: result.insertId} + }); + } catch (error) { + if (error.code === 'ER_DUP_ENTRY') { + return res.status(400).json({success: false, message: '规格名称已存在'}); + } + console.error('创建规格名称失败:', error); + res.status(500).json({success: false, message: '创建规格名称失败'}); + } +}); + + +router.delete('/names/:id', auth, adminAuth, async (req, res) => { + try { + const {id} = req.params; + const {id:user_id,role} = req.user + + let specCountQuery = new SelectBuilder() + .from('spec_names') + .select('COUNT(*) as total') + .where('id=?',id) + if(role !== 'admin'){ + specCountQuery.where('created_id=?',user_id) + } + let [spec] = await specCountQuery.execute(db) + if (spec.total === 0) { + return res.status(404).json({success: false, message: '规格名称不存在'}); + } + + // 检查该规格名称下是否还有规格值 + const [specValues] = await getDB().execute( + 'SELECT COUNT(*) as count FROM spec_values WHERE spec_name_id = ?', + [id] + ); + + if (specValues[0].count > 0) { + return res.status(400).json({success: false, message: '该规格名称下还有规格值,请先删除所有规格值'}); + } + // 删除规格名称 + await getDB().execute( + 'DELETE FROM spec_names WHERE id = ?', + [id] + ); + + res.json({ + success: true, + message: '规格名称删除成功' + }); + } catch (error) { + console.error('删除规格名称失败:', error); + res.status(500).json({success: false, message: '删除规格名称失败'}); + } +}); + + +router.get('/values', async (req, res) => { + try { + const {spec_name_id, status = 'active'} = req.query; + + let query = ` + SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name + FROM spec_values sv + LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id + WHERE 1 = 1 + `; + const params = []; + + if (spec_name_id) { + query += ' AND sv.spec_name_id = ?'; + params.push(spec_name_id); + } + + if (status) { + query += ' AND sv.status = ?'; + params.push(status); + } + + query += ' ORDER BY sv.spec_name_id, sv.sort_order, sv.id'; + + const [specValues] = await getDB().execute(query, params); + + res.json({ + success: true, + data: specValues + }); + } catch (error) { + console.error('获取规格值失败:', error); + res.status(500).json({success: false, message: '获取规格值失败'}); + } +}); + + +router.post('/values', auth, adminAuth, async (req, res) => { + try { + const {spec_name_id, value, display_value, color_code, image_url, sort_order = 0} = req.body; + + if (!spec_name_id || !value || !display_value) { + return res.status(400).json({success: false, message: '规格名称ID、规格值和显示值不能为空'}); + } + + const [result] = await getDB().execute( + `INSERT INTO spec_values (spec_name_id, value, display_value, color_code, image_url, sort_order) + VALUES (?, ?, ?, ?, ?, ?)`, + [spec_name_id, value, display_value, color_code || null, image_url || null, sort_order] + ); + + res.status(201).json({ + success: true, + message: '规格值创建成功', + data: {id: result.insertId} + }); + } catch (error) { + if (error.code === 'ER_DUP_ENTRY') { + return res.status(400).json({success: false, message: '该规格名称下的规格值已存在'}); + } + console.error('创建规格值失败:', error); + res.status(500).json({success: false, message: '创建规格值失败'}); + } +}); + + +router.get('/combinations/:productId', async (req, res) => { + try { + const {productId} = req.params; + const {status = 'active'} = req.query; + + // 获取商品的规格组合 + let query = ` + SELECT psc.*, + p.name as product_name, + p.price as base_price, + p.points_price as base_points_price, + p.rongdou_price as base_rongdou_price + FROM product_spec_combinations psc + LEFT JOIN products p ON psc.product_id = p.id + WHERE psc.product_id = ? + `; + const params = [productId]; + + if (status) { + query += ' AND psc.status = ?'; + params.push(status); + } + + query += ' ORDER BY psc.combination_key'; + + const [combinations] = await getDB().execute(query, params); + + // 为每个组合获取详细的规格值信息 + for (let combination of combinations) { + let specValueIds; + try { + // 处理不同的数据格式 + if (!combination.spec_values) { + specValueIds = []; + } else if (typeof combination.spec_values === 'string') { + // 如果是字符串,尝试JSON解析,失败则按逗号分隔处理 + try { + specValueIds = JSON.parse(combination.spec_values); + } catch { + // 按逗号分隔的字符串处理 + specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + } + } else if (Buffer.isBuffer(combination.spec_values)) { + // 如果是Buffer,转换为字符串后处理 + const strValue = combination.spec_values.toString(); + try { + specValueIds = JSON.parse(strValue); + } catch { + specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + } + } else { + // 其他情况,尝试直接使用 + specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : []; + } + } catch (error) { + console.error('解析规格值失败:', combination.spec_values, error); + specValueIds = []; + } + + if (specValueIds && specValueIds.length > 0) { + const placeholders = specValueIds.map(() => '?').join(','); + const [specDetails] = await getDB().execute( + `SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name + FROM spec_values sv + LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id + WHERE sv.id IN (${placeholders}) + ORDER BY sn.sort_order, sv.sort_order`, + specValueIds + ); + + combination.spec_details = specDetails; + } else { + combination.spec_details = []; + } + + // 计算实际价格 + combination.actual_price = combination.base_price + (combination.price_adjustment || 0); + combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0); + combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0); + combination.is_available = combination.stock > 0; + } + + res.json({ + success: true, + data: combinations + }); + } catch (error) { + console.error('获取规格组合失败:', error); + res.status(500).json({success: false, message: '获取规格组合失败'}); + } +}); + +router.get('/combinations/:id', async (req, res) => { + try { + const {id} = req.params; + + // 获取规格组合详情 + const [combinations] = await getDB().execute( + `SELECT psc.*, + p.name as product_name, + p.price as base_price, + p.points_price as base_points_price, + p.rongdou_price as base_rongdou_price + FROM product_spec_combinations psc + LEFT JOIN products p ON psc.product_id = p.id + WHERE psc.id = ?`, + [id] + ); + + if (combinations.length === 0) { + return res.status(404).json({success: false, message: '规格组合不存在'}); + } + + const combination = combinations[0]; + + // 解析规格值并获取详细信息 + let specValueIds; + try { + if (!combination.spec_values) { + specValueIds = []; + } else if (typeof combination.spec_values === 'string') { + try { + specValueIds = JSON.parse(combination.spec_values); + } catch { + specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + } + } else if (Buffer.isBuffer(combination.spec_values)) { + const strValue = combination.spec_values.toString(); + try { + specValueIds = JSON.parse(strValue); + } catch { + specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + } + } else { + specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : []; + } + } catch (error) { + console.error('解析规格值失败:', combination.spec_values, error); + specValueIds = []; + } + + if (specValueIds && specValueIds.length > 0) { + const placeholders = specValueIds.map(() => '?').join(','); + const [specDetails] = await getDB().execute( + `SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name + FROM spec_values sv + LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id + WHERE sv.id IN (${placeholders}) + ORDER BY sn.sort_order, sv.sort_order`, + specValueIds + ); + + combination.spec_details = specDetails; + } else { + combination.spec_details = []; + } + + // 计算实际价格 + combination.actual_price = combination.base_price + (combination.price_adjustment || 0); + combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0); + combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0); + combination.is_available = combination.stock > 0; + + res.json({ + success: true, + data: combination + }); + } catch (error) { + console.error('获取规格组合详情失败:', error); + res.status(500).json({success: false, message: '获取规格组合详情失败'}); + } +}); + + +router.delete('/combinations/:id', auth, adminAuth, async (req, res) => { + try { + const {id} = req.params; + + // 检查规格组合是否存在 + const [existingCombination] = await getDB().execute( + 'SELECT id FROM product_spec_combinations WHERE id = ?', + [id] + ); + + if (existingCombination.length === 0) { + return res.status(404).json({success: false, message: '规格组合不存在'}); + } + + // 删除规格组合 + await getDB().execute( + 'DELETE FROM product_spec_combinations WHERE id = ?', + [id] + ); + + res.json({ + success: true, + message: '规格组合删除成功' + }); + } catch (error) { + console.error('删除规格组合失败:', error); + res.status(500).json({success: false, message: '删除规格组合失败'}); + } +}); + + +router.put('/combinations/:id', auth, adminAuth, async (req, res) => { + try { + const {id} = req.params; + const { + price_adjustment, + points_adjustment, + rongdou_adjustment, + stock, + sku_code, + barcode, + weight, + volume, + status + } = req.body; + + // 检查规格组合是否存在 + const [existing] = await getDB().execute( + 'SELECT id FROM product_spec_combinations WHERE id = ?', + [id] + ); + + if (existing.length === 0) { + return res.status(404).json({success: false, message: '规格组合不存在'}); + } + + // 构建更新字段 + const updateFields = []; + const updateValues = []; + + if (price_adjustment !== undefined) { + updateFields.push('price_adjustment = ?'); + updateValues.push(price_adjustment); + } + + if (points_adjustment !== undefined) { + updateFields.push('points_adjustment = ?'); + updateValues.push(points_adjustment); + } + + if (rongdou_adjustment !== undefined) { + updateFields.push('rongdou_adjustment = ?'); + updateValues.push(rongdou_adjustment); + } + + if (stock !== undefined) { + updateFields.push('stock = ?'); + updateValues.push(stock); + } + + if (sku_code !== undefined) { + updateFields.push('sku_code = ?'); + updateValues.push(sku_code); + } + + if (barcode !== undefined) { + updateFields.push('barcode = ?'); + updateValues.push(barcode); + } + + if (weight !== undefined) { + updateFields.push('weight = ?'); + updateValues.push(weight); + } + + if (volume !== undefined) { + updateFields.push('volume = ?'); + updateValues.push(volume); + } + + if (status !== undefined) { + updateFields.push('status = ?'); + updateValues.push(status); + } + + if (updateFields.length === 0) { + return res.status(400).json({success: false, message: '没有提供要更新的字段'}); + } + + updateFields.push('updated_at = NOW()'); + updateValues.push(id); + + const updateQuery = `UPDATE product_spec_combinations + SET ${updateFields.join(', ')} + WHERE id = ?`; + + await getDB().execute(updateQuery, updateValues); + + res.json({ + success: true, + message: '规格组合更新成功' + }); + } catch (error) { + console.error('更新规格组合失败:', error); + res.status(500).json({success: false, message: '更新规格组合失败'}); + } +}); + + +router.post('/combinations', auth, adminAuth, async (req, res) => { + try { + const { + product_id, + spec_values, + price_adjustment = 0, + points_adjustment = 0, + rongdou_adjustment = 0, + stock = 0, + sku_code, + barcode, + weight, + volume + } = req.body; + + if (!product_id || !spec_values || !Array.isArray(spec_values) || spec_values.length === 0) { + return res.status(400).json({success: false, message: '商品ID和规格值数组不能为空'}); + } + + // 生成组合键 + const sortedSpecValues = [...spec_values].sort((a, b) => a - b); + const combinationKey = sortedSpecValues.join('-'); + + const [result] = await getDB().execute( + `INSERT INTO product_spec_combinations + (product_id, combination_key, spec_values, price_adjustment, points_adjustment, + rongdou_adjustment, stock, sku_code, barcode, weight, volume) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + product_id, + combinationKey, + JSON.stringify(sortedSpecValues), + price_adjustment, + points_adjustment, + rongdou_adjustment, + stock, + sku_code, + barcode, + weight, + volume + ] + ); + + res.status(201).json({ + success: true, + message: '规格组合创建成功', + data: {id: result.insertId, combination_key: combinationKey} + }); + } catch (error) { + if (error.code === 'ER_DUP_ENTRY') { + return res.status(400).json({success: false, message: '该规格组合已存在'}); + } + console.error('创建规格组合失败:', error); + res.status(500).json({success: false, message: '创建规格组合失败'}); + } +}); + + +router.post('/generate-combinations', auth, adminAuth, async (req, res) => { + try { + const {product_id, spec_name_ids, default_stock = 0} = req.body; + + if (!product_id || !spec_name_ids || !Array.isArray(spec_name_ids) || spec_name_ids.length === 0) { + return res.status(400).json({success: false, message: '商品ID和规格名称ID数组不能为空'}); + } + + // 获取每个规格名称下的所有活跃规格值 + const specValueGroups = []; + + for (const specNameId of spec_name_ids) { + const [specValues] = await getDB().execute( + 'SELECT id FROM spec_values WHERE spec_name_id = ? AND status = "active" ORDER BY sort_order, id', + [specNameId] + ); + + if (specValues.length === 0) { + return res.status(400).json({ + success: false, + message: `规格名称ID ${specNameId} 下没有活跃的规格值` + }); + } + + specValueGroups.push(specValues.map(sv => sv.id)); + } + + // 生成笛卡尔积 + function cartesianProduct(arrays) { + return arrays.reduce((acc, curr) => { + const result = []; + acc.forEach(a => { + curr.forEach(c => { + result.push([...a, c]); + }); + }); + return result; + }, [[]]); + } + + const combinations = cartesianProduct(specValueGroups); + + // 生成所有组合键 + const combinationData = combinations.map(combination => { + const sortedCombination = [...combination].sort((a, b) => a - b); + const combinationKey = sortedCombination.join('-'); + return { + combination: sortedCombination, + key: combinationKey + }; + }); + + // 批量检查已存在的组合 + const existingKeys = new Set(); + if (combinationData.length > 0) { + const keys = combinationData.map(item => item.key); + const placeholders = keys.map(() => '?').join(','); + + const [existingCombinations] = await getDB().execute( + `SELECT combination_key + FROM product_spec_combinations + WHERE product_id = ? + AND combination_key IN (${placeholders})`, + [product_id, ...keys] + ); + + existingCombinations.forEach(row => { + existingKeys.add(row.combination_key); + }); + } + + // 过滤出需要插入的新组合 + const newCombinations = combinationData.filter(item => !existingKeys.has(item.key)); + + // 批量插入新的规格组合 + let createdCount = 0; + const skippedCount = combinationData.length - newCombinations.length; + + if (newCombinations.length > 0) { + // 使用批量插入提高性能 + const values = []; + const placeholders = []; + + newCombinations.forEach(item => { + values.push( + product_id, + item.key, + JSON.stringify(item.combination), + default_stock + ); + placeholders.push('(?, ?, ?, ?)'); + }); + + const sql = `INSERT INTO product_spec_combinations + (product_id, combination_key, spec_values, stock) + VALUES ${placeholders.join(', ')}`; + + const [result] = await getDB().execute(sql, values); + createdCount = result.affectedRows; + } + + res.status(201).json({ + success: true, + message: '规格组合生成完成', + data: { + total_combinations: combinations.length, + created: createdCount, + skipped: skippedCount + } + }); + } catch (error) { + console.error('生成规格组合失败:', error); + res.status(500).json({success: false, message: '生成规格组合失败'}); } - - res.status(201).json({ - success: true, - message: '规格组合生成完成', - data: { - total_combinations: combinations.length, - created: createdCount, - skipped: skippedCount - } - }); - } catch (error) { - console.error('生成规格组合失败:', error); - res.status(500).json({ success: false, message: '生成规格组合失败' }); - } }); module.exports = router; \ No newline at end of file diff --git a/routes/supplier.js b/routes/supplier.js new file mode 100644 index 0000000..16c345d --- /dev/null +++ b/routes/supplier.js @@ -0,0 +1,255 @@ +//供应商接口 +const express = require('express'); +const {getDB} = require('../database'); +const {auth, adminAuth} = require('../middleware/auth'); +const router = express.Router(); +const {SelectBuilder, InsertBuilder, UpdateBuilder} = require('../config/dbv2') +const bcrypt = require('bcryptjs'); + + +/* +* username 账号 +* password 密码 +* avatar 头像 +* real_name 姓名 +* id_card 身份证号 +* wechat_qr 微信二维码 +* alipay_qr 支付宝收款码 +* bank_card 银行卡号 +* unionpay_qr 云闪付收款码 +* phone 手机号 +* +* */ +router.post('/add', auth, adminAuth, async (req, res) => { + const db = getDB(); + try { + const { + username, + password, + avatar, + real_name, + id_card, + wechat_qr, + alipay_qr, + bank_card, + unionpay_qr, + phone + } = req.body; + console.log('12333333') + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(phone)) { + return res.status(400).json({success: false, message: '手机号格式不正确'}); + } + let userCountQuery = new SelectBuilder() + .from('users') + .select('COUNT(*) as total') + .where(`(username=? or phone=?)`, username, phone) + .where('is_delete=?', false) + let [user] = await userCountQuery.execute(db); + if (user.total > 0) { + res.status(400).send({ + success: false, message: '手机号或者用户名重复' + }) + } + const hashedPassword = await bcrypt.hash(password, 10); + let insetObj = { + username, + password: hashedPassword, + real_name, + id_card, + wechat_qr, + alipay_qr, + bank_card, + unionpay_qr, + avatar, + phone, + user_type: 'supplier', + audit_status: 'approved' + } + console.log(insetObj, '111') + await db.query('START TRANSACTION'); + let sqlResult = new InsertBuilder() + .into('users') + .values(insetObj) + let result = await sqlResult.execute(db); + console.log(result) + await db.query('COMMIT'); + if (result.affectedRows > 0) { + res.json({success: true, message: '创建成功'}) + } else { + res.json({success: false, message: '系统错误请联系管理员'}) + await db.query('ROLLBACK'); + } + + } catch (err) { + console.log(err) + await db.query('ROLLBACK'); + } + +}) +/* +* 修改供应商 +* username 账号 +* password 密码 +* avatar 头像 +* real_name 姓名 +* id_card 身份证号 +* wechat_qr 微信二维码 +* alipay_qr 支付宝收款码 +* bank_card 银行卡号 +* unionpay_qr 云闪付收款码 +* phone 手机号 +* id 供应商id +* audit_status 审核状态 'pending','approved','rejected' +* */ +router.put('/edit', auth, adminAuth, async (req, res) => { + const db = getDB(); + try { + const { + username, + password, + id, + avatar, + real_name, + id_card, + wechat_qr, + bank_card, + unionpay_qr, + phone, + audit_status + } = req.body; + if (!id) { + return res.status(400).send({ + success: false, + message: '供应商id未填写' + }) + } + let userCountQuery = new SelectBuilder() + .from('users') + .select('COUNT(*) as total') + .where(`(username=? or phone=?)`, username, phone) + .where('id!=?', id) + .where('is_delete=?', false) + let userInfo = await userCountQuery.execute(db); + if (userInfo.total > 0) { + res.status(400).send({ + success: false, + message: '手机号或用户名重复请重新填写' + }) + } + // 动态组装更新对象 + + let upUser = new UpdateBuilder() + .update('users') + .where('id=?', id) + const fields = [ + 'username', + 'avatar', + 'real_name', + 'id_card', + 'wechat_qr', + 'bank_card', + 'unionpay_qr', + 'phone', + 'audit_status' + ]; + // 遍历允许的字段,存在才 set + for (const field of fields) { + if (req.body[field]) { + upUser.set(field, req.body[field]); + } + } + + // 密码单独处理(异步 hash) + if (password) { + upUser.set('password', await bcrypt.hash(password, 10)); + } + await upUser.execute(db); + res.json({success: true, message: '更新成功'}); + } catch (e) { + console.log(e) + res.status(500).send({ + success: false, + message: '系统错误请联系管理员' + }) + } + +}) +/* +* 供应商详情 +* id 供应商id +* */ +router.get('/details', async (req, res) => { + const {id} = req.query; + const db = getDB(); + try { + if (!id) { + return res.status(400).send({ + success: false, + message: '供应商id不能为空' + }) + } + let [user] = await new SelectBuilder() + .from('users') + .where('id=?', id) + .where('user_type=?', 'supplier') + .where('is_delete=?', false) + .execute(db) + if (user) { + delete user.password + } else { + return res.status(400).json({success: false, message: '无此用户'}) + } + + res.json({data: user, success: true, message: '查询成功'}) + } catch (err) { + return res.status(500).json({success: false, message: '系统错误,请联系管理员'}) + } + +}) +/* +* 供应商列表 +* page 1 页数 +* limit 20 每页数量 +* name 手机号,账号 +* */ +router.get('/list', async (req, res) => { + const db = getDB(); + try { + let {page = 1, limit = 20, name} = req.query; + console.log(page, limit, name); + let userCountQuery = new SelectBuilder() + .from('users') + .where('user_type=?', 'supplier') + .where('is_delete=?', false) + if (name) { + userCountQuery.where('(username LIKE ? or phone LIKE ?)', name, name) + } + const data = await userCountQuery.paginateWithCount(db, page, limit); + return res.json(data) + } catch (err) { + console.log(err) + return res.status(500).json({success: false, message: err}) + } +}) +/* +* 删除供应商 +* id +* */ +router.delete('/delete', auth, adminAuth, async (req, res) => { + const db = getDB(); + try { + const {id} = req.query; + await new UpdateBuilder() + .update('users') + .set('is_delete', true) + .where('id=?', id) + .execute(db) + res.json({success: true, message: '删除成功'}) + } catch (err) { + res.status(500).send({error: err}) + } + +}) +module.exports = router; diff --git a/routes/system.js b/routes/system.js deleted file mode 100644 index ffdaea0..0000000 --- a/routes/system.js +++ /dev/null @@ -1,321 +0,0 @@ -const express = require('express'); -const { auth } = require('../middleware/auth'); -const { validateQuery, validate } = require('../middleware/validation'); -const { logger } = require('../config/logger'); -const { HTTP_STATUS } = require('../config/constants'); -const { getDB } = require('../database'); -const Joi = require('joi'); - -const router = express.Router(); - -/** - * 系统设置验证规则 - */ -const systemSchemas = { - // 更新系统设置 - updateSettings: Joi.object({ - basic: Joi.object({ - siteName: Joi.string().max(100).allow(''), - siteDescription: Joi.string().max(500).allow(''), - siteKeywords: Joi.string().max(200).allow(''), - siteLogo: Joi.string().allow(''), - siteFavicon: Joi.string().allow(''), - - icp: Joi.string().max(100).allow('') - }).optional(), - features: Joi.object({ - allowRegister: Joi.boolean(), - allowTransfer: Joi.boolean(), - allowExchange: Joi.boolean(), - allowReview: Joi.boolean(), - allowComment: Joi.boolean() - }).optional(), - - security: Joi.object({ - maxLoginAttempts: Joi.number().integer().min(1).max(100), - lockoutDuration: Joi.number().integer().min(1).max(86400), - ipWhitelist: Joi.string().allow('') - }).optional() - }) -}; - -/** - * 获取系统设置 (公开接口,不需要认证) - * GET /api/system/settings - */ -router.get('/settings', async (req, res, next) => { - try { - - const db = getDB(); - - // 获取系统设置 - const [settings] = await db.execute( - 'SELECT setting_key, setting_value FROM system_settings' - ); - - // 组织设置数据 - const settingsData = { - basic: { - siteName: '', - siteDescription: '', - siteKeywords: '', - siteLogo: '', - siteFavicon: '', - - icp: '' - }, - features: { - allowRegister: true, - allowTransfer: true, - allowExchange: true, - allowReview: true, - allowComment: true - }, - - security: { - maxLoginAttempts: 5, - lockoutDuration: 300, - ipWhitelist: '' - } - }; - - // 填充数据库中的设置 - settings.forEach(setting => { - const keys = setting.setting_key.split('.'); - if (keys.length === 2 && settingsData[keys[0]]) { - try { - // 尝试解析JSON值,如果失败则使用原始值 - settingsData[keys[0]][keys[1]] = JSON.parse(setting.setting_value); - } catch { - settingsData[keys[0]][keys[1]] = setting.setting_value; - } - } - }); - - logger.info('System settings retrieved', { - settingsCount: settings.length - }); - - res.json({ - success: true, - data: settingsData - }); - } catch (error) { - next(error); - } -}); - -/** - * 更新系统设置 - * PUT /api/system/settings - */ -router.put('/settings', - auth, - validate(systemSchemas.updateSettings), - async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(HTTP_STATUS.FORBIDDEN).json({ - success: false, - message: '权限不足' - }); - } - - const db = getDB(); - const settings = req.body; - - // 开始事务 - await db.beginTransaction(); - - try { - // 更新设置 - for (const [category, categorySettings] of Object.entries(settings)) { - for (const [key, value] of Object.entries(categorySettings)) { - const settingKey = `${category}.${key}`; - const settingValue = JSON.stringify(value); - - await db.execute( - `INSERT INTO system_settings (setting_key, setting_value, updated_at) - VALUES (?, ?, NOW()) - ON DUPLICATE KEY UPDATE - setting_value = VALUES(setting_value), - updated_at = VALUES(updated_at)`, - [settingKey, settingValue] - ); - } - } - - await db.commit(); - - logger.info('System settings updated', { - userId: req.user.id, - categories: Object.keys(settings) - }); - - res.json({ - success: true, - message: '系统设置更新成功' - }); - } catch (error) { - await db.rollback(); - throw error; - } - } catch (error) { - next(error); - } - } -); - -/** - * 获取系统信息 - * GET /api/system/info - */ -router.get('/info', auth, async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(HTTP_STATUS.FORBIDDEN).json({ - success: false, - message: '权限不足' - }); - } - - const systemInfo = { - version: '1.0.0', - nodeVersion: process.version, - platform: process.platform, - uptime: process.uptime(), - memoryUsage: process.memoryUsage(), - timestamp: new Date().toISOString() - }; - - res.json({ - success: true, - data: systemInfo - }); - } catch (error) { - next(error); - } -}); - -/** - * 获取维护模式状态(公开接口) - * GET /api/system/maintenance-status - */ -router.get('/maintenance-status', async (req, res, next) => { - try { - const db = getDB(); - - // 从系统设置表获取维护模式状态 - const [rows] = await db.execute( - 'SELECT setting_value FROM system_settings WHERE setting_key = ?', - ['maintenance_mode'] - ); - - const maintenanceMode = rows.length > 0 ? rows[0].setting_value === 'true' : false; - - res.json({ - success: true, - data: { - maintenance_mode: maintenanceMode - } - }); - - } catch (error) { - console.error('获取维护模式状态失败:', error); - next(error); - } -}); - -/** - * 获取维护模式状态(管理员接口) - * GET /api/system/admin/maintenance-status - */ -router.get('/admin/maintenance-status', auth, async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({ - success: false, - error: { - code: 'FORBIDDEN', - message: '权限不足' - } - }); - } - - const db = getDB(); - - // 从系统设置表获取维护模式状态 - const [rows] = await db.execute( - 'SELECT setting_value FROM system_settings WHERE setting_key = ?', - ['maintenance_mode'] - ); - - const maintenanceMode = rows.length > 0 ? rows[0].setting_value === 'true' : false; - - res.json({ - success: true, - data: { - maintenance_mode: maintenanceMode - } - }); - - } catch (error) { - console.error('获取维护模式状态失败:', error); - next(error); - } -}); - -/** - * 切换维护模式 - * POST /api/system/toggle-maintenance - */ -router.post('/toggle-maintenance', auth, async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({ - success: false, - error: { - code: 'FORBIDDEN', - message: '权限不足' - } - }); - } - - const { maintenance_mode } = req.body; - const db = getDB(); - - // 更新或插入维护模式设置 - await db.execute( - `INSERT INTO system_settings (setting_key, setting_value, updated_at) - VALUES ('maintenance_mode', ?, NOW()) - ON DUPLICATE KEY UPDATE - setting_value = VALUES(setting_value), - updated_at = NOW()`, - [maintenance_mode ? 'true' : 'false'] - ); - - logger.info('Maintenance mode toggled', { - userId: req.user.id, - username: req.user.username, - maintenanceMode: maintenance_mode - }); - - res.json({ - success: true, - data: { - maintenance_mode: maintenance_mode - }, - message: maintenance_mode ? '维护模式已开启' : '维护模式已关闭' - }); - - } catch (error) { - console.error('切换维护模式失败:', error); - next(error); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/transfers.js b/routes/transfers.js deleted file mode 100644 index 91ff2fd..0000000 --- a/routes/transfers.js +++ /dev/null @@ -1,1208 +0,0 @@ -const express = require('express'); -const transferService = require('../services/transferService'); -const {auth: authenticateToken} = require('../middleware/auth'); -const {validate, validateQuery, transferSchemas, commonSchemas} = require('../middleware/validation'); -const {logger} = require('../config/logger'); -const {HTTP_STATUS} = require('../config/constants'); -const {getDB} = require('../database'); -const multer = require('multer'); -const path = require('path'); -const dayjs = require('dayjs'); - -const router = express.Router(); - - -router.get('/', - authenticateToken, - validateQuery(transferSchemas.query), - async (req, res, next) => { - try { - const {page, limit, status, start_date, end_date, search, sort, order} = req.query; - - const filters = { - status, - start_date, - end_date, - search - }; - - // 非管理员只能查看自己相关的转账 - if (req.user.role !== 'admin') { - filters.user_id = req.user.id; - } - - const result = await transferService.getTransfers(filters, {page, limit, sort, order}); - - logger.info('Transfer list requested', { - userId: req.user.id, - filters, - resultCount: result.transfers.length - }); - - res.json({ - success: true, - data: result - }); - } catch (error) { - next(error); - } - } -); - -router.get('/history', authenticateToken, async (req, res, next) => { - try { - const {page, limit, start_date, end_date, search, sort, order} = req.query; - - const filters = { - start_date, - end_date, - search - }; - - // 非管理员只能查看自己相关的转账 - if (req.user.role !== 'admin') { - filters.user_id = req.user.id; - } - - const result = await transferService.getTransfersHistory(filters, {page, limit, sort, order}); - - res.json({ - success: true, - data: result - }); - } catch (error) { - next(error); - } -}) - -router.get('/list', - authenticateToken, - validateQuery(transferSchemas.query), - async (req, res, next) => { - try { - const {page, limit, status, transfer_type, start_date, end_date, sort, order} = req.query; - - const filters = { - status, - transfer_type, - start_date, - end_date - }; - - // 非管理员只能查看自己相关的转账 - if (req.user.role !== 'admin') { - filters.user_id = req.user.id; - } - - const result = await transferService.getTransfers(filters, {page, limit, sort, order}); - - logger.info('Transfer list requested', { - userId: req.user.id, - filters, - resultCount: result.transfers.length - }); - - res.json({ - success: true, - data: result - }); - } catch (error) { - next(error); - } - } -); - - -router.get('/public-account', authenticateToken, async (req, res) => { - try { - const db = getDB(); - const [publicUser] = await db.execute(` - SELECT id, username, real_name, balance - FROM users - WHERE username = 'public_account' - AND is_system_account = TRUE - `); - - if (publicUser.length === 0) { - return res.status(404).json({success: false, message: '公户不存在'}); - } - - res.json({success: true, data: publicUser[0]}); - } catch (error) { - console.error('获取公户信息失败:', error); - res.status(500).json({success: false, message: '服务器错误'}); - } -}); - - -router.post('/create', - authenticateToken, - validate(transferSchemas.create), - async (req, res, next) => { - try { - const result = await transferService.createTransfer(req.user.id, req.body); - - logger.info('Transfer creation requested', { - userId: req.user.id, - transferId: result.transfer_id, - amount: req.body.amount - }); - - res.status(HTTP_STATUS.CREATED).json({ - success: true, - message: '转账记录创建成功,等待确认', - data: result - }); - } catch (error) { - next(error); - } - } -); - - -router.post('/admin/create', - authenticateToken, - async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const {from_user_id, to_user_id, amount, transfer_type, description} = req.body; - - // 验证必填字段 - if (!from_user_id || !to_user_id || !amount || !transfer_type) { - return res.status(400).json({success: false, message: '缺少必填字段'}); - } - - const result = await transferService.createTransfer(from_user_id, { - to_user_id, - amount, - transfer_type, - description: description || '管理员分配转账' - }); - - logger.info('Admin transfer creation requested', { - adminId: req.user.id, - fromUserId: from_user_id, - toUserId: to_user_id, - transferId: result.transfer_id, - amount: amount - }); - - res.status(HTTP_STATUS.CREATED).json({ - success: true, - message: '转账分配成功', - data: result - }); - } catch (error) { - next(error); - } - } -); - -// 确认转账 -router.post('/confirm', - authenticateToken, - validate(transferSchemas.confirm), - async (req, res, next) => { - try { - const {transfer_id, note} = req.body; - - await transferService.confirmTransfer(transfer_id, note, req.user.id); - - logger.info('Transfer confirmed', { - transferId: transfer_id, - operatorId: req.user.id - }); - - res.json({ - success: true, - message: '转账确认成功' - }); - } catch (error) { - next(error); - } - } -); - -// 确认转账(路径参数形式) -router.post('/confirm/:id', - authenticateToken, - async (req, res, next) => { - try { - const transfer_id = req.params.id; - const {action, note} = req.body; - - // 验证action参数 - if (action !== 'confirm') { - return res.status(400).json({ - success: false, - message: 'action参数必须为confirm' - }); - } - - await transferService.confirmTransfer(transfer_id, note, req.user.id); - - logger.info('Transfer confirmed via path param', { - transferId: transfer_id, - operatorId: req.user.id - }); - - res.json({ - success: true, - message: '转账确认成功' - }); - } catch (error) { - next(error); - } - } -); - -// 拒绝转账 -router.post('/reject', - authenticateToken, - validate(transferSchemas.reject), - async (req, res, next) => { - try { - const {transfer_id, note = ''} = req.body; - - await transferService.rejectTransfer(transfer_id, note, req.user.id); - - logger.info('Transfer rejected', { - transferId: transfer_id, - operatorId: req.user.id - }); - - res.json({ - success: true, - message: '转账已拒绝' - }); - } catch (error) { - next(error); - } - } -); - -// 用户确认收到转账 -router.post('/confirm-received', - authenticateToken, - async (req, res, next) => { - try { - const {transfer_id} = req.body; - - if (!transfer_id) { - return res.status(400).json({success: false, message: '缺少转账ID'}); - } - - await transferService.confirmReceived(transfer_id, req.user.id); - - logger.info('Transfer received confirmed by user', { - transferId: transfer_id, - userId: req.user.id - }); - - res.json({ - success: true, - message: '已确认收到转账,余额已更新' - }); - } catch (error) { - next(error); - } - } -); - -// 用户确认未收到转账 -router.post('/confirm-not-received', - authenticateToken, - async (req, res, next) => { - try { - const {transfer_id} = req.body; - - if (!transfer_id) { - return res.status(400).json({success: false, message: '缺少转账ID'}); - } - - await transferService.confirmNotReceived(transfer_id, req.user.id); - - logger.info('Transfer not received confirmed by user', { - transferId: transfer_id, - userId: req.user.id - }); - - res.json({ - success: true, - message: '已确认未收到转账' - }); - } catch (error) { - next(error); - } - } -); - - - -// 获取用户转账记录 -router.get('/user/:userId', authenticateToken, async (req, res) => { - try { - const userId = req.params.userId; - const {page = 1, limit = 10, status} = req.query; - - // 检查权限(只能查看自己的记录或管理员查看所有) - // if (req.user.id != userId && req.user.role !== 'admin') { - // return res.status(403).json({ success: false, message: '权限不足' }); - // } - - const db = getDB(); - - // 确保参数为有效数字 - const pageNum = Math.max(1, parseInt(page) || 1); - const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); - const offset = Math.max(0, (pageNum - 1) * limitNum); - - let whereClause = `WHERE source_type='manual' AND (t.from_user_id = ? OR t.to_user_id = ?)`; - const userIdInt = parseInt(userId); - let listParams = [userIdInt, userIdInt]; - let countParams = [userIdInt, userIdInt]; - - if (status) { - whereClause += ' AND t.status = ?'; - listParams.push(status); - countParams.push(status); - } - - // 添加分页参数 - listParams.push(limitNum.toString(), offset.toString()); - - const [transfers] = await db.execute(` - SELECT t.*, - from_user.username as from_username, - from_user.real_name as from_real_name, - to_user.username as to_username, - to_user.real_name as to_real_name - FROM transfers t - LEFT JOIN users from_user ON t.from_user_id = from_user.id - LEFT JOIN users to_user ON t.to_user_id = to_user.id - ${whereClause} - ORDER BY t.created_at - DESC - LIMIT ${limitNum} OFFSET ${offset} - `, countParams); - - const [countResult] = await db.execute(` - SELECT COUNT(*) as total - FROM transfers t ${whereClause} - `, countParams); - - res.json({ - success: true, - data: { - transfers, - pagination: { - page: pageNum, - limit: limitNum, - total: countResult[0].total, - pages: Math.ceil(countResult[0].total / limitNum) - } - } - }); - } catch (error) { - console.error('获取转账记录失败:', error); - res.status(500).json({success: false, message: '服务器错误'}); - } -}); - -// 获取转账统计信息 -router.get('/stats', authenticateToken, async (req, res) => { - try { - const userId = req.user.id; - const isAdmin = req.user.role === 'admin'; - const db = getDB(); - - let stats = {}; - - if (isAdmin) { - // 管理员可以查看全局统计 - const [totalStats] = await db.execute(` - SELECT COUNT(*) as total_transfers, - SUM(amount) as total_flow_amount, - SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, - SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, - SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received_count, - SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, - SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count, - SUM(CASE WHEN status = 'not_received' THEN 1 ELSE 0 END) as not_received_count, - SUM(CASE WHEN is_overdue = 1 THEN 1 ELSE 0 END) as overdue_count, - SUM(CASE WHEN is_bad_debt = 1 THEN 1 ELSE 0 END) as bad_debt_count, - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as total_amount, - SUM(CASE WHEN is_bad_debt = 1 THEN amount ELSE 0 END) as bad_debt_amount, - SUM(CASE - WHEN transfer_type = 'initial' AND status = 'confirmed' THEN amount - ELSE 0 END) as initial_amount, - SUM(CASE - WHEN transfer_type = 'return' AND status = 'confirmed' THEN amount - ELSE 0 END) as return_amount, - SUM(CASE - WHEN transfer_type = 'user_to_user' AND status = 'confirmed' THEN amount - ELSE 0 END) as user_to_user_amount, - (SELECT SUM(balance) - FROM users - WHERE role = 'user' - AND is_system_account = 1) as total_merchant_balance, - (SELECT SUM(balance) - FROM users - WHERE role = 'user' - AND is_system_account != 1) as total_user_balance, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as participated_transfers, - SUM(CASE WHEN source_type IN ('agent') THEN amount END) as agent_total, - SUM(CASE WHEN source_type IN ('operated_agent') THEN amount END) as operated_agent_total - FROM transfers - `); - - const todayStr = dayjs().format('YYYY-MM-DD'); - const currentYear = dayjs().year(); - const currentMonth = dayjs().month() + 1; - console.log(todayStr, 'todayStr'); - - const [todayStats] = await db.execute(` - SELECT COUNT(*) as today_transfers, - ( - COALESCE((SELECT SUM(amount) - FROM transfers - WHERE DATE(created_at) = ? - AND to_user_id IN (SELECT id FROM users WHERE is_system_account = 1) - AND status = 'received'), 0) - - COALESCE((SELECT SUM(amount) - FROM transfers - WHERE DATE(created_at) = ? - AND from_user_id IN (SELECT id FROM users WHERE is_system_account = 1) - AND status = 'received'), 0) - ) as today_amount - FROM transfers - WHERE DATE(created_at) = ? - `, [todayStr, todayStr, todayStr]); - - const [monthlyStats] = await db.execute(` - SELECT COUNT(*) as monthly_transfers, - SUM(CASE WHEN status = 'received' THEN amount ELSE 0 END) as monthly_amount, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as monthly_participated_transfers - FROM transfers - WHERE YEAR(created_at) = ? - AND MONTH(created_at) = ? - `, [currentYear, currentMonth]); - - // 获取上月统计数据用于对比 - const lastMonth = currentMonth === 1 ? 12 : currentMonth - 1; - const lastMonthYear = currentMonth === 1 ? currentYear - 1 : currentYear; - - const [lastMonthStats] = await db.execute(` - SELECT COUNT(*) as last_monthly_transfers, - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as last_monthly_amount, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as last_monthly_participated_transfers - FROM transfers - WHERE YEAR(created_at) = ? - AND MONTH(created_at) = ? - `, [lastMonthYear, lastMonth]); - - stats = { - total: { - transfers: totalStats[0].total_transfers || 0, - pending: parseFloat(totalStats[0].total_flow_amount || 0), - pending_count: totalStats[0].pending_count || 0, - confirmed: totalStats[0].confirmed_count || 0, - received_count: totalStats[0].received_count || 0, - rejected: totalStats[0].rejected_count || 0, - cancelled_count: totalStats[0].cancelled_count || 0, - not_received_count: totalStats[0].not_received_count || 0, - overdue: totalStats[0].overdue_count || 0, - bad_debt: totalStats[0].bad_debt_count || 0, - amount: parseFloat(totalStats[0].total_amount || 0), - bad_debt_amount: parseFloat(totalStats[0].bad_debt_amount || 0), - total_merchant_balance: parseFloat(totalStats[0].total_merchant_balance || 0), - initial_amount: parseFloat(totalStats[0].initial_amount || 0), - return_amount: parseFloat(totalStats[0].return_amount || 0), - user_to_user_amount: parseFloat(totalStats[0].user_to_user_amount || 0), - participated_transfers: totalStats[0].participated_transfers || 0, - total_user_balance:totalStats[0].total_user_balance || 0, - agent_total:totalStats[0].agent_total || 0,//代理收入 - operated_agent_total:totalStats[0].operated_agent_total || 0,//直营代理收入 - }, - today: { - transfers: todayStats[0].today_transfers || 0, - amount: parseFloat(todayStats[0].today_amount || 0) - }, - monthly: { - transfers: monthlyStats[0].monthly_transfers || 0, - amount: parseFloat(monthlyStats[0].monthly_amount || 0), - participated_transfers: monthlyStats[0].monthly_participated_transfers || 0 - }, - lastMonth: { - transfers: lastMonthStats[0].last_monthly_transfers || 0, - amount: parseFloat(lastMonthStats[0].last_monthly_amount || 0), - participated_transfers: lastMonthStats[0].last_monthly_participated_transfers || 0 - } - }; - } else { - // 普通用户只能查看自己的统计 - const [userStats] = await db.execute(` - SELECT COUNT(*) as total_transfers, - SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count, - SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count, - SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count, - SUM(CASE WHEN status = 'confirmed' AND from_user_id = ? THEN amount ELSE 0 END) as sent_amount, - SUM(CASE WHEN status = 'confirmed' AND to_user_id = ? THEN amount ELSE 0 END) as received_amount - FROM transfers - WHERE from_user_id = ? - OR to_user_id = ? - `, [userId, userId, userId, userId]); - - const todayStr = dayjs().format('YYYY-MM-DD'); - - const [todayStats] = await db.execute(` - SELECT COUNT(*) as today_transfers, - SUM(CASE WHEN status = 'confirmed' AND from_user_id = ? THEN amount ELSE 0 END) as today_sent, - SUM(CASE WHEN status = 'confirmed' AND to_user_id = ? THEN amount ELSE 0 END) as today_received - FROM transfers - WHERE (from_user_id = ? OR to_user_id = ?) - AND DATE(created_at) = ? - `, [userId, userId, userId, userId, todayStr]); - - stats = { - total: { - transfers: userStats[0].total_transfers || 0, - pending: userStats[0].pending_count || 0, - confirmed: userStats[0].confirmed_count || 0, - rejected: userStats[0].rejected_count || 0, - sent_amount: parseFloat(userStats[0].sent_amount || 0), - received_amount: parseFloat(userStats[0].received_amount || 0) - }, - today: { - transfers: todayStats[0].today_transfers || 0, - sent_amount: parseFloat(todayStats[0].today_sent || 0), - received_amount: parseFloat(todayStats[0].today_received || 0) - } - }; - } - - res.json({success: true, data: stats}); - } catch (error) { - console.error('获取转账统计失败:', error); - res.status(500).json({success: false, message: '获取转账统计失败'}); - } -}); - -// 获取待确认的转账 -router.get('/pending', authenticateToken, async (req, res) => { - try { - const userId = parseInt(req.user.id); - const db = getDB(); - - const [transfers] = await db.execute(` - SELECT t.*, - from_user.username as from_username, - from_user.real_name as from_real_name - FROM transfers t - LEFT JOIN users from_user ON t.from_user_id = from_user.id - WHERE t.to_user_id = ? - AND t.status = 'pending' - ORDER BY t.created_at DESC - `, [userId]); - - res.json({success: true, data: transfers}); - } catch (error) { - console.error('获取待确认转账失败:', error); - res.status(500).json({success: false, message: '服务器错误'}); - } -}); - -// 获取当前用户账户信息(不需要传递用户ID) -router.get('/account', authenticateToken, async (req, res) => { - try { - const userId = req.user.id; - - const db = getDB(); - const [user] = await db.execute(` - SELECT id, username, real_name, balance, created_at, updated_at, points - FROM users - WHERE id = ? - `, [userId]); - - if (user.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - // 返回用户账户信息,格式与原来的 accounts 表保持一致 - const accountData = { - id: user[0].id, - user_id: user[0].id, - account_type: 'user', - balance: user[0].balance, - username: user[0].username, - real_name: user[0].real_name, - created_at: user[0].created_at, - updated_at: user[0].updated_at, - points: user[0].points - }; - - res.json({success: true, data: accountData}); - } catch (error) { - console.error('获取账户信息失败:', error); - res.status(500).json({success: false, message: '服务器错误'}); - } -}); - -// 获取指定用户账户信息(管理员权限或用户本人) -router.get('/account/:userId', authenticateToken, async (req, res) => { - try { - const userId = req.params.userId; - - // 检查权限 - if (req.user.id != userId && req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const db = getDB(); - const [user] = await db.execute(` - SELECT id, username, real_name, balance, created_at, updated_at - FROM users - WHERE id = ? - `, [userId]); - - if (user.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - // 返回用户账户信息,格式与原来的 accounts 表保持一致 - const accountData = { - id: user[0].id, - user_id: user[0].id, - account_type: 'user', - balance: user[0].balance, - username: user[0].username, - real_name: user[0].real_name, - created_at: user[0].created_at, - updated_at: user[0].updated_at - }; - - res.json({success: true, data: accountData}); - } catch (error) { - console.error('获取账户信息失败:', error); - res.status(500).json({success: false, message: '服务器错误'}); - } -}); - -// 获取转账趋势数据(管理员权限) -router.get('/trend', authenticateToken, async (req, res) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const db = getDB(); - const {days = 7} = req.query; - const daysNum = Math.min(30, Math.max(1, parseInt(days) || 7)); - - // 首先获取数据库中最早和最晚的转账日期 - const [dateRange] = await db.execute(` - SELECT MIN(DATE(created_at)) as min_date, - MAX(DATE(created_at)) as max_date, - COUNT(*) as total_count - FROM transfers - `); - - if (dateRange[0].total_count === 0) { - // 如果没有转账记录,返回空数据 - const result = []; - const now = new Date(); - - for (let i = daysNum - 1; i >= 0; i--) { - const date = dayjs().subtract(i, 'day'); - result.push({ - date: date.format('MM-DD'), - count: 0, - amount: 0 - }); - } - - return res.json({ - success: true, - data: result - }); - } - - // 获取最近的转账数据(基于实际数据的最大日期) - const maxDate = dayjs(dateRange[0].max_date); - - // 获取指定天数内的转账趋势(从最大日期往前推) - const [trendData] = await db.execute(` - SELECT DATE(created_at) as date, - COUNT(*) as count, - SUM(amount) as amount - FROM transfers - WHERE DATE(created_at) >= DATE_SUB(?, INTERVAL ? DAY) - AND status IN ('confirmed', 'received') - GROUP BY DATE(created_at) - ORDER BY date ASC - `, [dateRange[0].max_date, daysNum - 1]); - - // 填充缺失的日期(转账数为0) - const result = []; - - for (let i = daysNum - 1; i >= 0; i--) { - const date = maxDate.subtract(i, 'day'); - const dateStr = date.format('YYYY-MM-DD'); - - // 修复日期比较:将数据库返回的Date对象转换为字符串进行比较 - const existingData = trendData.find(item => { - const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); - return itemDateStr === dateStr; - }); - - result.push({ - date: date.format('MM-DD'), - count: existingData ? existingData.count : 0, - amount: existingData ? parseFloat(existingData.amount) : 0 - }); - } - - res.json({ - success: true, - data: result - }); - } catch (error) { - console.error('获取转账趋势错误:', error); - res.status(500).json({success: false, message: '获取转账趋势失败'}); - } -}); - -// 管理员解除坏账(管理员权限) -router.post('/remove-bad-debt/:transferId', authenticateToken, async (req, res) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const {transferId} = req.params; - const {reason} = req.body; - const adminId = req.user.id; - - // 验证转账ID - if (!transferId || isNaN(transferId)) { - return res.status(400).json({success: false, message: '无效的转账ID'}); - } - - const result = await transferService.removeBadDebt(transferId, adminId, reason); - - res.json({ - success: true, - message: '坏账标记已解除', - data: result - }); - } catch (error) { - console.error('解除坏账失败:', error); - if (error.statusCode) { - return res.status(error.statusCode).json({ - success: false, - message: error.message - }); - } - res.status(500).json({success: false, message: '解除坏账失败'}); - } -}); - -// 强制变更转账状态(管理员权限) -router.post('/force-change-status/:transferId', authenticateToken, async (req, res) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const {transferId} = req.params; - const {newStatus, status, reason, adjust_balance = false} = req.body; - console.log('newStatus:', newStatus); - console.log('status:', status); - console.log('reason:', reason); - console.log('adjust_balance:', adjust_balance); - - // 兼容两种参数名:newStatus 和 status - const actualNewStatus = newStatus || status; - const adminId = req.user.id; - - // 验证转账ID - if (!transferId || isNaN(transferId)) { - return res.status(400).json({success: false, message: '无效的转账ID'}); - } - - // 验证必填参数 - if (!actualNewStatus) { - return res.status(400).json({success: false, message: '新状态不能为空'}); - } - - // if (!reason) { - // return res.status(400).json({ success: false, message: '变更原因不能为空' }); - // } - - const result = await transferService.forceChangeTransferStatus( - transferId, - actualNewStatus, - reason, - adminId, - adjust_balance - ); - - res.json({ - success: true, - message: `转账状态已从 ${result.oldStatus} 变更为 ${result.newStatus}`, - data: result - }); - } catch (error) { - console.error('强制变更转账状态失败:', error); - if (error.statusCode) { - return res.status(error.statusCode).json({ - success: false, - message: error.message - }); - } - res.status(500).json({success: false, message: '变更转账状态失败'}); - } -}); - -// 管理员查看数据库连接状态 -router.get('/admin/database/status', authenticateToken, async (req, res) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const dbMonitor = require('../db-monitor'); - const diagnosis = await dbMonitor.diagnose(); - - res.json({ - success: true, - data: diagnosis - }); - } catch (error) { - logger.error('Get database status failed', { - adminId: req.user.id, - error: error.message - }); - res.status(500).json({ - success: false, - message: '获取数据库状态失败: ' + error.message - }); - } -}); - -// 管理员获取数据库监控报告 -router.get('/admin/database/report', authenticateToken, async (req, res) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const dbMonitor = require('../db-monitor'); - const report = await dbMonitor.generateReport(); - - res.json({ - success: true, - data: { - report, - timestamp: new Date().toISOString() - } - }); - } catch (error) { - logger.error('Get database report failed', { - adminId: req.user.id, - error: error.message - }); - res.status(500).json({ - success: false, - message: '获取数据库报告失败: ' + error.message - }); - } -}); - -/** - * 获取待处理的匹配转账订单 - * @param {number} page - 页码 - * @param {number} limit - 每页数量 - * @param {string} status - 状态过滤 - * @param {string} search - 搜索关键词(用户名或真实姓名) - * @param {string} sort - 排序字段 - * @param {string} order - 排序方向(asc/desc) - */ -router.get('/pending-allocations', - authenticateToken, - async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const { - page = 1, - limit = 20, - status = '', - search = '', - sort = 'created_at', - order = 'desc' - } = req.query; - - const db = getDB(); - const offset = (parseInt(page) - 1) * parseInt(limit); - - // 构建查询条件 - let whereConditions = []; - let queryParams = []; - - // 状态过滤 - if (status) { - whereConditions.push('oa.status = ?'); - queryParams.push(status); - } - - // 搜索过滤(用户名或真实姓名) - if (search) { - whereConditions.push('(uf.username LIKE ? OR uf.real_name LIKE ? OR ut.username LIKE ? OR ut.real_name LIKE ?)'); - const searchPattern = `%${search}%`; - queryParams.push(searchPattern, searchPattern, searchPattern, searchPattern); - } - - const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : ''; - - // 验证排序字段 - const allowedSortFields = ['created_at', 'amount', 'status', 'cycle_number']; - const sortField = allowedSortFields.includes(sort) ? sort : 'created_at'; - const sortOrder = order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; - - // 获取总数 - const countQuery = ` - SELECT COUNT(*) as total - FROM transfers oa - JOIN users uf ON oa.from_user_id = uf.id - JOIN users ut ON oa.to_user_id = ut.id - JOIN matching_orders mo ON oa.id = mo.id - ${whereClause} - `; - - const [countResult] = await db.execute(countQuery, queryParams); - const total = countResult[0].total; - - // 使用 query 方法避免 LIMIT/OFFSET 参数问题 - const dataQuery = ` - SELECT oa.id, - oa.from_user_id, - oa.to_user_id, - oa.amount, - oa.cycle_number, - oa.status, - oa.outbound_date, - oa.return_date, - oa.can_return_after, - oa.confirmed_at, - oa.created_at, - oa.updated_at, - uf.username as from_username, - uf.real_name as from_real_name, - ut.username as to_username, - ut.real_name as to_real_name, - mo.amount as order_total_amount, - mo.status as order_status, - mo.matching_type, - t.status as transfer_status, - t.voucher_url - FROM transfers oa - JOIN users uf ON oa.from_user_id = uf.id - JOIN users ut ON oa.to_user_id = ut.id - JOIN matching_orders mo ON oa.id = mo.id - ${whereClause} - ORDER BY oa.${sortField} ${sortOrder} - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)} - `; - - const [allocations] = queryParams.length > 0 - ? await db.execute(dataQuery, queryParams) - : await db.query(dataQuery); - - // 计算分页信息 - const totalPages = Math.ceil(total / parseInt(limit)); - - logger.info('Pending allocations list requested', { - userId: req.user.id, - page: parseInt(page), - limit: parseInt(limit), - total, - resultCount: allocations.length - }); - - res.json({ - success: true, - data: { - allocations, - pagination: { - page: parseInt(page), - limit: parseInt(limit), - total, - totalPages - } - } - }); - } catch (error) { - logger.error('Get pending allocations failed', { - userId: req.user.id, - error: error.message - }); - next(error); - } - } -); - -/** - * 获取待处理匹配订单的统计信息 - */ -router.get('/pending-allocations/stats', - authenticateToken, - async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const db = getDB(); - - // 获取统计数据 - const [stats] = await db.execute(` - SELECT COUNT(*) as total_allocations, - COUNT(CASE WHEN oa.status = 'pending' THEN 1 END) as pending_count, - COUNT(CASE WHEN oa.status = 'confirmed' THEN 1 END) as confirmed_count, - COUNT(CASE WHEN oa.status = 'completed' THEN 1 END) as completed_count, - SUM(oa.amount) as total_amount, - SUM(CASE WHEN oa.status = 'pending' THEN oa.amount ELSE 0 END) as pending_amount - FROM transfers oa - JOIN matching_orders mo ON oa.id = mo.id - WHERE mo.status != 'cancelled' - `); - - res.json({ - success: true, - data: stats[0] - }); - } catch (error) { - logger.error('Get pending allocations stats failed', { - userId: req.user.id, - error: error.message - }); - next(error); - } - } -); - -/** - * 获取昨日用户转账统计列表 - */ -router.get('/daily-stats', - authenticateToken, - async (req, res, next) => { - try { - // 检查管理员权限 - if (req.user.role !== 'admin') { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const db = getDB(); - - // 获取昨日和今日的日期范围(从0点开始计算) - // 今日0点到23:59:59 - const todayStart = dayjs().startOf('day'); - const todayEnd = dayjs().endOf('day'); - - // 昨日0点到23:59:59 - const yesterdayStart = dayjs().subtract(1, 'day').startOf('day'); - const yesterdayEnd = dayjs().subtract(1, 'day').endOf('day'); - - // 转换为MySQL兼容的字符串格式 - const todayStartStr = todayStart.format('YYYY-MM-DD HH:mm:ss'); - const todayEndStr = todayEnd.format('YYYY-MM-DD HH:mm:ss'); - const yesterdayStartStr = yesterdayStart.format('YYYY-MM-DD HH:mm:ss'); - const yesterdayEndStr = yesterdayEnd.format('YYYY-MM-DD HH:mm:ss'); - - // 使用dayjs格式化日期字符串用于返回 - const todayStr = todayStart.format('YYYY-MM-DD'); - const yesterdayStr = yesterdayStart.format('YYYY-MM-DD'); - - // 获取所有用户的昨日转出和今日入账统计 - let [userStats] = await db.execute(` - SELECT u.id as user_id, - u.username, - u.real_name, - u.phone, - u.balance, - COALESCE(yesterday_out.amount, 0) as yesterday_out_amount, - COALESCE(today_in.amount, 0) as today_in_amount, - COALESCE(confirmed_from.confirmed_amount, 0) as confirmed_from_amount, - CASE - WHEN (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0)) > ABS(u.balance) - THEN ABS(u.balance) - ELSE (COALESCE(u.balance, 0) + COALESCE(confirmed_from.confirmed_amount, 0)) - END as balance_needed - FROM users u - LEFT JOIN (SELECT from_user_id, - SUM(amount) as amount - FROM transfers - WHERE created_at >= ? - AND created_at <= ? - AND status IN ('confirmed', 'received') - GROUP BY from_user_id) yesterday_out ON u.id = yesterday_out.from_user_id - LEFT JOIN (SELECT to_user_id, - SUM(amount) as amount - FROM transfers - WHERE created_at >= ? - AND created_at <= ? - AND status IN ('confirmed', 'received') - GROUP BY to_user_id) today_in ON u.id = today_in.to_user_id - left join (select from_user_id, - sum(amount) as confirmed_amount - from transfers - where status = 'received' - and created_at >= ? - and created_at <= ? - group by from_user_id) as confirmed_from on u.id = confirmed_from.from_user_id - WHERE u.role != 'admin' - AND u.is_system_account != 1 - AND yesterday_out.amount > 0 - AND u.balance < 0 - ORDER BY balance_needed DESC, yesterday_out_amount DESC - `, [yesterdayStartStr, yesterdayEndStr, todayStartStr, todayEndStr, todayStartStr, todayEndStr]); - // userStats = userStats.filter(item=>item.balance_needed >= 100) - userStats.forEach(item => { - item.balance_needed = Math.abs(item.balance_needed) - }) - res.json({ - success: true, - data: { - date: { - yesterday: yesterdayStr, - today: todayStr - }, - users: userStats - } - }); - } catch (error) { - logger.error('Get daily transfer stats failed', { - userId: req.user.id, - error: error.message - }); - next(error); - } - } -); - -module.exports = router; \ No newline at end of file diff --git a/routes/upload.js b/routes/upload.js deleted file mode 100644 index 2222e0b..0000000 --- a/routes/upload.js +++ /dev/null @@ -1,421 +0,0 @@ -const express = require('express'); -const multer = require('multer'); -const path = require('path'); -const { auth } = require('../middleware/auth'); -const { authenticateToken } = require('./auth'); -const minioService = require('../services/minioService'); -const { initializeBuckets } = require('../config/minio'); - -const router = express.Router(); - -// 初始化MinIO存储桶 -// initializeBuckets().catch(console.error); - -/** - * @swagger - * tags: - * name: Upload - * description: 文件上传API - */ - -// 配置multer内存存储(用于MinIO上传) -const storage = multer.memoryStorage(); - -// 文件过滤器 - 支持图片和视频 -const fileFilter = (req, file, cb) => { - // 允许图片和视频文件 - if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) { - cb(null, true); - } else { - cb(new Error('只能上传图片或视频文件'), false); - } -}; - -// 单文件上传配置 -const upload = multer({ - storage: storage, - fileFilter: fileFilter, - limits: { - fileSize: 5 * 1024 * 1024, // 5MB - files: 1 // 一次只能上传一个文件 - } -}); - -// 多文件上传配置 -const multiUpload = multer({ - storage: storage, - fileFilter: fileFilter, - limits: { - fileSize: 10 * 1024 * 1024, // 10MB (视频文件更大) - files: 10 // 最多10个文件 - } -}); - -/** - * @swagger - * /upload/image: - * post: - * summary: 上传图片 - * tags: [Upload] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * description: 要上传的图片文件 - * type: - * type: string - * enum: [avatar, product, document] - * default: document - * description: 上传文件类型 - * responses: - * 200: - * description: 图片上传成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * url: - * type: string - * description: 上传后的文件URL - * filename: - * type: string - * description: 上传后的文件名 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.post('/image', authenticateToken, (req, res) => { - upload.single('file')(req, res, async (err) => { - if (err instanceof multer.MulterError) { - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(400).json({ - success: false, - message: '文件大小不能超过 5MB' - }); - } - if (err.code === 'LIMIT_FILE_COUNT') { - return res.status(400).json({ - success: false, - message: '一次只能上传一个文件' - }); - } - return res.status(400).json({ - success: false, - message: '文件上传失败:' + err.message - }); - } else if (err) { - return res.status(400).json({ - success: false, - message: err.message - }); - } - - if (!req.file) { - return res.status(400).json({ - success: false, - message: '请选择要上传的文件' - }); - } - - try { - // 使用MinIO服务上传文件 - const type = req.body.type || 'document'; - const result = await minioService.uploadFile( - req.file.buffer, - req.file.originalname, - req.file.mimetype, - type - ); - - res.json({ - success: true, - message: '文件上传成功', - data: result.data - }); - } catch (error) { - console.error('文件上传到MinIO失败:', error); - res.status(500).json({ - success: false, - message: error.message || '文件上传失败' - }); - } - }); -}); - -/** - * @swagger - * /upload: - * post: - * summary: 多文件上传接口 (支持MediaUpload组件) - * tags: [Upload] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * files: - * type: array - * items: - * type: string - * format: binary - * description: 要上传的文件列表 - * type: - * type: string - * enum: [avatar, product, document] - * default: document - * description: 上传文件类型 - * responses: - * 200: - * description: 文件上传成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 文件上传成功 - * data: - * type: array - * items: - * type: object - * properties: - * filename: - * type: string - * originalname: - * type: string - * mimetype: - * type: string - * size: - * type: integer - * path: - * type: string - * url: - * type: string - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - * @access Private - */ -router.post('/', authenticateToken, (req, res) => { - multiUpload.array('file', 10)(req, res, async (err) => { - if (err instanceof multer.MulterError) { - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(400).json({ - success: false, - message: '文件大小不能超过 10MB' - }); - } - if (err.code === 'LIMIT_FILE_COUNT') { - return res.status(400).json({ - success: false, - message: '一次最多只能上传10个文件' - }); - } - return res.status(400).json({ - success: false, - message: '文件上传失败:' + err.message - }); - } else if (err) { - return res.status(400).json({ - success: false, - message: err.message - }); - } - - if (!req.files || req.files.length === 0) { - return res.status(400).json({ - success: false, - message: '请选择要上传的文件' - }); - } - - try { - // 使用MinIO服务上传多个文件 - const type = req.body.type || 'document'; - const files = req.files.map(file => ({ - buffer: file.buffer, - originalName: file.originalname, - mimeType: file.mimetype - })); - - const result = await minioService.uploadMultipleFiles(files, type); - - // 如果只上传了一个文件,返回单文件格式以保持兼容性 - if (result.data.files.length === 1) { - result.data.files.forEach(element => { - element.path = '/' + element.path - }); - res.json({ - success: true, - message: '文件上传成功', - data: { - ...result.data.files[0], - urls: result.data.urls // 同时提供urls数组格式 - } - }); - } else { - // 多文件返回数组格式 - res.json({ - success: true, - message: `成功上传${result.data.files.length}个文件`, - data: result.data - }); - } - } catch (error) { - console.error('文件上传到MinIO失败:', error); - res.status(500).json({ - success: false, - message: error.message || '文件上传失败' - }); - } - }); -}); - -/** - * @swagger - * /upload/single: - * post: - * summary: 单文件上传接口(兼容性接口) - * tags: [Upload] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * description: 要上传的文件 - * type: - * type: string - * enum: [avatar, product, document] - * default: document - * description: 上传文件类型 - * responses: - * 200: - * description: 文件上传成功 - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: true - * message: - * type: string - * example: 文件上传成功 - * url: - * type: string - * description: 上传后的文件URL - * filename: - * type: string - * description: 上传后的文件名 - * originalname: - * type: string - * description: 原始文件名 - * size: - * type: integer - * description: 文件大小 - * 400: - * description: 请求参数错误 - * 401: - * description: 未授权 - * 500: - * description: 服务器错误 - */ -router.post('/single', auth, (req, res) => { - upload.single('file')(req, res, async (err) => { - if (err instanceof multer.MulterError) { - return res.status(400).json({ - success: false, - message: '文件上传失败:' + err.message - }); - } else if (err) { - return res.status(400).json({ - success: false, - message: err.message - }); - } - - if (!req.file) { - return res.status(400).json({ success: false, message: '没有上传文件' }); - } - - try { - // 使用MinIO服务上传文件 - const type = req.body.type || 'document'; - const result = await minioService.uploadFile( - req.file.buffer, - req.file.originalname, - req.file.mimetype, - type - ); - - res.json({ - success: true, - message: '文件上传成功', - url: result.data.url, - filename: result.data.filename, - originalname: result.data.originalname, - size: result.data.size - }); - } catch (error) { - console.error('文件上传到MinIO失败:', error); - res.status(500).json({ - success: false, - message: error.message || '文件上传失败' - }); - } - }); -}); - -// 错误处理中间件 -router.use((error, req, res, next) => { - if (error instanceof multer.MulterError) { - if (error.code === 'LIMIT_FILE_SIZE') { - return res.status(400).json({ success: false, message: '文件大小不能超过10MB' }); - } - if (error.code === 'LIMIT_FILE_COUNT') { - return res.status(400).json({ success: false, message: '一次最多只能上传10个文件' }); - } - } - - if (error.message === '只能上传图片或视频文件') { - return res.status(400).json({ success: false, message: error.message }); - } - - res.status(500).json({ success: false, message: '上传失败' }); -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 5b0afbc..0000000 --- a/routes/users.js +++ /dev/null @@ -1,1816 +0,0 @@ -const express = require('express'); -const bcrypt = require('bcryptjs'); -const {getDB} = require('../database'); -const {auth, adminAuth} = require('../middleware/auth'); -const dayjs = require('dayjs'); - -const router = express.Router(); - -/** - * @swagger - * tags: - * name: Users - * description: 用户管理API - */ - -router.post('/', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - await db.query('START TRANSACTION'); - - const { - username, - password, - role = 'user', - isSystemAccount = false, // 是否为虚拟商户 - realName, - idCard, - wechatQr, - alipayQr, - bankCard, - unionpayQr, - province, - city, - districtId, - phone, - avatar, - user_type = 'directly_operated', - inviter = null - } = req.body; - - if (!username || !password) { - return res.status(400).json({success: false, message: '用户名和密码不能为空'}); - } - - if (!realName || !idCard) { - return res.status(400).json({success: false, message: '姓名和身份证号不能为空'}); - } - if (!city || !districtId || !province) { - return res.status(400).json({success: false, message: '请选择城市和区县'}); - } - - // 验证身份证号格式 - const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; - if (!idCardRegex.test(idCard)) { - return res.status(400).json({success: false, message: '身份证号格式不正确'}); - } - - // 检查用户是否已存在 - const [existingUsers] = await db.execute( - 'SELECT id FROM users WHERE username = ? OR id_card = ? OR (phone IS NOT NULL AND phone = ?)', - [username, idCard, phone || null] - ); - - if (existingUsers.length > 0) { - return res.status(400).json({success: false, message: '用户名、身份证号或手机号已存在'}); - } - - // 加密密码 - const hashedPassword = await bcrypt.hash(password, 10); - - // 创建用户 - console.log([username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone, province, city, districtId, user_type, inviter], 'info'); - - const [result] = await db.execute( - 'INSERT INTO users (username, password, role, is_system_account, points, real_name, id_card, wechat_qr, alipay_qr, bank_card, unionpay_qr, phone, province, city, district_id, user_type, inviter,avatar) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?)', - [username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone, province, city, districtId, user_type, inviter, avatar] - ); - - const userId = result.insertId; - if (user_type === 'agent_directly') { - const agentCode = 'AG' + Date.now().toString().slice(-8); - await db.execute( - 'INSERT INTO regional_agents (user_id, region_id,status,agent_code) VALUES (?, ?,?,?)', - [userId, districtId, 'active', agentCode] - ); - await db.execute( - `UPDATE users - SET payment_status='paid' - WHERE id = ${userId}` - ) - await db.execute(` - INSERT INTO distribution (user_id, amount, is_offline, type) - VALUES (${userId}, 2980, 1, 'system') - `) - } - - // 用户余额已在创建用户时设置为默认值0.00,无需额外操作 - - await db.query('COMMIT'); - - // 返回创建的用户信息(不包含密码) - const [newUser] = await db.execute( - 'SELECT id, username, role, avatar, points, real_name, phone, created_at, updated_at FROM users WHERE id = ?', - [userId] - ); - - res.status(201).json({ - success: true, - message: '用户创建成功', - user: newUser[0] - }); - } catch (error) { - try { - await getDB().query('ROLLBACK'); - } catch (rollbackError) { - console.error('回滚错误:', rollbackError); - } - console.error('创建用户错误:', error); - res.status(500).json({success: false, message: '创建用户失败'}); - } -}); - - -router.get('/pending-audit', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const {page = 1, limit = 10} = req.query; - const pageNum = parseInt(page) || 1; - const limitNum = parseInt(limit) || 10; - const offset = (pageNum - 1) * limitNum; - - // 获取待审核用户总数 - const [countResult] = await db.execute( - 'SELECT COUNT(*) as total FROM users WHERE audit_status = ?', - ['pending'] - ); - const total = countResult[0].total; - - // 获取待审核用户列表 - const [users] = await db.execute( - `SELECT id, - username, - phone, - real_name, - business_license, - id_card_front, - id_card_back, - wechat_qr, - alipay_qr, - unionpay_qr, - bank_card, - audit_status, - created_at - FROM users - WHERE audit_status = ? - ORDER BY created_at ASC - LIMIT ${limitNum} OFFSET ${offset}`, - ['pending'] - ); - - res.json({ - success: true, - data: { - users, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - } - }); - } catch (error) { - console.error('获取待审核用户列表错误:', error); - res.status(500).json({success: false, message: '获取待审核用户列表失败'}); - } -}); - -// 获取用户列表用于转账(普通用户权限) -router.get('/for-transfer', auth, async (req, res) => { - try { - const db = getDB(); - - // 获取所有用户的基本信息(用于转账选择) - const [users] = await db.execute( - 'SELECT id, username, real_name FROM users WHERE id != ? ORDER BY username', - [req.user.id] - ); - - res.json({ - success: true, - data: users - }); - } catch (error) { - console.error('获取转账用户列表错误:', error); - res.status(500).json({success: false, message: '获取用户列表失败'}); - } -}); - -// 获取用户列表(管理员权限) -router.get('/', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const { - page = 1, - limit = 10, - search = '', - role = '', - city = '', - district = '', - province = '', - sort = 'created_at', - order = 'desc' - } = req.query; - - // 确保参数为有效数字 - const pageNum = Math.max(1, parseInt(page) || 1); - const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); - const offset = Math.max(0, (pageNum - 1) * limitNum); - - let whereConditions = []; - let countParams = []; - let listParams = []; - - // 构建查询条件 - if (search) { - whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ?)'); - countParams.push(`%${search}%`, `%${search}%`); - listParams.push(`%${search}%`, `%${search}%`); - } - - if (role && role !== 'all') { - whereConditions.push('u.role = ?'); - countParams.push(role); - listParams.push(role); - } - - if (city) { - whereConditions.push('u.city = ?'); - countParams.push(city); - listParams.push(city); - } - - if (province) { - whereConditions.push('u.province = ?'); - countParams.push(province); - listParams.push(province); - } - - if (district) { - whereConditions.push('u.district_id = ?'); - countParams.push(district); - listParams.push(district); - } - - const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; - - // 添加分页参数 - listParams.push(limitNum.toString(), offset.toString()); - - - // 获取总数 - const [countResult] = await db.execute( - `SELECT COUNT(*) as total - FROM users u - LEFT JOIN zhejiang_regions r ON u.district_id = r.id - ${whereClause}`, - countParams - ); - - // 验证排序字段,防止SQL注入 - const validSortFields = ['id', 'username', 'role', 'points', 'balance', 'created_at', 'updated_at']; - const sortField = validSortFields.includes(sort) ? sort : 'created_at'; - - // 验证排序方向 - const sortOrder = (order && (order.toUpperCase() === 'ASC' || order.toUpperCase() === 'DESC')) - ? order.toUpperCase() - : 'DESC'; - - // 获取用户列表,关联地区信息和转账统计 - const [users] = await db.execute( - `SELECT u.id, - u.username, - u.role, - u.avatar, - u.points, - u.balance, - u.real_name, - u.id_card, - u.phone, - u.wechat_qr, - u.alipay_qr, - u.bank_card, - u.unionpay_qr, - u.audit_status, - u.is_system_account, - u.created_at, - u.updated_at, - u.province, - u.city, - u.district_id, - u.id_card_front, - u.id_card_back, - u.business_license, - u.is_distribute, - u.user_type, - u.inviter, - p.name as province_name, - c.name as city_name, - d.name as district_name, - COALESCE(yesterday_out.amount, 0) as yesterday_transfer_amount, - COALESCE(today_in.amount, 0) as today_received_amount - FROM users u - LEFT JOIN china_regions p ON u.province = p.code - LEFT JOIN china_regions c ON u.city = c.code - LEFT JOIN china_regions d ON u.district_id = d.code - LEFT JOIN (SELECT from_user_id, SUM(amount) as amount - FROM transfers - WHERE created_at >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY)) - AND created_at < DATE(NOW()) - AND status IN ('confirmed', 'received') - GROUP BY from_user_id) yesterday_out ON u.id = yesterday_out.from_user_id - LEFT JOIN (SELECT to_user_id, SUM(amount) as amount - FROM transfers - WHERE created_at >= DATE(NOW()) - AND created_at < DATE(DATE_ADD(NOW(), INTERVAL 1 DAY)) - AND status IN ('confirmed', 'received') - GROUP BY to_user_id) today_in ON u.id = today_in.to_user_id - ${whereClause} - ORDER BY u.${sortField} ${sortOrder} - LIMIT ${limitNum} OFFSET ${offset}`, - listParams.slice(0, -2) - ); - users.forEach(user => { - user.region = [user.province, user.city, user.district_id] - }) - - res.json({ - success: true, - users, - total: countResult[0].total, - page: pageNum, - limit: limitNum, - totalPages: Math.ceil(countResult[0].total / limitNum) - }); - } catch (error) { - console.error('获取用户列表错误:', error); - res.status(500).json({success: false, message: '获取用户列表失败'}); - } -}); - -// 获取当前用户的个人资料 -router.get('/profile', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.user.id; - - const [users] = await db.execute( - 'SELECT * FROM users WHERE id = ?', - [userId] - ); - const [distribution] = await db.execute(` - SELECT count(*) as total - FROM distribution - WHERE user_id = ?`, [userId]) - users[0].distribution = distribution[0].total > 0 ? true : false; - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - const profile = { - ...users[0], - nickname: users[0].username, // 添加nickname字段,映射到username - realName: users[0].real_name, - idCard: users[0].id_card, - wechatQr: users[0].wechat_qr, - alipayQr: users[0].alipay_qr, - bankCard: users[0].bank_card, - unionpayQr: users[0].unionpay_qr, - businessLicense: users[0].business_license, - idCardFront: users[0].id_card_front, - idCardBack: users[0].id_card_back - }; - res.json({success: true, user: profile}); - } catch (error) { - console.error('获取用户资料错误:', error); - res.status(500).json({success: false, message: '获取用户资料失败'}); - } -}); - -/** - * 获取当前用户的收款码状态 - * 用于检查用户是否已上传微信、支付宝、云闪付收款码 - */ -router.get('/payment-codes-status', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.user.id; - - const [users] = await db.execute( - 'SELECT wechat_qr, alipay_qr, unionpay_qr FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - const paymentCodes = users[0]; - - res.json({ - success: true, - data: { - wechat_qr: paymentCodes.wechat_qr || '', - alipay_qr: paymentCodes.alipay_qr || '', - unionpay_qr: paymentCodes.unionpay_qr || '' - } - }); - } catch (error) { - console.error('获取收款码状态错误:', error); - res.status(500).json({success: false, message: '获取收款码状态失败'}); - } -}); - -// 获取用户收款信息 -router.get('/payment-info/:userId', auth, async (req, res) => { - try { - const db = getDB(); - const targetUserId = req.params.userId; - - const [users] = await db.execute( - 'SELECT id, username, wechat_qr, alipay_qr, unionpay_qr, bank_card FROM users WHERE id = ?', - [targetUserId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - const user = users[0]; - res.json({ - success: true, - data: { - id: user.id, - username: user.username, - wechat_qr: user.wechat_qr, - alipay_qr: user.alipay_qr, - unionpay_qr: user.unionpay_qr, - bank_card: user.bank_card - } - }); - } catch (error) { - console.error('获取用户收款信息错误:', error); - res.status(500).json({success: false, message: '获取用户收款信息失败'}); - } -}); - -// 获取当前用户的统计信息 -router.get('/stats', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.user.id; - - // 如果是管理员,返回全局统计 - if (req.user.role === 'admin') { - // 总用户数 - const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users'); - - // 直营数量 - const [directly_operated] = await db.execute('SELECT COUNT(*) as count FROM users WHERE user_type = "directly_operated"'); - // 代理数量 - const [agent] = await db.execute('SELECT COUNT(*) as count FROM users WHERE user_type = "agent"'); - // 直营代理数量 - const [agent_directly] = await db.execute('SELECT COUNT(*) as count FROM users WHERE user_type = "agent_directly"'); - // 普通用户数量 - const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE user_type = "user"'); - - // 本月新增用户 - const [monthUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())' - ); - - // 上月新增用户 - const [lastMonthUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))' - ); - - // 计算月增长率 - const monthGrowthRate = lastMonthUsers[0].count > 0 - ? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2) - : 0; - - // 用户总积分 - const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users'); - - // 今日新增用户 - const [todayUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()' - ); - - // 昨日新增用户 - const [yesterdayUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)' - ); - - // 活跃用户数(有订单的用户) - const [activeUsers] = await db.execute( - 'SELECT COUNT(DISTINCT from_user_id) as count FROM transfers' - ); - const [weekUsers] = await db.execute(` - SELECT COUNT(DISTINCT from_user_id) as count - FROM transfers - WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 7 DAY) - `) - res.json({ - success: true, - stats: { - totalUsers: totalUsers[0].count, - directly_operated: directly_operated[0].count, - agent: agent[0].count, - agent_directly: agent_directly[0].count, - regularUsers: regularUsers[0].count, - monthNewUsers: monthUsers[0].count, - todayUsers: todayUsers[0].count, - yesterdayUsers: yesterdayUsers[0].count, - monthlyGrowth: parseFloat(monthGrowthRate), - totalPoints: totalPoints[0].total, - activeUsers: activeUsers[0].count, - activeRate: (weekUsers[0].count / totalUsers[0].count * 100).toFixed(2) - } - }); - } else { - // 普通用户返回个人统计 - // 用户订单数 - const [orderCount] = await db.execute( - 'SELECT COUNT(*) as count FROM orders WHERE user_id = ?', - [userId] - ); - - // 用户总消费 - const [totalSpent] = await db.execute( - 'SELECT COALESCE(SUM(total_amount), 0) as total FROM orders WHERE user_id = ? AND status = "completed"', - [userId] - ); - - // 用户积分历史 - const [pointsEarned] = await db.execute( - 'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "earn"', - [userId] - ); - - const [pointsSpent] = await db.execute( - 'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "spend"', - [userId] - ); - - res.json({ - success: true, - stats: { - orderCount: orderCount[0].count, - totalSpent: totalSpent[0].total, - pointsEarned: pointsEarned[0].total, - pointsSpent: pointsSpent[0].total, - currentPoints: req.user.points || 0 - } - }); - } - } catch (error) { - console.error('获取用户统计错误:', error); - res.status(500).json({success: false, message: '获取用户统计失败'}); - } -}); - -// 获取用户统计信息(管理员权限)- 必须在/:id路由之前定义 -router.get('/admin/stats', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - // 总用户数 - const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users'); - - // 管理员数量 - const [adminUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "admin"'); - - // 普通用户数量 - const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "user"'); - - // 本月新增用户 - const [monthUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())' - ); - - // 上月新增用户 - const [lastMonthUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))' - ); - - // 计算月增长率 - const monthGrowthRate = lastMonthUsers[0].count > 0 - ? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2) - : 0; - - // 用户总积分 - const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users'); - - // 今日新增用户 - const [todayUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()' - ); - - // 昨日新增用户 - const [yesterdayUsers] = await db.execute( - 'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)' - ); - - // 活跃用户数(有订单的用户) - const [activeUsers] = await db.execute( - 'SELECT COUNT(DISTINCT user_id) as count FROM orders' - ); - - res.json({ - success: true, - stats: { - totalUsers: totalUsers[0].count, - adminUsers: adminUsers[0].count, - regularUsers: regularUsers[0].count, - monthNewUsers: monthUsers[0].count, - todayUsers: todayUsers[0].count, - yesterdayUsers: yesterdayUsers[0].count, - monthlyGrowth: parseFloat(monthGrowthRate), - totalPoints: totalPoints[0].total, - activeUsers: activeUsers[0].count - } - }); - } catch (error) { - console.error('获取用户统计错误:', error); - res.status(500).json({success: false, message: '获取用户统计失败'}); - } -}); - -// 获取当前用户积分 -router.get('/points', auth, async (req, res) => { - try { - const userId = req.user.id; - - const [users] = await getDB().execute( - 'SELECT points FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - res.json({ - success: true, - points: users[0].points - }); - } catch (error) { - console.error('获取用户积分错误:', error); - res.status(500).json({success: false, message: '获取用户积分失败'}); - } -}); - -// 获取用户积分历史记录 -router.get('/points/history', auth, async (req, res) => { - try { - const userId = req.user.id; - const {page = 1, limit = 20, type} = req.query; - - // 确保参数为有效数字 - const pageNum = Math.max(1, parseInt(page) || 1); - const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 20)); - const offset = Math.max(0, (pageNum - 1) * limitNum); - - let whereClause = 'WHERE user_id = ?'; - let queryParams = [userId]; - - if (type && ['earn', 'spend'].includes(type)) { - whereClause += ' AND type = ?'; - queryParams.push(type); - } - - // 获取总数 - const [countResult] = await getDB().execute( - `SELECT COUNT(*) as total - FROM points_history ${whereClause}`, - queryParams - ); - - // 获取历史记录 - const [records] = await getDB().execute( - `SELECT id, type, amount, description, order_id, created_at - FROM points_history ${whereClause} - ORDER BY created_at DESC - LIMIT ${limitNum} OFFSET ${offset}`, - queryParams - ); - - res.json({ - success: true, - data: { - records, - pagination: { - page: pageNum, - limit: limitNum, - total: countResult[0].total, - totalPages: Math.ceil(countResult[0].total / limitNum) - } - } - }); - } catch (error) { - console.error('获取积分历史失败:', error); - res.status(500).json({success: false, message: '获取积分历史失败'}); - } -}); - -// 获取用户增长趋势数据(管理员权限) -router.get('/growth-trend', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const {days = 7} = req.query; - const daysNum = Math.min(90, Math.max(1, parseInt(days) || 7)); - - // 获取指定天数内的用户注册趋势 - const [trendData] = await db.execute(` - SELECT DATE(created_at) as date, - COUNT(*) as count - FROM users - WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY) - GROUP BY DATE(created_at) - ORDER BY date ASC - `, [daysNum]); - - // 填充缺失的日期(注册数为0) - const result = []; - - for (let i = daysNum - 1; i >= 0; i--) { - const date = dayjs().subtract(i, 'day'); - const dateStr = date.format('YYYY-MM-DD'); - - // 修复日期比较:将数据库返回的Date对象转换为字符串进行比较 - const existingData = trendData.find(item => { - const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); - return itemDateStr === dateStr; - }); - - result.push({ - date: date.format('MM-DD'), - count: existingData ? existingData.count : 0 - }); - } - - res.json({ - success: true, - data: result - }); - } catch (error) { - console.error('获取用户增长趋势错误:', error); - res.status(500).json({success: false, message: '获取用户增长趋势失败'}); - } -}); - -// 获取日收入统计数据(管理员权限) -router.get('/daily-revenue', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const {days = 30} = req.query; - const daysNum = Math.min(90, Math.max(1, parseInt(days) || 30)); - - // 获取指定天数内的用户注册数据,按天统计 - const [dailyData] = await db.execute(` - SELECT DATE(created_at) as date, - COUNT(*) as user_count - FROM users - WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY) - GROUP BY DATE(created_at) - ORDER BY date ASC - `, [daysNum]); - const [dailyDataTransfers] = await db.execute(` - SELECT DATE(created_at) as date, - SUM(CASE WHEN source_type IN ('system') THEN amount END) as amount - FROM transfers - WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY) - GROUP BY DATE(created_at) - ORDER BY date ASC - `, [daysNum]); - // 填充缺失的日期(注册数为0) - const result = []; - for (let i = daysNum - 1; i >= 0; i--) { - const date = dayjs().subtract(i, 'day'); - const dateStr = date.format('YYYY-MM-DD'); // YYYY-MM-DD格式 - const dateDisplay = date.format('M/D'); // 显示格式 - - const existingData = dailyData.find(item => { - const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); - return itemDateStr === dateStr; - }); - const existingDataTransfers = dailyDataTransfers.find(item => { - const itemDateStr = dayjs(item.date).format('YYYY-MM-DD'); - return itemDateStr === dateStr; - }); - const userCount = existingData ? existingData.user_count : 0; - const revenue = existingDataTransfers && existingDataTransfers.amount !== null ? existingDataTransfers.amount : 0; // 每个用户398元收入 - - result.push({ - date: dateDisplay, - userCount: userCount, - amount: revenue - }); - } - - res.json({ - success: true, - data: result - }); - } catch (error) { - console.error('获取日收入统计错误:', error); - res.status(500).json({success: false, message: '获取日收入统计失败'}); - } -}); -// 获取当前用户个人资料 -router.get('/profile', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.user.id; - - const [users] = await db.execute( - 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, created_at, updated_at FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - // 转换字段名以匹配前端 - const user = users[0]; - const profile = { - ...user, - nickname: user.username, // 添加nickname字段,映射到username - realName: user.real_name, - idCard: user.id_card, - wechatQr: user.wechat_qr, - alipayQr: user.alipay_qr, - bankCard: user.bank_card, - unionpayQr: user.unionpay_qr, - businessLicense: user.business_license, - idCardFront: user.id_card_front, - idCardBack: user.id_card_back, - auditStatus: user.audit_status - }; - - res.json({success: true, user: profile}); - } catch (error) { - console.error('获取用户个人资料错误:', error); - res.status(500).json({success: false, message: '获取用户个人资料失败'}); - } -}); - -// 更新当前用户个人资料 -router.put('/profile', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.user.id; - const { - username, - nickname, - avatar, - realName, - idCard, - phone, - wechatQr, - alipayQr, - bankCard, - unionpayQr, - businessLicense, - idCardFront, - idCardBack, - city, - districtId - } = req.body; - - // 处理nickname字段,如果提供了nickname,则使用nickname作为username - const finalUsername = nickname || username; - - // 检查用户名、身份证号和手机号是否已被其他用户使用 - if (finalUsername || idCard || phone) { - const conditions = []; - const checkValues = []; - - if (finalUsername) { - conditions.push('username = ?'); - checkValues.push(finalUsername); - } - if (idCard) { - conditions.push('id_card = ?'); - checkValues.push(idCard); - } - if (phone) { - conditions.push('phone = ?'); - checkValues.push(phone); - } - - if (conditions.length > 0) { - const [existingUsers] = await db.execute( - `SELECT id - FROM users - WHERE (${conditions.join(' OR ')}) - AND id != ?`, - [...checkValues, userId] - ); - - if (existingUsers.length > 0) { - return res.status(400).json({success: false, message: '用户名、身份证号或手机号已被使用'}); - } - } - } - - // 验证身份证号格式 - if (idCard) { - const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; - if (!idCardRegex.test(idCard)) { - return res.status(400).json({success: false, message: '身份证号格式不正确'}); - } - } - - // 构建更新字段 - const updateFields = []; - const updateValues = []; - - if (finalUsername !== undefined) { - updateFields.push('username = ?'); - updateValues.push(finalUsername); - } - - if (avatar !== undefined) { - updateFields.push('avatar = ?'); - updateValues.push(avatar); - } - - if (realName !== undefined) { - updateFields.push('real_name = ?'); - updateValues.push(realName); - } - - if (idCard !== undefined) { - updateFields.push('id_card = ?'); - updateValues.push(idCard); - } - - if (phone !== undefined) { - updateFields.push('phone = ?'); - updateValues.push(phone); - } - - // 添加城市和地区字段更新 - if (city !== undefined) { - updateFields.push('city = ?'); - updateValues.push(city); - } - - if (districtId !== undefined) { - updateFields.push('district_id = ?'); - updateValues.push(districtId); - } - - // 检查是否更新了需要重新审核的关键信息 - let needsReaudit = false; - - if (wechatQr !== undefined) { - updateFields.push('wechat_qr = ?'); - updateValues.push(wechatQr); - needsReaudit = true; - } - - if (alipayQr !== undefined) { - updateFields.push('alipay_qr = ?'); - updateValues.push(alipayQr); - needsReaudit = true; - } - - if (bankCard !== undefined) { - updateFields.push('bank_card = ?'); - updateValues.push(bankCard); - needsReaudit = true; - } - - if (unionpayQr !== undefined) { - updateFields.push('unionpay_qr = ?'); - updateValues.push(unionpayQr); - needsReaudit = true; - } - - if (city !== undefined) { - updateFields.push('city = ?'); - updateValues.push(city); - } - - if (districtId !== undefined) { - updateFields.push('district_id = ?'); - updateValues.push(districtId); - } - - if (businessLicense !== undefined) { - updateFields.push('business_license = ?'); - updateValues.push(businessLicense); - needsReaudit = true; - } - - if (idCardFront !== undefined) { - updateFields.push('id_card_front = ?'); - updateValues.push(idCardFront); - needsReaudit = true; - } - - if (idCardBack !== undefined) { - updateFields.push('id_card_back = ?'); - updateValues.push(idCardBack); - needsReaudit = true; - } - - // 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核 - if (needsReaudit && req.user.role !== 'admin') { - updateFields.push('audit_status = ?'); - updateValues.push('pending'); - } - - if (updateFields.length === 0) { - return res.status(400).json({success: false, message: '没有要更新的字段'}); - } - - updateValues.push(userId); - - await db.execute( - `UPDATE users - SET ${updateFields.join(', ')} - WHERE id = ?`, - updateValues - ); - - // 返回更新后的用户信息 - const [updatedUsers] = await db.execute( - 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, is_system_account, created_at, updated_at FROM users WHERE id = ?', - [userId] - ); - - // 转换字段名以匹配前端 - const user = updatedUsers[0]; - const profile = { - ...user, - nickname: user.username, // 添加nickname字段,映射到username - realName: user.real_name, - idCard: user.id_card, - wechatQr: user.wechat_qr, - alipayQr: user.alipay_qr, - bankCard: user.bank_card, - unionpayQr: user.unionpay_qr, - businessLicense: user.business_license, - idCardFront: user.id_card_front, - idCardBack: user.id_card_back, - auditStatus: user.audit_status - }; - - res.json({ - success: true, - message: '个人资料更新成功', - data: profile - }); - } catch (error) { - console.error('更新个人资料错误:', error); - res.status(500).json({success: false, message: '更新个人资料失败'}); - } -}); -// 获取用户详情 -router.get('/:id', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - - // 只有管理员或用户本人可以查看详情 - if (req.user.role !== 'admin' && req.user.id != userId) { - return res.status(403).json({success: false, message: '权限不足'}); - } - - const [users] = await db.execute( - 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, created_at, updated_at FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - res.json({success: true, user: users[0]}); - } catch (error) { - console.error('获取用户详情错误:', error); - res.status(500).json({success: false, message: '获取用户详情失败'}); - } -}); - -// 更新用户信息 -router.put('/:id', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - const { - username, - password, - role, - isSystemAccount, - avatar, - realName, - idCard, - phone, - wechatQr, - alipayQr, - bankCard, - unionpayQr, - idCardFront, - idCardBack, - businessLicense, - province, - city, - districtId, - user_type, - inviter, - } = req.body; - - // 只有管理员或用户本人可以更新信息 - if (req.user.role !== 'admin' && req.user.id != userId) { - return res.status(403).json({success: false, message: '权限不足'}); - } - - // 非管理员不能修改角色 - if (req.user.role !== 'admin' && role) { - return res.status(403).json({success: false, message: '无权限修改用户角色'}); - } - - // 检查用户名、身份证号和手机号是否已被其他用户使用 - if (username || idCard || phone) { - const conditions = []; - const checkValues = []; - - if (username) { - conditions.push('username = ?'); - checkValues.push(username); - } - if (idCard) { - conditions.push('id_card = ?'); - checkValues.push(idCard); - } - if (phone) { - conditions.push('phone = ?'); - checkValues.push(phone); - } - - if (conditions.length > 0) { - const [existingUsers] = await db.execute( - `SELECT id - FROM users - WHERE (${conditions.join(' OR ')}) - AND id != ?`, - [...checkValues, userId] - ); - - if (existingUsers.length > 0) { - return res.status(400).json({success: false, message: '用户名、身份证号或手机号已被使用'}); - } - } - } - - // 验证身份证号格式 - if (idCard) { - const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; - if (!idCardRegex.test(idCard)) { - return res.status(400).json({success: false, message: '身份证号格式不正确'}); - } - } - - // 构建更新字段 - const updateFields = []; - const updateValues = []; - - if (username) { - updateFields.push('username = ?'); - updateValues.push(username); - } - - // 处理密码更新 - if (password && password.trim() !== '') { - const hashedPassword = await bcrypt.hash(password, 10); - updateFields.push('password = ?'); - updateValues.push(hashedPassword); - } - - if (role && req.user.role === 'admin') { - updateFields.push('role = ?'); - updateValues.push(role); - } - - // 只有管理员可以修改账户类型 - if (isSystemAccount !== undefined && req.user.role === 'admin') { - updateFields.push('is_system_account = ?'); - updateValues.push(isSystemAccount); - } - - if (avatar !== undefined) { - updateFields.push('avatar = ?'); - updateValues.push(avatar); - } - - if (realName !== undefined) { - updateFields.push('real_name = ?'); - updateValues.push(realName); - } - - if (idCard !== undefined) { - updateFields.push('id_card = ?'); - updateValues.push(idCard); - } - - if (phone !== undefined) { - updateFields.push('phone = ?'); - updateValues.push(phone); - } - if (city !== undefined) { - updateFields.push('city = ?'); - updateValues.push(city); - } - - if (districtId !== undefined) { - updateFields.push('district_id = ?'); - updateValues.push(districtId); - } - // 检查是否更新了需要重新审核的关键信息 - let needsReaudit = false; - - if (wechatQr !== undefined) { - updateFields.push('wechat_qr = ?'); - updateValues.push(wechatQr); - needsReaudit = true; - } - - if (alipayQr !== undefined) { - updateFields.push('alipay_qr = ?'); - updateValues.push(alipayQr); - needsReaudit = true; - } - - if (bankCard !== undefined) { - updateFields.push('bank_card = ?'); - updateValues.push(bankCard); - needsReaudit = true; - } - - if (unionpayQr !== undefined) { - updateFields.push('unionpay_qr = ?'); - updateValues.push(unionpayQr); - needsReaudit = true; - } - if (idCardFront !== undefined) { - updateFields.push('id_card_front = ?'); - updateValues.push(idCardFront); - needsReaudit = true; - } - if (idCardBack !== undefined) { - updateFields.push('id_card_back = ?'); - updateValues.push(idCardBack); - needsReaudit = true; - } - if (province !== undefined) { - updateFields.push('province = ?'); - updateValues.push(province); - needsReaudit = true; - } - if (businessLicense !== undefined) { - updateFields.push('business_license = ?'); - updateValues.push(businessLicense); - needsReaudit = true; - } - if (user_type !== undefined) { - updateFields.push('user_type = ?'); - updateValues.push(user_type); - needsReaudit = true; - } - if (inviter !== undefined) { - updateFields.push('inviter = ?'); - updateValues.push(inviter); - needsReaudit = true; - } - - // 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核 - if (needsReaudit && req.user.role !== 'admin') { - updateFields.push('audit_status = ?'); - updateValues.push('pending'); - } - - if (updateFields.length === 0) { - return res.status(400).json({success: false, message: '没有要更新的字段'}); - } - - updateValues.push(userId); - - await db.execute( - `UPDATE users - SET ${updateFields.join(', ')} - WHERE id = ?`, - updateValues - ); - - // 返回更新后的用户信息 - const [updatedUsers] = await db.execute( - 'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, city, district_id, province, created_at, updated_at FROM users WHERE id = ?', - [userId] - ); - - res.json({ - success: true, - message: '用户信息更新成功', - user: updatedUsers[0] - }); - } catch (error) { - console.error('更新用户信息错误:', error); - res.status(500).json({success: false, message: '更新用户信息失败'}); - } -}); - - -// 删除用户(管理员权限) -router.delete('/:id', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - - // 不能删除自己 - if (req.user.id == userId) { - return res.status(400).json({success: false, message: '不能删除自己的账户'}); - } - - // 检查用户是否存在 - const [users] = await db.execute( - 'SELECT id FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - // 删除用户 - await db.execute('DELETE FROM users WHERE id = ?', [userId]); - - res.json({success: true, message: '用户删除成功'}); - } catch (error) { - console.error('删除用户错误:', error); - res.status(500).json({success: false, message: '删除用户失败'}); - } -}); - -/** - * 审核用户(管理员权限) - */ -router.put('/:id/audit', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - const {action, note} = req.body; // action: 'approve' 或 'reject' - - if (!action || !['approve', 'reject'].includes(action)) { - return res.status(400).json({success: false, message: '审核操作无效'}); - } - - // 检查用户是否存在且为待审核状态 - const [users] = await db.execute( - 'SELECT id, username, audit_status FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - const user = users[0]; - if (user.audit_status !== 'pending') { - return res.status(400).json({success: false, message: '该用户不是待审核状态'}); - } - - // 更新审核状态 - const auditStatus = action === 'approve' ? 'approved' : 'rejected'; - await db.execute( - `UPDATE users - SET audit_status = ?, - audit_note = ?, - audited_by = ?, - audited_at = NOW() - WHERE id = ?`, - [auditStatus, note || null, req.user.id, userId] - ); - - const message = action === 'approve' ? '用户审核通过' : '用户审核拒绝'; - res.json({success: true, message}); - } catch (error) { - console.error('审核用户错误:', error); - res.status(500).json({success: false, message: '审核用户失败'}); - } -}); - -/** - * 获取用户审核详情(管理员权限) - */ -router.get('/:id/audit-detail', auth, adminAuth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - - // 获取用户详细信息 - const [users] = await db.execute( - `SELECT u.id, - u.username, - u.phone, - u.real_name, - u.business_license, - u.id_card_front, - u.id_card_back, - u.audit_status, - u.audit_note, - u.audited_at, - u.created_at, - auditor.username as auditor_name - FROM users u - LEFT JOIN users auditor ON u.audited_by = auditor.id - WHERE u.id = ?`, - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - - res.json({ - success: true, - data: users[0] - }); - } catch (error) { - console.error('获取用户审核详情错误:', error); - res.status(500).json({success: false, message: '获取用户审核详情失败'}); - } -}); - -router.put('/:id/distribute', auth, async (req, res) => { - try { - const db = getDB(); - const userId = req.params.id; - const {is_distribute} = req.body; - - if (typeof is_distribute !== 'boolean') { - return res.status(400).json({success: false, message: '分发状态无效'}); - } - - - // 检查用户是否存在 - const [users] = await db.execute( - 'SELECT id,user_type FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({success: false, message: '用户不存在'}); - } - if (users[0].user_type === 'directly_operated') { - return res.status(400).json({success: false, message: '直营用户不允许开启委托出售'}); - } - let [isServiceFee] = await db.execute('SELECT COUNT(*) AS total FROM distribution WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND user_id = ?', [userId]); - if (isServiceFee[0].total > 0) { - // 更新分发状态 - await db.execute( - 'UPDATE users SET is_distribute = ? WHERE id = ?', - [is_distribute, userId] - ); - res.json({ - success: true, - message: '分发状态更新成功', - is_distribute - }); - } else { - return res.json({success: false, message: '请缴纳2980融豆服务费'}); - } - } catch (error) { - - } -}) -/** - * 扣除2980融豆服务费 - */ -router.post('/:id/deduct-service-fee', auth, async (req, res) => { - const db = getDB(); - try { - const userId = req.params.id; - const serviceFee = 2980; // 服务费金额 - - // 开始事务 - await db.query('START TRANSACTION'); - - // 使用行级锁定查询用户信息,只锁定当前用户记录 - const [users] = await db.execute( - 'SELECT id, balance, username,district_id FROM users WHERE id = ? FOR UPDATE', - [userId] - ); - - if (users.length === 0) { - await db.query('ROLLBACK'); - return res.status(404).json({success: false, message: '用户不存在'}); - } - //判断今年是否已扣款 - let [isServiceFee] = await db.execute('SELECT COUNT(*) AS total FROM distribution WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND user_id = ?', [userId]); - if (isServiceFee[0].total > 0) { - return res.status(400).json({success: false, message: '已缴纳2980融豆服务费'}); - } - const user = users[0]; - const currentBalance = Math.abs(user.balance); // 将负数转为正数处理 - - // 检查融豆余额是否足够 - if (currentBalance < serviceFee) { - await db.query('ROLLBACK'); - return res.status(400).json({ - success: false, - message: `融豆余额不足,当前余额:${currentBalance},需要:${serviceFee}` - }); - } - - // 扣除融豆(balance字段为负数,所以减去服务费实际是增加负数) - await db.execute( - 'UPDATE users SET balance = balance + ? WHERE id = ?', - [serviceFee, userId] - ); - //查找上级分销 - let [distribute] = await db.execute( - 'SELECT inviter FROM users WHERE id = ?', - [userId] - ); - distribute = distribute[0] - //如果有上级分销 - if (distribute.inviter) { - // 查找上级分销 - let [distributeUser] = await db.execute( - 'SELECT id, balance,user_type,inviter FROM users WHERE id = ?', - [distribute.inviter] - ); - distributeUser = distributeUser[0] - if (distributeUser.user_type == 'agent') { - //给代理添加2980融豆的70% 增加区域保护 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.7, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.7, '用户服务费返现', 'agent'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system'] - ); - //记录服务费 - await db.execute( - 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)', - [userId, distributeUser.id, serviceFee, 'agent'] - ) - } - //如果不是代理,查看是否是直营代理 - if (distributeUser.user_type == 'agent_directly') { - //给直营代理50%融豆给平台50%融豆 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.5, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent_operated'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] - ); - //记录服务费 - await db.execute( - 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)', - [userId, distributeUser.id, serviceFee, 'direct_agent'] - ) - } - //是否是直营 - if (distributeUser.user_type == 'directly_operated') { - //查询这个月直营做了多少单 - let [orderCount] = await db.execute( - `SELECT COUNT(*) AS total - FROM distribution - WHERE agent_id = ? - AND created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')`, - [distributeUser.id] - ); - orderCount = orderCount[0] - if (orderCount.total <= 5) { - //给直营代理20%融豆给平台50% 融豆给用户30% - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.2, distributeUser.inviter] - ); - //给直营添加30%融豆 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.3, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.2, '用户服务费返现', 'operated_agent'] - ); - //记录直营利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.3, '用户服务费返现', 'directly_operated'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] - ); - } - if (orderCount.total > 5 && orderCount.total <= 15) { - //给直营代理20%融豆给平台50%融豆给用户30% - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.15, distributeUser.inviter] - ); - //给直营添加30%融豆 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.35, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.15, '用户服务费返现', 'agent_operated'] - ); - //记录直营利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.35, '用户服务费返现', 'directly_operated'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] - ); - } - if (orderCount.total > 15) { - //给直营代理20%融豆给平台50%融豆给用户30% - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.1, distributeUser.inviter] - ); - //给直营添加30%融豆 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.4, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.inviter, 'user_to_agent', 'received', serviceFee * 0.1, '用户服务费返现', 'agent_operated'] - ); - //记录直营利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_operated', 'received', serviceFee * 0.4, '用户服务费返现', 'directly_operated'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.5, '用户服务费返现', 'system'] - ); - } - //记录服务费 - await db.execute( - 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)', - [userId, distributeUser.id, serviceFee, 'direct_agent'] - ) - } - //要是用户之间分销 - if (distributeUser.user_type == 'user') { - //查询用户是否有上级 - let [userUpInfo] = await db.execute( - `SELECT * - FROM users - WHERE id = ?`, - [distributeUser.inviter] - ) - userUpInfo = userUpInfo[0] - //判断用户上级是否是代理 - if (userUpInfo && userUpInfo.user_type === 'agent') { - //给用户分配 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.2, distributeUser.id] - ); - //给代理分配 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.5, userUpInfo.id] - ); - //记录用户转账信息 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_user', 'received', serviceFee * 0.2, '用户服务费返现', 'operated'] - ); - //记录代理转账信息 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, userUpInfo.id, 'user_to_agent', 'received', serviceFee * 0.5, '用户服务费返现', 'agent'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.3, '用户服务费返现', 'system'] - ); - } else { - //用户没有上级 - await db.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [serviceFee * 0.2, distributeUser.id] - ); - //记录转账记录 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, distributeUser.id, 'user_to_user', 'received', serviceFee * 0.2, '用户服务费返现', 'operated'] - ); - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.8, '用户服务费返现', 'system'] - ); - - } - //记录服务费 - await db.execute( - 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)', - [userId, distributeUser.id, serviceFee, 'user'] - ) - } - - } else { - //判断用户此区域是否有代理 - let [agentUser] = await db.execute(` - SELECT user_id - FROM regional_agents - WHERE region_id = ? - `, [users[0].district_id]) - if (agentUser.length > 0) { - //给代理分成5% - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, agentUser[0].user_id, 'user_to_regional', 'received', serviceFee * 0.05, '区域保护服务费返现', 'agent'] - ) - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee * 0.95, '用户服务费返现', 'system'] - ); - } else { - //记录平台利润 - await db.execute( - 'INSERT INTO transfers (from_user_id, to_user_id, transfer_type,status,amount,description,source_type) VALUES (?, ?, ?,?,?,?,?)', - [userId, 3512, 'user_to_system', 'received', serviceFee, '用户服务费返现', 'system'] - ); - - } - await db.execute( - 'INSERT INTO distribution (user_id,agent_id, amount, type) VALUES (?, ?, ?,?)', - [userId, 3512, serviceFee, 'user'] - ) - - } - await db.execute( - 'UPDATE users SET is_distribute = ? WHERE id = ?', - [true, userId] - ); - // 提交事务 - await db.query('COMMIT'); - - res.json({ - success: true, - message: '服务费扣除成功', - data: { - user_id: userId, - username: user.username, - deducted_amount: serviceFee, - remaining_balance: currentBalance - serviceFee - } - }); - - } catch (error) { - try { - // 发生错误时回滚事务 - await db.query('ROLLBACK'); - } catch (rollbackError) { - console.error('回滚失败:', rollbackError); - } - console.error('扣除服务费失败:', error); - res.status(500).json({success: false, message: '扣除服务费失败'}); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/wechatPay.js b/routes/wechatPay.js deleted file mode 100644 index 149e337..0000000 --- a/routes/wechatPay.js +++ /dev/null @@ -1,202 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const WechatPayService = require('../services/wechatPayService'); -const { getDB } = require('../database'); -const { auth, paymentAuth } = require('../middleware/auth'); - -// 创建微信支付服务实例 -const wechatPayService = new WechatPayService(); - -/** - * 创建注册支付订单 (API v3 - H5支付) - * POST /api/wechat-pay/create-registration-order - */ -router.post('/create-registration-order', paymentAuth, async (req, res) => { - try { - const userId = req.user.id; - const username = req.user.username; - const phone = req.user.phone; - - // 获取客户端IP - const clientIp = req.headers['x-forwarded-for'] || - req.headers['x-real-ip'] || - req.connection.remoteAddress || - req.socket.remoteAddress || - (req.connection.socket ? req.connection.socket.remoteAddress : null) || - '127.0.0.1'; - - // 检查用户是否已经支付过 - const db = getDB(); - const [existingOrders] = await db.execute( - 'SELECT id FROM payment_orders WHERE user_id = ? AND status = "paid"', - [userId] - ); - - if (existingOrders.length > 0) { - return res.status(400).json({ - success: false, - message: '用户已完成支付,无需重复支付' - }); - } - - console.log('创建H5支付订单:', { - userId, - username, - phone, - clientIp - }); - - // 创建H5支付订单 - const result = await wechatPayService.createRegistrationPayOrder({ - userId, - username, - phone, - clientIp - }); - - if (result.success) { - res.json({ - success: true, - data: { - outTradeNo: result.data.outTradeNo, - h5Url: result.data.h5Url, - paymentType: result.data.paymentType - } - }); - } else { - res.status(500).json({ - success: false, - message: '创建支付订单失败' - }); - } - } catch (error) { - console.error('创建H5支付订单异常:', error); - res.status(500).json({ - success: false, - message: error.message || '服务器内部错误' - }); - } -}); - -// H5支付不需要获取openid,移除相关接口 - -/** - * 微信支付回调接口 (API v3) - * POST /api/wechat-pay/notify - */ -router.post('/notify', async (req, res) => { - try { - // API v3 回调是JSON格式 - const notifyData = req.body; - - // 获取请求头中的签名信息 - const signature = req.headers['wechatpay-signature']; - const timestamp = req.headers['wechatpay-timestamp']; - const nonce = req.headers['wechatpay-nonce']; - const serial = req.headers['wechatpay-serial']; - - console.log('收到API v3支付回调:', { - signature, - timestamp, - nonce, - serial, - body: notifyData - }); - - // 验证签名和处理回调 - const result = await wechatPayService.handleV3PaymentNotify({ - signature, - timestamp, - nonce, - serial, - body: JSON.stringify(notifyData) - }); - - if (result.success) { - // API v3 成功响应 - res.status(200).json({ code: 'SUCCESS', message: '成功' }); - } else { - // API v3 失败响应 - res.status(400).json({ code: 'FAIL', message: result.message || '处理失败' }); - } - } catch (error) { - console.error('支付回调处理异常:', error); - res.status(500).json({ code: 'ERROR', message: '服务器内部错误' }); - } -}); - -/** - * 查询支付状态 - * GET /api/wechat-pay/query-status/:outTradeNo - */ -router.get('/query-status/:outTradeNo', paymentAuth, async (req, res) => { - try { - const { outTradeNo } = req.params; - const userId = req.user.id; - - // 验证订单是否属于当前用户 - const db = getDB(); - const [orders] = await db.execute( - 'SELECT id FROM payment_orders WHERE out_trade_no = ? AND user_id = ?', - [outTradeNo, userId] - ); - - if (orders.length === 0) { - return res.status(404).json({ - success: false, - message: '订单不存在或无权限访问' - }); - } - - const result = await wechatPayService.queryPaymentStatus(outTradeNo); - res.json(result); - } catch (error) { - console.error('查询支付状态失败:', error); - res.status(500).json({ - success: false, - message: error.message || '查询支付状态失败' - }); - } -}); - -/** - * 检查用户支付状态 - * GET /api/wechat-pay/check-user-payment - */ -router.get('/check-user-payment', auth, async (req, res) => { - try { - const userId = req.user.id; - const db = getDB(); - - // 查询用户支付状态 - const [users] = await db.execute( - 'SELECT payment_status FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - return res.status(404).json({ - success: false, - message: '用户不存在' - }); - } - - const paymentStatus = users[0].payment_status; - - res.json({ - success: true, - data: { - paymentStatus, - isPaid: paymentStatus === 'paid' - } - }); - } catch (error) { - console.error('检查用户支付状态失败:', error); - res.status(500).json({ - success: false, - message: '检查支付状态失败' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/scripts/README_MERGE_TABLES.md b/scripts/README_MERGE_TABLES.md deleted file mode 100644 index 4b5a980..0000000 --- a/scripts/README_MERGE_TABLES.md +++ /dev/null @@ -1,56 +0,0 @@ -# 表合并说明文档 - -## 概述 - -本文档说明如何使用 `merge_tables.js` 脚本将 `order_allocations` 表数据合并到 `transfers` 表中,以实现数据库结构优化。 - -## 背景 - -原系统中,`order_allocations` 表和 `transfers` 表存在功能重叠,为了简化数据库结构和提高查询效率,我们决定将 `order_allocations` 表的数据合并到 `transfers` 表中,并通过 `source_type` 字段区分不同来源的转账记录。 - -## 合并策略 - -1. 为 `transfers` 表添加必要的字段,包括 `source_type`、`matching_order_id`、`cycle_number` 等 -2. 将 `order_allocations` 表中的数据迁移到 `transfers` 表 -3. 更新相关的外键引用 - -## 使用方法 - -### 前置条件 - -1. 确保已对数据库进行备份 -2. 确保系统处于维护状态,没有用户正在使用 - -### 执行步骤 - -1. 进入项目根目录 -2. 执行以下命令运行合并脚本: - -```bash -node scripts/merge_tables.js -``` - -3. 脚本执行完成后,检查控制台输出,确认迁移是否成功 - -### 验证步骤 - -1. 检查 `transfers` 表中是否包含所有 `order_allocations` 表的数据 -2. 验证系统功能是否正常,特别是与匹配订单相关的功能 -3. 确认无误后,可以考虑删除 `order_allocations` 表(可选) - -## 回滚方案 - -如果合并过程中出现问题,或者合并后系统功能异常,可以通过以下步骤回滚: - -1. 使用之前的数据库备份恢复数据 -2. 如果没有备份,可以手动将 `transfers` 表中 `source_type='allocation'` 的记录删除,并重新运行原有的系统 - -## 注意事项 - -1. 合并过程会自动处理已有关联的记录,避免重复迁移 -2. 合并脚本使用事务进行操作,确保数据一致性 -3. 建议在测试环境验证成功后再在生产环境执行 - -## 技术支持 - -如有问题,请联系技术支持团队。 \ No newline at end of file diff --git a/scripts/import_china_regions.js b/scripts/import_china_regions.js deleted file mode 100644 index 99af78d..0000000 --- a/scripts/import_china_regions.js +++ /dev/null @@ -1,144 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const mysql = require('mysql2/promise'); -require('dotenv').config(); - -// 数据库配置 -const dbConfig = { - host: process.env.DB_HOST || '114.55.111.44', - user: process.env.DB_USER || 'maov2', - password: process.env.DB_PASSWORD || '5fYhw8z6T62b7heS', - database: process.env.DB_NAME || 'maov2', -}; - -// 初始化数据库连接 -async function initDB() { - try { - const connection = await mysql.createConnection(dbConfig); - console.log('数据库连接成功'); - return connection; - } catch (error) { - console.error('数据库连接失败:', error); - throw error; - } -} - -// 递归解析省市区数据 -function parseRegionData(regions, parentCode = null, level = 1) { - const result = []; - let sortOrder = 1; - - for (const region of regions) { - // 添加当前区域 - result.push({ - code: region.code, - name: region.name, - parent_code: parentCode, - level: level, - sort_order: sortOrder++ - }); - - // 递归处理子区域 - if (region.children && region.children.length > 0) { - const childrenData = parseRegionData(region.children, region.code, level + 1); - result.push(...childrenData); - } - } - - return result; -} - -// 导入省市区数据 -async function importChinaRegions() { - let connection; - - try { - // 读取 JSON 数据文件 - const jsonFilePath = path.join(__dirname, 'pca-code.json'); - const jsonData = fs.readFileSync(jsonFilePath, 'utf8'); - const regionsData = JSON.parse(jsonData); - - console.log('成功读取省市区数据文件'); - - // 解析数据 - const parsedData = parseRegionData(regionsData); - console.log(`解析完成,共 ${parsedData.length} 条记录`); - - // 连接数据库 - connection = await initDB(); - - // 清空现有数据 - await connection.execute('DELETE FROM china_regions'); - console.log('已清空现有数据'); - - // 批量插入数据 - const batchSize = 100; - let insertedCount = 0; - - for (let i = 0; i < parsedData.length; i += batchSize) { - const batch = parsedData.slice(i, i + batchSize); - const values = batch.map(item => [ - item.code, - item.name, - item.parent_code, - item.level, - item.sort_order - ]); - - const placeholders = values.map(() => '(?, ?, ?, ?, ?)').join(', '); - const flatValues = values.flat(); - - await connection.execute( - `INSERT INTO china_regions (code, name, parent_code, level, sort_order) VALUES ${placeholders}`, - flatValues - ); - - insertedCount += batch.length; - console.log(`已插入 ${insertedCount}/${parsedData.length} 条记录`); - } - - // 统计导入结果 - const [provinceResult] = await connection.execute( - 'SELECT COUNT(*) as count FROM china_regions WHERE level = 1' - ); - const [cityResult] = await connection.execute( - 'SELECT COUNT(*) as count FROM china_regions WHERE level = 2' - ); - const [districtResult] = await connection.execute( - 'SELECT COUNT(*) as count FROM china_regions WHERE level = 3' - ); - const [totalResult] = await connection.execute( - 'SELECT COUNT(*) as count FROM china_regions' - ); - - console.log('\n=== 导入完成 ==='); - console.log(`省份数量: ${provinceResult[0].count}`); - console.log(`城市数量: ${cityResult[0].count}`); - console.log(`区县数量: ${districtResult[0].count}`); - console.log(`总记录数: ${totalResult[0].count}`); - - } catch (error) { - console.error('导入失败:', error); - throw error; - } finally { - if (connection) { - await connection.end(); - console.log('数据库连接已关闭'); - } - } -} - -// 如果直接运行此脚本 -if (require.main === module) { - importChinaRegions() - .then(() => { - console.log('省市区数据导入成功!'); - process.exit(0); - }) - .catch((error) => { - console.error('导入过程中发生错误:', error); - process.exit(1); - }); -} - -module.exports = { importChinaRegions }; \ No newline at end of file diff --git a/scripts/pca-code.json b/scripts/pca-code.json deleted file mode 100644 index aaca902..0000000 --- a/scripts/pca-code.json +++ /dev/null @@ -1,14625 +0,0 @@ -[ - { - "code": "11", - "name": "北京市", - "children": [ - { - "code": "1101", - "name": "市辖区", - "children": [ - { - "code": "110101", - "name": "东城区" - }, - { - "code": "110102", - "name": "西城区" - }, - { - "code": "110105", - "name": "朝阳区" - }, - { - "code": "110106", - "name": "丰台区" - }, - { - "code": "110107", - "name": "石景山区" - }, - { - "code": "110108", - "name": "海淀区" - }, - { - "code": "110109", - "name": "门头沟区" - }, - { - "code": "110111", - "name": "房山区" - }, - { - "code": "110112", - "name": "通州区" - }, - { - "code": "110113", - "name": "顺义区" - }, - { - "code": "110114", - "name": "昌平区" - }, - { - "code": "110115", - "name": "大兴区" - }, - { - "code": "110116", - "name": "怀柔区" - }, - { - "code": "110117", - "name": "平谷区" - }, - { - "code": "110118", - "name": "密云区" - }, - { - "code": "110119", - "name": "延庆区" - } - ] - } - ] - }, - { - "code": "12", - "name": "天津市", - "children": [ - { - "code": "1201", - "name": "市辖区", - "children": [ - { - "code": "120101", - "name": "和平区" - }, - { - "code": "120102", - "name": "河东区" - }, - { - "code": "120103", - "name": "河西区" - }, - { - "code": "120104", - "name": "南开区" - }, - { - "code": "120105", - "name": "河北区" - }, - { - "code": "120106", - "name": "红桥区" - }, - { - "code": "120110", - "name": "东丽区" - }, - { - "code": "120111", - "name": "西青区" - }, - { - "code": "120112", - "name": "津南区" - }, - { - "code": "120113", - "name": "北辰区" - }, - { - "code": "120114", - "name": "武清区" - }, - { - "code": "120115", - "name": "宝坻区" - }, - { - "code": "120116", - "name": "滨海新区" - }, - { - "code": "120117", - "name": "宁河区" - }, - { - "code": "120118", - "name": "静海区" - }, - { - "code": "120119", - "name": "蓟州区" - } - ] - } - ] - }, - { - "code": "13", - "name": "河北省", - "children": [ - { - "code": "1301", - "name": "石家庄市", - "children": [ - { - "code": "130102", - "name": "长安区" - }, - { - "code": "130104", - "name": "桥西区" - }, - { - "code": "130105", - "name": "新华区" - }, - { - "code": "130107", - "name": "井陉矿区" - }, - { - "code": "130108", - "name": "裕华区" - }, - { - "code": "130109", - "name": "藁城区" - }, - { - "code": "130110", - "name": "鹿泉区" - }, - { - "code": "130111", - "name": "栾城区" - }, - { - "code": "130121", - "name": "井陉县" - }, - { - "code": "130123", - "name": "正定县" - }, - { - "code": "130125", - "name": "行唐县" - }, - { - "code": "130126", - "name": "灵寿县" - }, - { - "code": "130127", - "name": "高邑县" - }, - { - "code": "130128", - "name": "深泽县" - }, - { - "code": "130129", - "name": "赞皇县" - }, - { - "code": "130130", - "name": "无极县" - }, - { - "code": "130131", - "name": "平山县" - }, - { - "code": "130132", - "name": "元氏县" - }, - { - "code": "130133", - "name": "赵县" - }, - { - "code": "130171", - "name": "石家庄高新技术产业开发区" - }, - { - "code": "130172", - "name": "石家庄循环化工园区" - }, - { - "code": "130181", - "name": "辛集市" - }, - { - "code": "130183", - "name": "晋州市" - }, - { - "code": "130184", - "name": "新乐市" - } - ] - }, - { - "code": "1302", - "name": "唐山市", - "children": [ - { - "code": "130202", - "name": "路南区" - }, - { - "code": "130203", - "name": "路北区" - }, - { - "code": "130204", - "name": "古冶区" - }, - { - "code": "130205", - "name": "开平区" - }, - { - "code": "130207", - "name": "丰南区" - }, - { - "code": "130208", - "name": "丰润区" - }, - { - "code": "130209", - "name": "曹妃甸区" - }, - { - "code": "130224", - "name": "滦南县" - }, - { - "code": "130225", - "name": "乐亭县" - }, - { - "code": "130227", - "name": "迁西县" - }, - { - "code": "130229", - "name": "玉田县" - }, - { - "code": "130271", - "name": "河北唐山芦台经济开发区" - }, - { - "code": "130272", - "name": "唐山市汉沽管理区" - }, - { - "code": "130273", - "name": "唐山高新技术产业开发区" - }, - { - "code": "130274", - "name": "河北唐山海港经济开发区" - }, - { - "code": "130281", - "name": "遵化市" - }, - { - "code": "130283", - "name": "迁安市" - }, - { - "code": "130284", - "name": "滦州市" - } - ] - }, - { - "code": "1303", - "name": "秦皇岛市", - "children": [ - { - "code": "130302", - "name": "海港区" - }, - { - "code": "130303", - "name": "山海关区" - }, - { - "code": "130304", - "name": "北戴河区" - }, - { - "code": "130306", - "name": "抚宁区" - }, - { - "code": "130321", - "name": "青龙满族自治县" - }, - { - "code": "130322", - "name": "昌黎县" - }, - { - "code": "130324", - "name": "卢龙县" - }, - { - "code": "130371", - "name": "秦皇岛市经济技术开发区" - }, - { - "code": "130372", - "name": "北戴河新区" - } - ] - }, - { - "code": "1304", - "name": "邯郸市", - "children": [ - { - "code": "130402", - "name": "邯山区" - }, - { - "code": "130403", - "name": "丛台区" - }, - { - "code": "130404", - "name": "复兴区" - }, - { - "code": "130406", - "name": "峰峰矿区" - }, - { - "code": "130407", - "name": "肥乡区" - }, - { - "code": "130408", - "name": "永年区" - }, - { - "code": "130423", - "name": "临漳县" - }, - { - "code": "130424", - "name": "成安县" - }, - { - "code": "130425", - "name": "大名县" - }, - { - "code": "130426", - "name": "涉县" - }, - { - "code": "130427", - "name": "磁县" - }, - { - "code": "130430", - "name": "邱县" - }, - { - "code": "130431", - "name": "鸡泽县" - }, - { - "code": "130432", - "name": "广平县" - }, - { - "code": "130433", - "name": "馆陶县" - }, - { - "code": "130434", - "name": "魏县" - }, - { - "code": "130435", - "name": "曲周县" - }, - { - "code": "130471", - "name": "邯郸经济技术开发区" - }, - { - "code": "130473", - "name": "邯郸冀南新区" - }, - { - "code": "130481", - "name": "武安市" - } - ] - }, - { - "code": "1305", - "name": "邢台市", - "children": [ - { - "code": "130502", - "name": "襄都区" - }, - { - "code": "130503", - "name": "信都区" - }, - { - "code": "130505", - "name": "任泽区" - }, - { - "code": "130506", - "name": "南和区" - }, - { - "code": "130522", - "name": "临城县" - }, - { - "code": "130523", - "name": "内丘县" - }, - { - "code": "130524", - "name": "柏乡县" - }, - { - "code": "130525", - "name": "隆尧县" - }, - { - "code": "130528", - "name": "宁晋县" - }, - { - "code": "130529", - "name": "巨鹿县" - }, - { - "code": "130530", - "name": "新河县" - }, - { - "code": "130531", - "name": "广宗县" - }, - { - "code": "130532", - "name": "平乡县" - }, - { - "code": "130533", - "name": "威县" - }, - { - "code": "130534", - "name": "清河县" - }, - { - "code": "130535", - "name": "临西县" - }, - { - "code": "130571", - "name": "河北邢台经济开发区" - }, - { - "code": "130581", - "name": "南宫市" - }, - { - "code": "130582", - "name": "沙河市" - } - ] - }, - { - "code": "1306", - "name": "保定市", - "children": [ - { - "code": "130602", - "name": "竞秀区" - }, - { - "code": "130606", - "name": "莲池区" - }, - { - "code": "130607", - "name": "满城区" - }, - { - "code": "130608", - "name": "清苑区" - }, - { - "code": "130609", - "name": "徐水区" - }, - { - "code": "130623", - "name": "涞水县" - }, - { - "code": "130624", - "name": "阜平县" - }, - { - "code": "130626", - "name": "定兴县" - }, - { - "code": "130627", - "name": "唐县" - }, - { - "code": "130628", - "name": "高阳县" - }, - { - "code": "130629", - "name": "容城县" - }, - { - "code": "130630", - "name": "涞源县" - }, - { - "code": "130631", - "name": "望都县" - }, - { - "code": "130632", - "name": "安新县" - }, - { - "code": "130633", - "name": "易县" - }, - { - "code": "130634", - "name": "曲阳县" - }, - { - "code": "130635", - "name": "蠡县" - }, - { - "code": "130636", - "name": "顺平县" - }, - { - "code": "130637", - "name": "博野县" - }, - { - "code": "130638", - "name": "雄县" - }, - { - "code": "130671", - "name": "保定高新技术产业开发区" - }, - { - "code": "130672", - "name": "保定白沟新城" - }, - { - "code": "130681", - "name": "涿州市" - }, - { - "code": "130682", - "name": "定州市" - }, - { - "code": "130683", - "name": "安国市" - }, - { - "code": "130684", - "name": "高碑店市" - } - ] - }, - { - "code": "1307", - "name": "张家口市", - "children": [ - { - "code": "130702", - "name": "桥东区" - }, - { - "code": "130703", - "name": "桥西区" - }, - { - "code": "130705", - "name": "宣化区" - }, - { - "code": "130706", - "name": "下花园区" - }, - { - "code": "130708", - "name": "万全区" - }, - { - "code": "130709", - "name": "崇礼区" - }, - { - "code": "130722", - "name": "张北县" - }, - { - "code": "130723", - "name": "康保县" - }, - { - "code": "130724", - "name": "沽源县" - }, - { - "code": "130725", - "name": "尚义县" - }, - { - "code": "130726", - "name": "蔚县" - }, - { - "code": "130727", - "name": "阳原县" - }, - { - "code": "130728", - "name": "怀安县" - }, - { - "code": "130730", - "name": "怀来县" - }, - { - "code": "130731", - "name": "涿鹿县" - }, - { - "code": "130732", - "name": "赤城县" - }, - { - "code": "130771", - "name": "张家口经济开发区" - }, - { - "code": "130772", - "name": "张家口市察北管理区" - }, - { - "code": "130773", - "name": "张家口市塞北管理区" - } - ] - }, - { - "code": "1308", - "name": "承德市", - "children": [ - { - "code": "130802", - "name": "双桥区" - }, - { - "code": "130803", - "name": "双滦区" - }, - { - "code": "130804", - "name": "鹰手营子矿区" - }, - { - "code": "130821", - "name": "承德县" - }, - { - "code": "130822", - "name": "兴隆县" - }, - { - "code": "130824", - "name": "滦平县" - }, - { - "code": "130825", - "name": "隆化县" - }, - { - "code": "130826", - "name": "丰宁满族自治县" - }, - { - "code": "130827", - "name": "宽城满族自治县" - }, - { - "code": "130828", - "name": "围场满族蒙古族自治县" - }, - { - "code": "130871", - "name": "承德高新技术产业开发区" - }, - { - "code": "130881", - "name": "平泉市" - } - ] - }, - { - "code": "1309", - "name": "沧州市", - "children": [ - { - "code": "130902", - "name": "新华区" - }, - { - "code": "130903", - "name": "运河区" - }, - { - "code": "130921", - "name": "沧县" - }, - { - "code": "130922", - "name": "青县" - }, - { - "code": "130923", - "name": "东光县" - }, - { - "code": "130924", - "name": "海兴县" - }, - { - "code": "130925", - "name": "盐山县" - }, - { - "code": "130926", - "name": "肃宁县" - }, - { - "code": "130927", - "name": "南皮县" - }, - { - "code": "130928", - "name": "吴桥县" - }, - { - "code": "130929", - "name": "献县" - }, - { - "code": "130930", - "name": "孟村回族自治县" - }, - { - "code": "130971", - "name": "河北沧州经济开发区" - }, - { - "code": "130972", - "name": "沧州高新技术产业开发区" - }, - { - "code": "130973", - "name": "沧州渤海新区" - }, - { - "code": "130981", - "name": "泊头市" - }, - { - "code": "130982", - "name": "任丘市" - }, - { - "code": "130983", - "name": "黄骅市" - }, - { - "code": "130984", - "name": "河间市" - } - ] - }, - { - "code": "1310", - "name": "廊坊市", - "children": [ - { - "code": "131002", - "name": "安次区" - }, - { - "code": "131003", - "name": "广阳区" - }, - { - "code": "131022", - "name": "固安县" - }, - { - "code": "131023", - "name": "永清县" - }, - { - "code": "131024", - "name": "香河县" - }, - { - "code": "131025", - "name": "大城县" - }, - { - "code": "131026", - "name": "文安县" - }, - { - "code": "131028", - "name": "大厂回族自治县" - }, - { - "code": "131071", - "name": "廊坊经济技术开发区" - }, - { - "code": "131081", - "name": "霸州市" - }, - { - "code": "131082", - "name": "三河市" - } - ] - }, - { - "code": "1311", - "name": "衡水市", - "children": [ - { - "code": "131102", - "name": "桃城区" - }, - { - "code": "131103", - "name": "冀州区" - }, - { - "code": "131121", - "name": "枣强县" - }, - { - "code": "131122", - "name": "武邑县" - }, - { - "code": "131123", - "name": "武强县" - }, - { - "code": "131124", - "name": "饶阳县" - }, - { - "code": "131125", - "name": "安平县" - }, - { - "code": "131126", - "name": "故城县" - }, - { - "code": "131127", - "name": "景县" - }, - { - "code": "131128", - "name": "阜城县" - }, - { - "code": "131171", - "name": "河北衡水高新技术产业开发区" - }, - { - "code": "131172", - "name": "衡水滨湖新区" - }, - { - "code": "131182", - "name": "深州市" - } - ] - } - ] - }, - { - "code": "14", - "name": "山西省", - "children": [ - { - "code": "1401", - "name": "太原市", - "children": [ - { - "code": "140105", - "name": "小店区" - }, - { - "code": "140106", - "name": "迎泽区" - }, - { - "code": "140107", - "name": "杏花岭区" - }, - { - "code": "140108", - "name": "尖草坪区" - }, - { - "code": "140109", - "name": "万柏林区" - }, - { - "code": "140110", - "name": "晋源区" - }, - { - "code": "140121", - "name": "清徐县" - }, - { - "code": "140122", - "name": "阳曲县" - }, - { - "code": "140123", - "name": "娄烦县" - }, - { - "code": "140171", - "name": "山西转型综合改革示范区" - }, - { - "code": "140181", - "name": "古交市" - } - ] - }, - { - "code": "1402", - "name": "大同市", - "children": [ - { - "code": "140212", - "name": "新荣区" - }, - { - "code": "140213", - "name": "平城区" - }, - { - "code": "140214", - "name": "云冈区" - }, - { - "code": "140215", - "name": "云州区" - }, - { - "code": "140221", - "name": "阳高县" - }, - { - "code": "140222", - "name": "天镇县" - }, - { - "code": "140223", - "name": "广灵县" - }, - { - "code": "140224", - "name": "灵丘县" - }, - { - "code": "140225", - "name": "浑源县" - }, - { - "code": "140226", - "name": "左云县" - }, - { - "code": "140271", - "name": "山西大同经济开发区" - } - ] - }, - { - "code": "1403", - "name": "阳泉市", - "children": [ - { - "code": "140302", - "name": "城区" - }, - { - "code": "140303", - "name": "矿区" - }, - { - "code": "140311", - "name": "郊区" - }, - { - "code": "140321", - "name": "平定县" - }, - { - "code": "140322", - "name": "盂县" - } - ] - }, - { - "code": "1404", - "name": "长治市", - "children": [ - { - "code": "140403", - "name": "潞州区" - }, - { - "code": "140404", - "name": "上党区" - }, - { - "code": "140405", - "name": "屯留区" - }, - { - "code": "140406", - "name": "潞城区" - }, - { - "code": "140423", - "name": "襄垣县" - }, - { - "code": "140425", - "name": "平顺县" - }, - { - "code": "140426", - "name": "黎城县" - }, - { - "code": "140427", - "name": "壶关县" - }, - { - "code": "140428", - "name": "长子县" - }, - { - "code": "140429", - "name": "武乡县" - }, - { - "code": "140430", - "name": "沁县" - }, - { - "code": "140431", - "name": "沁源县" - } - ] - }, - { - "code": "1405", - "name": "晋城市", - "children": [ - { - "code": "140502", - "name": "城区" - }, - { - "code": "140521", - "name": "沁水县" - }, - { - "code": "140522", - "name": "阳城县" - }, - { - "code": "140524", - "name": "陵川县" - }, - { - "code": "140525", - "name": "泽州县" - }, - { - "code": "140581", - "name": "高平市" - } - ] - }, - { - "code": "1406", - "name": "朔州市", - "children": [ - { - "code": "140602", - "name": "朔城区" - }, - { - "code": "140603", - "name": "平鲁区" - }, - { - "code": "140621", - "name": "山阴县" - }, - { - "code": "140622", - "name": "应县" - }, - { - "code": "140623", - "name": "右玉县" - }, - { - "code": "140671", - "name": "山西朔州经济开发区" - }, - { - "code": "140681", - "name": "怀仁市" - } - ] - }, - { - "code": "1407", - "name": "晋中市", - "children": [ - { - "code": "140702", - "name": "榆次区" - }, - { - "code": "140703", - "name": "太谷区" - }, - { - "code": "140721", - "name": "榆社县" - }, - { - "code": "140722", - "name": "左权县" - }, - { - "code": "140723", - "name": "和顺县" - }, - { - "code": "140724", - "name": "昔阳县" - }, - { - "code": "140725", - "name": "寿阳县" - }, - { - "code": "140727", - "name": "祁县" - }, - { - "code": "140728", - "name": "平遥县" - }, - { - "code": "140729", - "name": "灵石县" - }, - { - "code": "140781", - "name": "介休市" - } - ] - }, - { - "code": "1408", - "name": "运城市", - "children": [ - { - "code": "140802", - "name": "盐湖区" - }, - { - "code": "140821", - "name": "临猗县" - }, - { - "code": "140822", - "name": "万荣县" - }, - { - "code": "140823", - "name": "闻喜县" - }, - { - "code": "140824", - "name": "稷山县" - }, - { - "code": "140825", - "name": "新绛县" - }, - { - "code": "140826", - "name": "绛县" - }, - { - "code": "140827", - "name": "垣曲县" - }, - { - "code": "140828", - "name": "夏县" - }, - { - "code": "140829", - "name": "平陆县" - }, - { - "code": "140830", - "name": "芮城县" - }, - { - "code": "140881", - "name": "永济市" - }, - { - "code": "140882", - "name": "河津市" - } - ] - }, - { - "code": "1409", - "name": "忻州市", - "children": [ - { - "code": "140902", - "name": "忻府区" - }, - { - "code": "140921", - "name": "定襄县" - }, - { - "code": "140922", - "name": "五台县" - }, - { - "code": "140923", - "name": "代县" - }, - { - "code": "140924", - "name": "繁峙县" - }, - { - "code": "140925", - "name": "宁武县" - }, - { - "code": "140926", - "name": "静乐县" - }, - { - "code": "140927", - "name": "神池县" - }, - { - "code": "140928", - "name": "五寨县" - }, - { - "code": "140929", - "name": "岢岚县" - }, - { - "code": "140930", - "name": "河曲县" - }, - { - "code": "140931", - "name": "保德县" - }, - { - "code": "140932", - "name": "偏关县" - }, - { - "code": "140971", - "name": "五台山风景名胜区" - }, - { - "code": "140981", - "name": "原平市" - } - ] - }, - { - "code": "1410", - "name": "临汾市", - "children": [ - { - "code": "141002", - "name": "尧都区" - }, - { - "code": "141021", - "name": "曲沃县" - }, - { - "code": "141022", - "name": "翼城县" - }, - { - "code": "141023", - "name": "襄汾县" - }, - { - "code": "141024", - "name": "洪洞县" - }, - { - "code": "141025", - "name": "古县" - }, - { - "code": "141026", - "name": "安泽县" - }, - { - "code": "141027", - "name": "浮山县" - }, - { - "code": "141028", - "name": "吉县" - }, - { - "code": "141029", - "name": "乡宁县" - }, - { - "code": "141030", - "name": "大宁县" - }, - { - "code": "141031", - "name": "隰县" - }, - { - "code": "141032", - "name": "永和县" - }, - { - "code": "141033", - "name": "蒲县" - }, - { - "code": "141034", - "name": "汾西县" - }, - { - "code": "141081", - "name": "侯马市" - }, - { - "code": "141082", - "name": "霍州市" - } - ] - }, - { - "code": "1411", - "name": "吕梁市", - "children": [ - { - "code": "141102", - "name": "离石区" - }, - { - "code": "141121", - "name": "文水县" - }, - { - "code": "141122", - "name": "交城县" - }, - { - "code": "141123", - "name": "兴县" - }, - { - "code": "141124", - "name": "临县" - }, - { - "code": "141125", - "name": "柳林县" - }, - { - "code": "141126", - "name": "石楼县" - }, - { - "code": "141127", - "name": "岚县" - }, - { - "code": "141128", - "name": "方山县" - }, - { - "code": "141129", - "name": "中阳县" - }, - { - "code": "141130", - "name": "交口县" - }, - { - "code": "141181", - "name": "孝义市" - }, - { - "code": "141182", - "name": "汾阳市" - } - ] - } - ] - }, - { - "code": "15", - "name": "内蒙古自治区", - "children": [ - { - "code": "1501", - "name": "呼和浩特市", - "children": [ - { - "code": "150102", - "name": "新城区" - }, - { - "code": "150103", - "name": "回民区" - }, - { - "code": "150104", - "name": "玉泉区" - }, - { - "code": "150105", - "name": "赛罕区" - }, - { - "code": "150121", - "name": "土默特左旗" - }, - { - "code": "150122", - "name": "托克托县" - }, - { - "code": "150123", - "name": "和林格尔县" - }, - { - "code": "150124", - "name": "清水河县" - }, - { - "code": "150125", - "name": "武川县" - }, - { - "code": "150172", - "name": "呼和浩特经济技术开发区" - } - ] - }, - { - "code": "1502", - "name": "包头市", - "children": [ - { - "code": "150202", - "name": "东河区" - }, - { - "code": "150203", - "name": "昆都仑区" - }, - { - "code": "150204", - "name": "青山区" - }, - { - "code": "150205", - "name": "石拐区" - }, - { - "code": "150206", - "name": "白云鄂博矿区" - }, - { - "code": "150207", - "name": "九原区" - }, - { - "code": "150221", - "name": "土默特右旗" - }, - { - "code": "150222", - "name": "固阳县" - }, - { - "code": "150223", - "name": "达尔罕茂明安联合旗" - }, - { - "code": "150271", - "name": "包头稀土高新技术产业开发区" - } - ] - }, - { - "code": "1503", - "name": "乌海市", - "children": [ - { - "code": "150302", - "name": "海勃湾区" - }, - { - "code": "150303", - "name": "海南区" - }, - { - "code": "150304", - "name": "乌达区" - } - ] - }, - { - "code": "1504", - "name": "赤峰市", - "children": [ - { - "code": "150402", - "name": "红山区" - }, - { - "code": "150403", - "name": "元宝山区" - }, - { - "code": "150404", - "name": "松山区" - }, - { - "code": "150421", - "name": "阿鲁科尔沁旗" - }, - { - "code": "150422", - "name": "巴林左旗" - }, - { - "code": "150423", - "name": "巴林右旗" - }, - { - "code": "150424", - "name": "林西县" - }, - { - "code": "150425", - "name": "克什克腾旗" - }, - { - "code": "150426", - "name": "翁牛特旗" - }, - { - "code": "150428", - "name": "喀喇沁旗" - }, - { - "code": "150429", - "name": "宁城县" - }, - { - "code": "150430", - "name": "敖汉旗" - } - ] - }, - { - "code": "1505", - "name": "通辽市", - "children": [ - { - "code": "150502", - "name": "科尔沁区" - }, - { - "code": "150521", - "name": "科尔沁左翼中旗" - }, - { - "code": "150522", - "name": "科尔沁左翼后旗" - }, - { - "code": "150523", - "name": "开鲁县" - }, - { - "code": "150524", - "name": "库伦旗" - }, - { - "code": "150525", - "name": "奈曼旗" - }, - { - "code": "150526", - "name": "扎鲁特旗" - }, - { - "code": "150571", - "name": "通辽经济技术开发区" - }, - { - "code": "150581", - "name": "霍林郭勒市" - } - ] - }, - { - "code": "1506", - "name": "鄂尔多斯市", - "children": [ - { - "code": "150602", - "name": "东胜区" - }, - { - "code": "150603", - "name": "康巴什区" - }, - { - "code": "150621", - "name": "达拉特旗" - }, - { - "code": "150622", - "name": "准格尔旗" - }, - { - "code": "150623", - "name": "鄂托克前旗" - }, - { - "code": "150624", - "name": "鄂托克旗" - }, - { - "code": "150625", - "name": "杭锦旗" - }, - { - "code": "150626", - "name": "乌审旗" - }, - { - "code": "150627", - "name": "伊金霍洛旗" - } - ] - }, - { - "code": "1507", - "name": "呼伦贝尔市", - "children": [ - { - "code": "150702", - "name": "海拉尔区" - }, - { - "code": "150703", - "name": "扎赉诺尔区" - }, - { - "code": "150721", - "name": "阿荣旗" - }, - { - "code": "150722", - "name": "莫力达瓦达斡尔族自治旗" - }, - { - "code": "150723", - "name": "鄂伦春自治旗" - }, - { - "code": "150724", - "name": "鄂温克族自治旗" - }, - { - "code": "150725", - "name": "陈巴尔虎旗" - }, - { - "code": "150726", - "name": "新巴尔虎左旗" - }, - { - "code": "150727", - "name": "新巴尔虎右旗" - }, - { - "code": "150781", - "name": "满洲里市" - }, - { - "code": "150782", - "name": "牙克石市" - }, - { - "code": "150783", - "name": "扎兰屯市" - }, - { - "code": "150784", - "name": "额尔古纳市" - }, - { - "code": "150785", - "name": "根河市" - } - ] - }, - { - "code": "1508", - "name": "巴彦淖尔市", - "children": [ - { - "code": "150802", - "name": "临河区" - }, - { - "code": "150821", - "name": "五原县" - }, - { - "code": "150822", - "name": "磴口县" - }, - { - "code": "150823", - "name": "乌拉特前旗" - }, - { - "code": "150824", - "name": "乌拉特中旗" - }, - { - "code": "150825", - "name": "乌拉特后旗" - }, - { - "code": "150826", - "name": "杭锦后旗" - } - ] - }, - { - "code": "1509", - "name": "乌兰察布市", - "children": [ - { - "code": "150902", - "name": "集宁区" - }, - { - "code": "150921", - "name": "卓资县" - }, - { - "code": "150922", - "name": "化德县" - }, - { - "code": "150923", - "name": "商都县" - }, - { - "code": "150924", - "name": "兴和县" - }, - { - "code": "150925", - "name": "凉城县" - }, - { - "code": "150926", - "name": "察哈尔右翼前旗" - }, - { - "code": "150927", - "name": "察哈尔右翼中旗" - }, - { - "code": "150928", - "name": "察哈尔右翼后旗" - }, - { - "code": "150929", - "name": "四子王旗" - }, - { - "code": "150981", - "name": "丰镇市" - } - ] - }, - { - "code": "1522", - "name": "兴安盟", - "children": [ - { - "code": "152201", - "name": "乌兰浩特市" - }, - { - "code": "152202", - "name": "阿尔山市" - }, - { - "code": "152221", - "name": "科尔沁右翼前旗" - }, - { - "code": "152222", - "name": "科尔沁右翼中旗" - }, - { - "code": "152223", - "name": "扎赉特旗" - }, - { - "code": "152224", - "name": "突泉县" - } - ] - }, - { - "code": "1525", - "name": "锡林郭勒盟", - "children": [ - { - "code": "152501", - "name": "二连浩特市" - }, - { - "code": "152502", - "name": "锡林浩特市" - }, - { - "code": "152522", - "name": "阿巴嘎旗" - }, - { - "code": "152523", - "name": "苏尼特左旗" - }, - { - "code": "152524", - "name": "苏尼特右旗" - }, - { - "code": "152525", - "name": "东乌珠穆沁旗" - }, - { - "code": "152526", - "name": "西乌珠穆沁旗" - }, - { - "code": "152527", - "name": "太仆寺旗" - }, - { - "code": "152528", - "name": "镶黄旗" - }, - { - "code": "152529", - "name": "正镶白旗" - }, - { - "code": "152530", - "name": "正蓝旗" - }, - { - "code": "152531", - "name": "多伦县" - }, - { - "code": "152571", - "name": "乌拉盖管理区管委会" - } - ] - }, - { - "code": "1529", - "name": "阿拉善盟", - "children": [ - { - "code": "152921", - "name": "阿拉善左旗" - }, - { - "code": "152922", - "name": "阿拉善右旗" - }, - { - "code": "152923", - "name": "额济纳旗" - }, - { - "code": "152971", - "name": "内蒙古阿拉善高新技术产业开发区" - } - ] - } - ] - }, - { - "code": "21", - "name": "辽宁省", - "children": [ - { - "code": "2101", - "name": "沈阳市", - "children": [ - { - "code": "210102", - "name": "和平区" - }, - { - "code": "210103", - "name": "沈河区" - }, - { - "code": "210104", - "name": "大东区" - }, - { - "code": "210105", - "name": "皇姑区" - }, - { - "code": "210106", - "name": "铁西区" - }, - { - "code": "210111", - "name": "苏家屯区" - }, - { - "code": "210112", - "name": "浑南区" - }, - { - "code": "210113", - "name": "沈北新区" - }, - { - "code": "210114", - "name": "于洪区" - }, - { - "code": "210115", - "name": "辽中区" - }, - { - "code": "210123", - "name": "康平县" - }, - { - "code": "210124", - "name": "法库县" - }, - { - "code": "210181", - "name": "新民市" - } - ] - }, - { - "code": "2102", - "name": "大连市", - "children": [ - { - "code": "210202", - "name": "中山区" - }, - { - "code": "210203", - "name": "西岗区" - }, - { - "code": "210204", - "name": "沙河口区" - }, - { - "code": "210211", - "name": "甘井子区" - }, - { - "code": "210212", - "name": "旅顺口区" - }, - { - "code": "210213", - "name": "金州区" - }, - { - "code": "210214", - "name": "普兰店区" - }, - { - "code": "210224", - "name": "长海县" - }, - { - "code": "210281", - "name": "瓦房店市" - }, - { - "code": "210283", - "name": "庄河市" - } - ] - }, - { - "code": "2103", - "name": "鞍山市", - "children": [ - { - "code": "210302", - "name": "铁东区" - }, - { - "code": "210303", - "name": "铁西区" - }, - { - "code": "210304", - "name": "立山区" - }, - { - "code": "210311", - "name": "千山区" - }, - { - "code": "210321", - "name": "台安县" - }, - { - "code": "210323", - "name": "岫岩满族自治县" - }, - { - "code": "210381", - "name": "海城市" - } - ] - }, - { - "code": "2104", - "name": "抚顺市", - "children": [ - { - "code": "210402", - "name": "新抚区" - }, - { - "code": "210403", - "name": "东洲区" - }, - { - "code": "210404", - "name": "望花区" - }, - { - "code": "210411", - "name": "顺城区" - }, - { - "code": "210421", - "name": "抚顺县" - }, - { - "code": "210422", - "name": "新宾满族自治县" - }, - { - "code": "210423", - "name": "清原满族自治县" - } - ] - }, - { - "code": "2105", - "name": "本溪市", - "children": [ - { - "code": "210502", - "name": "平山区" - }, - { - "code": "210503", - "name": "溪湖区" - }, - { - "code": "210504", - "name": "明山区" - }, - { - "code": "210505", - "name": "南芬区" - }, - { - "code": "210521", - "name": "本溪满族自治县" - }, - { - "code": "210522", - "name": "桓仁满族自治县" - } - ] - }, - { - "code": "2106", - "name": "丹东市", - "children": [ - { - "code": "210602", - "name": "元宝区" - }, - { - "code": "210603", - "name": "振兴区" - }, - { - "code": "210604", - "name": "振安区" - }, - { - "code": "210624", - "name": "宽甸满族自治县" - }, - { - "code": "210681", - "name": "东港市" - }, - { - "code": "210682", - "name": "凤城市" - } - ] - }, - { - "code": "2107", - "name": "锦州市", - "children": [ - { - "code": "210702", - "name": "古塔区" - }, - { - "code": "210703", - "name": "凌河区" - }, - { - "code": "210711", - "name": "太和区" - }, - { - "code": "210726", - "name": "黑山县" - }, - { - "code": "210727", - "name": "义县" - }, - { - "code": "210781", - "name": "凌海市" - }, - { - "code": "210782", - "name": "北镇市" - } - ] - }, - { - "code": "2108", - "name": "营口市", - "children": [ - { - "code": "210802", - "name": "站前区" - }, - { - "code": "210803", - "name": "西市区" - }, - { - "code": "210804", - "name": "鲅鱼圈区" - }, - { - "code": "210811", - "name": "老边区" - }, - { - "code": "210881", - "name": "盖州市" - }, - { - "code": "210882", - "name": "大石桥市" - } - ] - }, - { - "code": "2109", - "name": "阜新市", - "children": [ - { - "code": "210902", - "name": "海州区" - }, - { - "code": "210903", - "name": "新邱区" - }, - { - "code": "210904", - "name": "太平区" - }, - { - "code": "210905", - "name": "清河门区" - }, - { - "code": "210911", - "name": "细河区" - }, - { - "code": "210921", - "name": "阜新蒙古族自治县" - }, - { - "code": "210922", - "name": "彰武县" - } - ] - }, - { - "code": "2110", - "name": "辽阳市", - "children": [ - { - "code": "211002", - "name": "白塔区" - }, - { - "code": "211003", - "name": "文圣区" - }, - { - "code": "211004", - "name": "宏伟区" - }, - { - "code": "211005", - "name": "弓长岭区" - }, - { - "code": "211011", - "name": "太子河区" - }, - { - "code": "211021", - "name": "辽阳县" - }, - { - "code": "211081", - "name": "灯塔市" - } - ] - }, - { - "code": "2111", - "name": "盘锦市", - "children": [ - { - "code": "211102", - "name": "双台子区" - }, - { - "code": "211103", - "name": "兴隆台区" - }, - { - "code": "211104", - "name": "大洼区" - }, - { - "code": "211122", - "name": "盘山县" - } - ] - }, - { - "code": "2112", - "name": "铁岭市", - "children": [ - { - "code": "211202", - "name": "银州区" - }, - { - "code": "211204", - "name": "清河区" - }, - { - "code": "211221", - "name": "铁岭县" - }, - { - "code": "211223", - "name": "西丰县" - }, - { - "code": "211224", - "name": "昌图县" - }, - { - "code": "211281", - "name": "调兵山市" - }, - { - "code": "211282", - "name": "开原市" - } - ] - }, - { - "code": "2113", - "name": "朝阳市", - "children": [ - { - "code": "211302", - "name": "双塔区" - }, - { - "code": "211303", - "name": "龙城区" - }, - { - "code": "211321", - "name": "朝阳县" - }, - { - "code": "211322", - "name": "建平县" - }, - { - "code": "211324", - "name": "喀喇沁左翼蒙古族自治县" - }, - { - "code": "211381", - "name": "北票市" - }, - { - "code": "211382", - "name": "凌源市" - } - ] - }, - { - "code": "2114", - "name": "葫芦岛市", - "children": [ - { - "code": "211402", - "name": "连山区" - }, - { - "code": "211403", - "name": "龙港区" - }, - { - "code": "211404", - "name": "南票区" - }, - { - "code": "211421", - "name": "绥中县" - }, - { - "code": "211422", - "name": "建昌县" - }, - { - "code": "211481", - "name": "兴城市" - } - ] - } - ] - }, - { - "code": "22", - "name": "吉林省", - "children": [ - { - "code": "2201", - "name": "长春市", - "children": [ - { - "code": "220102", - "name": "南关区" - }, - { - "code": "220103", - "name": "宽城区" - }, - { - "code": "220104", - "name": "朝阳区" - }, - { - "code": "220105", - "name": "二道区" - }, - { - "code": "220106", - "name": "绿园区" - }, - { - "code": "220112", - "name": "双阳区" - }, - { - "code": "220113", - "name": "九台区" - }, - { - "code": "220122", - "name": "农安县" - }, - { - "code": "220171", - "name": "长春经济技术开发区" - }, - { - "code": "220172", - "name": "长春净月高新技术产业开发区" - }, - { - "code": "220173", - "name": "长春高新技术产业开发区" - }, - { - "code": "220174", - "name": "长春汽车经济技术开发区" - }, - { - "code": "220182", - "name": "榆树市" - }, - { - "code": "220183", - "name": "德惠市" - }, - { - "code": "220184", - "name": "公主岭市" - } - ] - }, - { - "code": "2202", - "name": "吉林市", - "children": [ - { - "code": "220202", - "name": "昌邑区" - }, - { - "code": "220203", - "name": "龙潭区" - }, - { - "code": "220204", - "name": "船营区" - }, - { - "code": "220211", - "name": "丰满区" - }, - { - "code": "220221", - "name": "永吉县" - }, - { - "code": "220271", - "name": "吉林经济开发区" - }, - { - "code": "220272", - "name": "吉林高新技术产业开发区" - }, - { - "code": "220273", - "name": "吉林中国新加坡食品区" - }, - { - "code": "220281", - "name": "蛟河市" - }, - { - "code": "220282", - "name": "桦甸市" - }, - { - "code": "220283", - "name": "舒兰市" - }, - { - "code": "220284", - "name": "磐石市" - } - ] - }, - { - "code": "2203", - "name": "四平市", - "children": [ - { - "code": "220302", - "name": "铁西区" - }, - { - "code": "220303", - "name": "铁东区" - }, - { - "code": "220322", - "name": "梨树县" - }, - { - "code": "220323", - "name": "伊通满族自治县" - }, - { - "code": "220382", - "name": "双辽市" - } - ] - }, - { - "code": "2204", - "name": "辽源市", - "children": [ - { - "code": "220402", - "name": "龙山区" - }, - { - "code": "220403", - "name": "西安区" - }, - { - "code": "220421", - "name": "东丰县" - }, - { - "code": "220422", - "name": "东辽县" - } - ] - }, - { - "code": "2205", - "name": "通化市", - "children": [ - { - "code": "220502", - "name": "东昌区" - }, - { - "code": "220503", - "name": "二道江区" - }, - { - "code": "220521", - "name": "通化县" - }, - { - "code": "220523", - "name": "辉南县" - }, - { - "code": "220524", - "name": "柳河县" - }, - { - "code": "220581", - "name": "梅河口市" - }, - { - "code": "220582", - "name": "集安市" - } - ] - }, - { - "code": "2206", - "name": "白山市", - "children": [ - { - "code": "220602", - "name": "浑江区" - }, - { - "code": "220605", - "name": "江源区" - }, - { - "code": "220621", - "name": "抚松县" - }, - { - "code": "220622", - "name": "靖宇县" - }, - { - "code": "220623", - "name": "长白朝鲜族自治县" - }, - { - "code": "220681", - "name": "临江市" - } - ] - }, - { - "code": "2207", - "name": "松原市", - "children": [ - { - "code": "220702", - "name": "宁江区" - }, - { - "code": "220721", - "name": "前郭尔罗斯蒙古族自治县" - }, - { - "code": "220722", - "name": "长岭县" - }, - { - "code": "220723", - "name": "乾安县" - }, - { - "code": "220771", - "name": "吉林松原经济开发区" - }, - { - "code": "220781", - "name": "扶余市" - } - ] - }, - { - "code": "2208", - "name": "白城市", - "children": [ - { - "code": "220802", - "name": "洮北区" - }, - { - "code": "220821", - "name": "镇赉县" - }, - { - "code": "220822", - "name": "通榆县" - }, - { - "code": "220871", - "name": "吉林白城经济开发区" - }, - { - "code": "220881", - "name": "洮南市" - }, - { - "code": "220882", - "name": "大安市" - } - ] - }, - { - "code": "2224", - "name": "延边朝鲜族自治州", - "children": [ - { - "code": "222401", - "name": "延吉市" - }, - { - "code": "222402", - "name": "图们市" - }, - { - "code": "222403", - "name": "敦化市" - }, - { - "code": "222404", - "name": "珲春市" - }, - { - "code": "222405", - "name": "龙井市" - }, - { - "code": "222406", - "name": "和龙市" - }, - { - "code": "222424", - "name": "汪清县" - }, - { - "code": "222426", - "name": "安图县" - } - ] - } - ] - }, - { - "code": "23", - "name": "黑龙江省", - "children": [ - { - "code": "2301", - "name": "哈尔滨市", - "children": [ - { - "code": "230102", - "name": "道里区" - }, - { - "code": "230103", - "name": "南岗区" - }, - { - "code": "230104", - "name": "道外区" - }, - { - "code": "230108", - "name": "平房区" - }, - { - "code": "230109", - "name": "松北区" - }, - { - "code": "230110", - "name": "香坊区" - }, - { - "code": "230111", - "name": "呼兰区" - }, - { - "code": "230112", - "name": "阿城区" - }, - { - "code": "230113", - "name": "双城区" - }, - { - "code": "230123", - "name": "依兰县" - }, - { - "code": "230124", - "name": "方正县" - }, - { - "code": "230125", - "name": "宾县" - }, - { - "code": "230126", - "name": "巴彦县" - }, - { - "code": "230127", - "name": "木兰县" - }, - { - "code": "230128", - "name": "通河县" - }, - { - "code": "230129", - "name": "延寿县" - }, - { - "code": "230183", - "name": "尚志市" - }, - { - "code": "230184", - "name": "五常市" - } - ] - }, - { - "code": "2302", - "name": "齐齐哈尔市", - "children": [ - { - "code": "230202", - "name": "龙沙区" - }, - { - "code": "230203", - "name": "建华区" - }, - { - "code": "230204", - "name": "铁锋区" - }, - { - "code": "230205", - "name": "昂昂溪区" - }, - { - "code": "230206", - "name": "富拉尔基区" - }, - { - "code": "230207", - "name": "碾子山区" - }, - { - "code": "230208", - "name": "梅里斯达斡尔族区" - }, - { - "code": "230221", - "name": "龙江县" - }, - { - "code": "230223", - "name": "依安县" - }, - { - "code": "230224", - "name": "泰来县" - }, - { - "code": "230225", - "name": "甘南县" - }, - { - "code": "230227", - "name": "富裕县" - }, - { - "code": "230229", - "name": "克山县" - }, - { - "code": "230230", - "name": "克东县" - }, - { - "code": "230231", - "name": "拜泉县" - }, - { - "code": "230281", - "name": "讷河市" - } - ] - }, - { - "code": "2303", - "name": "鸡西市", - "children": [ - { - "code": "230302", - "name": "鸡冠区" - }, - { - "code": "230303", - "name": "恒山区" - }, - { - "code": "230304", - "name": "滴道区" - }, - { - "code": "230305", - "name": "梨树区" - }, - { - "code": "230306", - "name": "城子河区" - }, - { - "code": "230307", - "name": "麻山区" - }, - { - "code": "230321", - "name": "鸡东县" - }, - { - "code": "230381", - "name": "虎林市" - }, - { - "code": "230382", - "name": "密山市" - } - ] - }, - { - "code": "2304", - "name": "鹤岗市", - "children": [ - { - "code": "230402", - "name": "向阳区" - }, - { - "code": "230403", - "name": "工农区" - }, - { - "code": "230404", - "name": "南山区" - }, - { - "code": "230405", - "name": "兴安区" - }, - { - "code": "230406", - "name": "东山区" - }, - { - "code": "230407", - "name": "兴山区" - }, - { - "code": "230421", - "name": "萝北县" - }, - { - "code": "230422", - "name": "绥滨县" - } - ] - }, - { - "code": "2305", - "name": "双鸭山市", - "children": [ - { - "code": "230502", - "name": "尖山区" - }, - { - "code": "230503", - "name": "岭东区" - }, - { - "code": "230505", - "name": "四方台区" - }, - { - "code": "230506", - "name": "宝山区" - }, - { - "code": "230521", - "name": "集贤县" - }, - { - "code": "230522", - "name": "友谊县" - }, - { - "code": "230523", - "name": "宝清县" - }, - { - "code": "230524", - "name": "饶河县" - } - ] - }, - { - "code": "2306", - "name": "大庆市", - "children": [ - { - "code": "230602", - "name": "萨尔图区" - }, - { - "code": "230603", - "name": "龙凤区" - }, - { - "code": "230604", - "name": "让胡路区" - }, - { - "code": "230605", - "name": "红岗区" - }, - { - "code": "230606", - "name": "大同区" - }, - { - "code": "230621", - "name": "肇州县" - }, - { - "code": "230622", - "name": "肇源县" - }, - { - "code": "230623", - "name": "林甸县" - }, - { - "code": "230624", - "name": "杜尔伯特蒙古族自治县" - }, - { - "code": "230671", - "name": "大庆高新技术产业开发区" - } - ] - }, - { - "code": "2307", - "name": "伊春市", - "children": [ - { - "code": "230717", - "name": "伊美区" - }, - { - "code": "230718", - "name": "乌翠区" - }, - { - "code": "230719", - "name": "友好区" - }, - { - "code": "230722", - "name": "嘉荫县" - }, - { - "code": "230723", - "name": "汤旺县" - }, - { - "code": "230724", - "name": "丰林县" - }, - { - "code": "230725", - "name": "大箐山县" - }, - { - "code": "230726", - "name": "南岔县" - }, - { - "code": "230751", - "name": "金林区" - }, - { - "code": "230781", - "name": "铁力市" - } - ] - }, - { - "code": "2308", - "name": "佳木斯市", - "children": [ - { - "code": "230803", - "name": "向阳区" - }, - { - "code": "230804", - "name": "前进区" - }, - { - "code": "230805", - "name": "东风区" - }, - { - "code": "230811", - "name": "郊区" - }, - { - "code": "230822", - "name": "桦南县" - }, - { - "code": "230826", - "name": "桦川县" - }, - { - "code": "230828", - "name": "汤原县" - }, - { - "code": "230881", - "name": "同江市" - }, - { - "code": "230882", - "name": "富锦市" - }, - { - "code": "230883", - "name": "抚远市" - } - ] - }, - { - "code": "2309", - "name": "七台河市", - "children": [ - { - "code": "230902", - "name": "新兴区" - }, - { - "code": "230903", - "name": "桃山区" - }, - { - "code": "230904", - "name": "茄子河区" - }, - { - "code": "230921", - "name": "勃利县" - } - ] - }, - { - "code": "2310", - "name": "牡丹江市", - "children": [ - { - "code": "231002", - "name": "东安区" - }, - { - "code": "231003", - "name": "阳明区" - }, - { - "code": "231004", - "name": "爱民区" - }, - { - "code": "231005", - "name": "西安区" - }, - { - "code": "231025", - "name": "林口县" - }, - { - "code": "231081", - "name": "绥芬河市" - }, - { - "code": "231083", - "name": "海林市" - }, - { - "code": "231084", - "name": "宁安市" - }, - { - "code": "231085", - "name": "穆棱市" - }, - { - "code": "231086", - "name": "东宁市" - } - ] - }, - { - "code": "2311", - "name": "黑河市", - "children": [ - { - "code": "231102", - "name": "爱辉区" - }, - { - "code": "231123", - "name": "逊克县" - }, - { - "code": "231124", - "name": "孙吴县" - }, - { - "code": "231181", - "name": "北安市" - }, - { - "code": "231182", - "name": "五大连池市" - }, - { - "code": "231183", - "name": "嫩江市" - } - ] - }, - { - "code": "2312", - "name": "绥化市", - "children": [ - { - "code": "231202", - "name": "北林区" - }, - { - "code": "231221", - "name": "望奎县" - }, - { - "code": "231222", - "name": "兰西县" - }, - { - "code": "231223", - "name": "青冈县" - }, - { - "code": "231224", - "name": "庆安县" - }, - { - "code": "231225", - "name": "明水县" - }, - { - "code": "231226", - "name": "绥棱县" - }, - { - "code": "231281", - "name": "安达市" - }, - { - "code": "231282", - "name": "肇东市" - }, - { - "code": "231283", - "name": "海伦市" - } - ] - }, - { - "code": "2327", - "name": "大兴安岭地区", - "children": [ - { - "code": "232701", - "name": "漠河市" - }, - { - "code": "232721", - "name": "呼玛县" - }, - { - "code": "232722", - "name": "塔河县" - }, - { - "code": "232761", - "name": "加格达奇区" - }, - { - "code": "232762", - "name": "松岭区" - }, - { - "code": "232763", - "name": "新林区" - }, - { - "code": "232764", - "name": "呼中区" - } - ] - } - ] - }, - { - "code": "31", - "name": "上海市", - "children": [ - { - "code": "3101", - "name": "市辖区", - "children": [ - { - "code": "310101", - "name": "黄浦区" - }, - { - "code": "310104", - "name": "徐汇区" - }, - { - "code": "310105", - "name": "长宁区" - }, - { - "code": "310106", - "name": "静安区" - }, - { - "code": "310107", - "name": "普陀区" - }, - { - "code": "310109", - "name": "虹口区" - }, - { - "code": "310110", - "name": "杨浦区" - }, - { - "code": "310112", - "name": "闵行区" - }, - { - "code": "310113", - "name": "宝山区" - }, - { - "code": "310114", - "name": "嘉定区" - }, - { - "code": "310115", - "name": "浦东新区" - }, - { - "code": "310116", - "name": "金山区" - }, - { - "code": "310117", - "name": "松江区" - }, - { - "code": "310118", - "name": "青浦区" - }, - { - "code": "310120", - "name": "奉贤区" - }, - { - "code": "310151", - "name": "崇明区" - } - ] - } - ] - }, - { - "code": "32", - "name": "江苏省", - "children": [ - { - "code": "3201", - "name": "南京市", - "children": [ - { - "code": "320102", - "name": "玄武区" - }, - { - "code": "320104", - "name": "秦淮区" - }, - { - "code": "320105", - "name": "建邺区" - }, - { - "code": "320106", - "name": "鼓楼区" - }, - { - "code": "320111", - "name": "浦口区" - }, - { - "code": "320113", - "name": "栖霞区" - }, - { - "code": "320114", - "name": "雨花台区" - }, - { - "code": "320115", - "name": "江宁区" - }, - { - "code": "320116", - "name": "六合区" - }, - { - "code": "320117", - "name": "溧水区" - }, - { - "code": "320118", - "name": "高淳区" - } - ] - }, - { - "code": "3202", - "name": "无锡市", - "children": [ - { - "code": "320205", - "name": "锡山区" - }, - { - "code": "320206", - "name": "惠山区" - }, - { - "code": "320211", - "name": "滨湖区" - }, - { - "code": "320213", - "name": "梁溪区" - }, - { - "code": "320214", - "name": "新吴区" - }, - { - "code": "320281", - "name": "江阴市" - }, - { - "code": "320282", - "name": "宜兴市" - } - ] - }, - { - "code": "3203", - "name": "徐州市", - "children": [ - { - "code": "320302", - "name": "鼓楼区" - }, - { - "code": "320303", - "name": "云龙区" - }, - { - "code": "320305", - "name": "贾汪区" - }, - { - "code": "320311", - "name": "泉山区" - }, - { - "code": "320312", - "name": "铜山区" - }, - { - "code": "320321", - "name": "丰县" - }, - { - "code": "320322", - "name": "沛县" - }, - { - "code": "320324", - "name": "睢宁县" - }, - { - "code": "320371", - "name": "徐州经济技术开发区" - }, - { - "code": "320381", - "name": "新沂市" - }, - { - "code": "320382", - "name": "邳州市" - } - ] - }, - { - "code": "3204", - "name": "常州市", - "children": [ - { - "code": "320402", - "name": "天宁区" - }, - { - "code": "320404", - "name": "钟楼区" - }, - { - "code": "320411", - "name": "新北区" - }, - { - "code": "320412", - "name": "武进区" - }, - { - "code": "320413", - "name": "金坛区" - }, - { - "code": "320481", - "name": "溧阳市" - } - ] - }, - { - "code": "3205", - "name": "苏州市", - "children": [ - { - "code": "320505", - "name": "虎丘区" - }, - { - "code": "320506", - "name": "吴中区" - }, - { - "code": "320507", - "name": "相城区" - }, - { - "code": "320508", - "name": "姑苏区" - }, - { - "code": "320509", - "name": "吴江区" - }, - { - "code": "320576", - "name": "苏州工业园区" - }, - { - "code": "320581", - "name": "常熟市" - }, - { - "code": "320582", - "name": "张家港市" - }, - { - "code": "320583", - "name": "昆山市" - }, - { - "code": "320585", - "name": "太仓市" - } - ] - }, - { - "code": "3206", - "name": "南通市", - "children": [ - { - "code": "320612", - "name": "通州区" - }, - { - "code": "320613", - "name": "崇川区" - }, - { - "code": "320614", - "name": "海门区" - }, - { - "code": "320623", - "name": "如东县" - }, - { - "code": "320671", - "name": "南通经济技术开发区" - }, - { - "code": "320681", - "name": "启东市" - }, - { - "code": "320682", - "name": "如皋市" - }, - { - "code": "320685", - "name": "海安市" - } - ] - }, - { - "code": "3207", - "name": "连云港市", - "children": [ - { - "code": "320703", - "name": "连云区" - }, - { - "code": "320706", - "name": "海州区" - }, - { - "code": "320707", - "name": "赣榆区" - }, - { - "code": "320722", - "name": "东海县" - }, - { - "code": "320723", - "name": "灌云县" - }, - { - "code": "320724", - "name": "灌南县" - }, - { - "code": "320771", - "name": "连云港经济技术开发区" - } - ] - }, - { - "code": "3208", - "name": "淮安市", - "children": [ - { - "code": "320803", - "name": "淮安区" - }, - { - "code": "320804", - "name": "淮阴区" - }, - { - "code": "320812", - "name": "清江浦区" - }, - { - "code": "320813", - "name": "洪泽区" - }, - { - "code": "320826", - "name": "涟水县" - }, - { - "code": "320830", - "name": "盱眙县" - }, - { - "code": "320831", - "name": "金湖县" - }, - { - "code": "320871", - "name": "淮安经济技术开发区" - } - ] - }, - { - "code": "3209", - "name": "盐城市", - "children": [ - { - "code": "320902", - "name": "亭湖区" - }, - { - "code": "320903", - "name": "盐都区" - }, - { - "code": "320904", - "name": "大丰区" - }, - { - "code": "320921", - "name": "响水县" - }, - { - "code": "320922", - "name": "滨海县" - }, - { - "code": "320923", - "name": "阜宁县" - }, - { - "code": "320924", - "name": "射阳县" - }, - { - "code": "320925", - "name": "建湖县" - }, - { - "code": "320971", - "name": "盐城经济技术开发区" - }, - { - "code": "320981", - "name": "东台市" - } - ] - }, - { - "code": "3210", - "name": "扬州市", - "children": [ - { - "code": "321002", - "name": "广陵区" - }, - { - "code": "321003", - "name": "邗江区" - }, - { - "code": "321012", - "name": "江都区" - }, - { - "code": "321023", - "name": "宝应县" - }, - { - "code": "321071", - "name": "扬州经济技术开发区" - }, - { - "code": "321081", - "name": "仪征市" - }, - { - "code": "321084", - "name": "高邮市" - } - ] - }, - { - "code": "3211", - "name": "镇江市", - "children": [ - { - "code": "321102", - "name": "京口区" - }, - { - "code": "321111", - "name": "润州区" - }, - { - "code": "321112", - "name": "丹徒区" - }, - { - "code": "321171", - "name": "镇江新区" - }, - { - "code": "321181", - "name": "丹阳市" - }, - { - "code": "321182", - "name": "扬中市" - }, - { - "code": "321183", - "name": "句容市" - } - ] - }, - { - "code": "3212", - "name": "泰州市", - "children": [ - { - "code": "321202", - "name": "海陵区" - }, - { - "code": "321203", - "name": "高港区" - }, - { - "code": "321204", - "name": "姜堰区" - }, - { - "code": "321281", - "name": "兴化市" - }, - { - "code": "321282", - "name": "靖江市" - }, - { - "code": "321283", - "name": "泰兴市" - } - ] - }, - { - "code": "3213", - "name": "宿迁市", - "children": [ - { - "code": "321302", - "name": "宿城区" - }, - { - "code": "321311", - "name": "宿豫区" - }, - { - "code": "321322", - "name": "沭阳县" - }, - { - "code": "321323", - "name": "泗阳县" - }, - { - "code": "321324", - "name": "泗洪县" - }, - { - "code": "321371", - "name": "宿迁经济技术开发区" - } - ] - } - ] - }, - { - "code": "33", - "name": "浙江省", - "children": [ - { - "code": "3301", - "name": "杭州市", - "children": [ - { - "code": "330102", - "name": "上城区" - }, - { - "code": "330105", - "name": "拱墅区" - }, - { - "code": "330106", - "name": "西湖区" - }, - { - "code": "330108", - "name": "滨江区" - }, - { - "code": "330109", - "name": "萧山区" - }, - { - "code": "330110", - "name": "余杭区" - }, - { - "code": "330111", - "name": "富阳区" - }, - { - "code": "330112", - "name": "临安区" - }, - { - "code": "330113", - "name": "临平区" - }, - { - "code": "330114", - "name": "钱塘区" - }, - { - "code": "330122", - "name": "桐庐县" - }, - { - "code": "330127", - "name": "淳安县" - }, - { - "code": "330182", - "name": "建德市" - } - ] - }, - { - "code": "3302", - "name": "宁波市", - "children": [ - { - "code": "330203", - "name": "海曙区" - }, - { - "code": "330205", - "name": "江北区" - }, - { - "code": "330206", - "name": "北仑区" - }, - { - "code": "330211", - "name": "镇海区" - }, - { - "code": "330212", - "name": "鄞州区" - }, - { - "code": "330213", - "name": "奉化区" - }, - { - "code": "330225", - "name": "象山县" - }, - { - "code": "330226", - "name": "宁海县" - }, - { - "code": "330281", - "name": "余姚市" - }, - { - "code": "330282", - "name": "慈溪市" - } - ] - }, - { - "code": "3303", - "name": "温州市", - "children": [ - { - "code": "330302", - "name": "鹿城区" - }, - { - "code": "330303", - "name": "龙湾区" - }, - { - "code": "330304", - "name": "瓯海区" - }, - { - "code": "330305", - "name": "洞头区" - }, - { - "code": "330324", - "name": "永嘉县" - }, - { - "code": "330326", - "name": "平阳县" - }, - { - "code": "330327", - "name": "苍南县" - }, - { - "code": "330328", - "name": "文成县" - }, - { - "code": "330329", - "name": "泰顺县" - }, - { - "code": "330381", - "name": "瑞安市" - }, - { - "code": "330382", - "name": "乐清市" - }, - { - "code": "330383", - "name": "龙港市" - } - ] - }, - { - "code": "3304", - "name": "嘉兴市", - "children": [ - { - "code": "330402", - "name": "南湖区" - }, - { - "code": "330411", - "name": "秀洲区" - }, - { - "code": "330421", - "name": "嘉善县" - }, - { - "code": "330424", - "name": "海盐县" - }, - { - "code": "330481", - "name": "海宁市" - }, - { - "code": "330482", - "name": "平湖市" - }, - { - "code": "330483", - "name": "桐乡市" - } - ] - }, - { - "code": "3305", - "name": "湖州市", - "children": [ - { - "code": "330502", - "name": "吴兴区" - }, - { - "code": "330503", - "name": "南浔区" - }, - { - "code": "330521", - "name": "德清县" - }, - { - "code": "330522", - "name": "长兴县" - }, - { - "code": "330523", - "name": "安吉县" - } - ] - }, - { - "code": "3306", - "name": "绍兴市", - "children": [ - { - "code": "330602", - "name": "越城区" - }, - { - "code": "330603", - "name": "柯桥区" - }, - { - "code": "330604", - "name": "上虞区" - }, - { - "code": "330624", - "name": "新昌县" - }, - { - "code": "330681", - "name": "诸暨市" - }, - { - "code": "330683", - "name": "嵊州市" - } - ] - }, - { - "code": "3307", - "name": "金华市", - "children": [ - { - "code": "330702", - "name": "婺城区" - }, - { - "code": "330703", - "name": "金东区" - }, - { - "code": "330723", - "name": "武义县" - }, - { - "code": "330726", - "name": "浦江县" - }, - { - "code": "330727", - "name": "磐安县" - }, - { - "code": "330781", - "name": "兰溪市" - }, - { - "code": "330782", - "name": "义乌市" - }, - { - "code": "330783", - "name": "东阳市" - }, - { - "code": "330784", - "name": "永康市" - } - ] - }, - { - "code": "3308", - "name": "衢州市", - "children": [ - { - "code": "330802", - "name": "柯城区" - }, - { - "code": "330803", - "name": "衢江区" - }, - { - "code": "330822", - "name": "常山县" - }, - { - "code": "330824", - "name": "开化县" - }, - { - "code": "330825", - "name": "龙游县" - }, - { - "code": "330881", - "name": "江山市" - } - ] - }, - { - "code": "3309", - "name": "舟山市", - "children": [ - { - "code": "330902", - "name": "定海区" - }, - { - "code": "330903", - "name": "普陀区" - }, - { - "code": "330921", - "name": "岱山县" - }, - { - "code": "330922", - "name": "嵊泗县" - } - ] - }, - { - "code": "3310", - "name": "台州市", - "children": [ - { - "code": "331002", - "name": "椒江区" - }, - { - "code": "331003", - "name": "黄岩区" - }, - { - "code": "331004", - "name": "路桥区" - }, - { - "code": "331022", - "name": "三门县" - }, - { - "code": "331023", - "name": "天台县" - }, - { - "code": "331024", - "name": "仙居县" - }, - { - "code": "331081", - "name": "温岭市" - }, - { - "code": "331082", - "name": "临海市" - }, - { - "code": "331083", - "name": "玉环市" - } - ] - }, - { - "code": "3311", - "name": "丽水市", - "children": [ - { - "code": "331102", - "name": "莲都区" - }, - { - "code": "331121", - "name": "青田县" - }, - { - "code": "331122", - "name": "缙云县" - }, - { - "code": "331123", - "name": "遂昌县" - }, - { - "code": "331124", - "name": "松阳县" - }, - { - "code": "331125", - "name": "云和县" - }, - { - "code": "331126", - "name": "庆元县" - }, - { - "code": "331127", - "name": "景宁畲族自治县" - }, - { - "code": "331181", - "name": "龙泉市" - } - ] - } - ] - }, - { - "code": "34", - "name": "安徽省", - "children": [ - { - "code": "3401", - "name": "合肥市", - "children": [ - { - "code": "340102", - "name": "瑶海区" - }, - { - "code": "340103", - "name": "庐阳区" - }, - { - "code": "340104", - "name": "蜀山区" - }, - { - "code": "340111", - "name": "包河区" - }, - { - "code": "340121", - "name": "长丰县" - }, - { - "code": "340122", - "name": "肥东县" - }, - { - "code": "340123", - "name": "肥西县" - }, - { - "code": "340124", - "name": "庐江县" - }, - { - "code": "340176", - "name": "合肥高新技术产业开发区" - }, - { - "code": "340177", - "name": "合肥经济技术开发区" - }, - { - "code": "340178", - "name": "合肥新站高新技术产业开发区" - }, - { - "code": "340181", - "name": "巢湖市" - } - ] - }, - { - "code": "3402", - "name": "芜湖市", - "children": [ - { - "code": "340202", - "name": "镜湖区" - }, - { - "code": "340207", - "name": "鸠江区" - }, - { - "code": "340209", - "name": "弋江区" - }, - { - "code": "340210", - "name": "湾沚区" - }, - { - "code": "340212", - "name": "繁昌区" - }, - { - "code": "340223", - "name": "南陵县" - }, - { - "code": "340271", - "name": "芜湖经济技术开发区" - }, - { - "code": "340272", - "name": "安徽芜湖三山经济开发区" - }, - { - "code": "340281", - "name": "无为市" - } - ] - }, - { - "code": "3403", - "name": "蚌埠市", - "children": [ - { - "code": "340302", - "name": "龙子湖区" - }, - { - "code": "340303", - "name": "蚌山区" - }, - { - "code": "340304", - "name": "禹会区" - }, - { - "code": "340311", - "name": "淮上区" - }, - { - "code": "340321", - "name": "怀远县" - }, - { - "code": "340322", - "name": "五河县" - }, - { - "code": "340323", - "name": "固镇县" - }, - { - "code": "340371", - "name": "蚌埠市高新技术开发区" - }, - { - "code": "340372", - "name": "蚌埠市经济开发区" - } - ] - }, - { - "code": "3404", - "name": "淮南市", - "children": [ - { - "code": "340402", - "name": "大通区" - }, - { - "code": "340403", - "name": "田家庵区" - }, - { - "code": "340404", - "name": "谢家集区" - }, - { - "code": "340405", - "name": "八公山区" - }, - { - "code": "340406", - "name": "潘集区" - }, - { - "code": "340421", - "name": "凤台县" - }, - { - "code": "340422", - "name": "寿县" - } - ] - }, - { - "code": "3405", - "name": "马鞍山市", - "children": [ - { - "code": "340503", - "name": "花山区" - }, - { - "code": "340504", - "name": "雨山区" - }, - { - "code": "340506", - "name": "博望区" - }, - { - "code": "340521", - "name": "当涂县" - }, - { - "code": "340522", - "name": "含山县" - }, - { - "code": "340523", - "name": "和县" - } - ] - }, - { - "code": "3406", - "name": "淮北市", - "children": [ - { - "code": "340602", - "name": "杜集区" - }, - { - "code": "340603", - "name": "相山区" - }, - { - "code": "340604", - "name": "烈山区" - }, - { - "code": "340621", - "name": "濉溪县" - } - ] - }, - { - "code": "3407", - "name": "铜陵市", - "children": [ - { - "code": "340705", - "name": "铜官区" - }, - { - "code": "340706", - "name": "义安区" - }, - { - "code": "340711", - "name": "郊区" - }, - { - "code": "340722", - "name": "枞阳县" - } - ] - }, - { - "code": "3408", - "name": "安庆市", - "children": [ - { - "code": "340802", - "name": "迎江区" - }, - { - "code": "340803", - "name": "大观区" - }, - { - "code": "340811", - "name": "宜秀区" - }, - { - "code": "340822", - "name": "怀宁县" - }, - { - "code": "340825", - "name": "太湖县" - }, - { - "code": "340826", - "name": "宿松县" - }, - { - "code": "340827", - "name": "望江县" - }, - { - "code": "340828", - "name": "岳西县" - }, - { - "code": "340871", - "name": "安徽安庆经济开发区" - }, - { - "code": "340881", - "name": "桐城市" - }, - { - "code": "340882", - "name": "潜山市" - } - ] - }, - { - "code": "3410", - "name": "黄山市", - "children": [ - { - "code": "341002", - "name": "屯溪区" - }, - { - "code": "341003", - "name": "黄山区" - }, - { - "code": "341004", - "name": "徽州区" - }, - { - "code": "341021", - "name": "歙县" - }, - { - "code": "341022", - "name": "休宁县" - }, - { - "code": "341023", - "name": "黟县" - }, - { - "code": "341024", - "name": "祁门县" - } - ] - }, - { - "code": "3411", - "name": "滁州市", - "children": [ - { - "code": "341102", - "name": "琅琊区" - }, - { - "code": "341103", - "name": "南谯区" - }, - { - "code": "341122", - "name": "来安县" - }, - { - "code": "341124", - "name": "全椒县" - }, - { - "code": "341125", - "name": "定远县" - }, - { - "code": "341126", - "name": "凤阳县" - }, - { - "code": "341171", - "name": "中新苏滁高新技术产业开发区" - }, - { - "code": "341172", - "name": "滁州经济技术开发区" - }, - { - "code": "341181", - "name": "天长市" - }, - { - "code": "341182", - "name": "明光市" - } - ] - }, - { - "code": "3412", - "name": "阜阳市", - "children": [ - { - "code": "341202", - "name": "颍州区" - }, - { - "code": "341203", - "name": "颍东区" - }, - { - "code": "341204", - "name": "颍泉区" - }, - { - "code": "341221", - "name": "临泉县" - }, - { - "code": "341222", - "name": "太和县" - }, - { - "code": "341225", - "name": "阜南县" - }, - { - "code": "341226", - "name": "颍上县" - }, - { - "code": "341271", - "name": "阜阳合肥现代产业园区" - }, - { - "code": "341272", - "name": "阜阳经济技术开发区" - }, - { - "code": "341282", - "name": "界首市" - } - ] - }, - { - "code": "3413", - "name": "宿州市", - "children": [ - { - "code": "341302", - "name": "埇桥区" - }, - { - "code": "341321", - "name": "砀山县" - }, - { - "code": "341322", - "name": "萧县" - }, - { - "code": "341323", - "name": "灵璧县" - }, - { - "code": "341324", - "name": "泗县" - }, - { - "code": "341371", - "name": "宿州马鞍山现代产业园区" - }, - { - "code": "341372", - "name": "宿州经济技术开发区" - } - ] - }, - { - "code": "3415", - "name": "六安市", - "children": [ - { - "code": "341502", - "name": "金安区" - }, - { - "code": "341503", - "name": "裕安区" - }, - { - "code": "341504", - "name": "叶集区" - }, - { - "code": "341522", - "name": "霍邱县" - }, - { - "code": "341523", - "name": "舒城县" - }, - { - "code": "341524", - "name": "金寨县" - }, - { - "code": "341525", - "name": "霍山县" - } - ] - }, - { - "code": "3416", - "name": "亳州市", - "children": [ - { - "code": "341602", - "name": "谯城区" - }, - { - "code": "341621", - "name": "涡阳县" - }, - { - "code": "341622", - "name": "蒙城县" - }, - { - "code": "341623", - "name": "利辛县" - } - ] - }, - { - "code": "3417", - "name": "池州市", - "children": [ - { - "code": "341702", - "name": "贵池区" - }, - { - "code": "341721", - "name": "东至县" - }, - { - "code": "341722", - "name": "石台县" - }, - { - "code": "341723", - "name": "青阳县" - } - ] - }, - { - "code": "3418", - "name": "宣城市", - "children": [ - { - "code": "341802", - "name": "宣州区" - }, - { - "code": "341821", - "name": "郎溪县" - }, - { - "code": "341823", - "name": "泾县" - }, - { - "code": "341824", - "name": "绩溪县" - }, - { - "code": "341825", - "name": "旌德县" - }, - { - "code": "341871", - "name": "宣城市经济开发区" - }, - { - "code": "341881", - "name": "宁国市" - }, - { - "code": "341882", - "name": "广德市" - } - ] - } - ] - }, - { - "code": "35", - "name": "福建省", - "children": [ - { - "code": "3501", - "name": "福州市", - "children": [ - { - "code": "350102", - "name": "鼓楼区" - }, - { - "code": "350103", - "name": "台江区" - }, - { - "code": "350104", - "name": "仓山区" - }, - { - "code": "350105", - "name": "马尾区" - }, - { - "code": "350111", - "name": "晋安区" - }, - { - "code": "350112", - "name": "长乐区" - }, - { - "code": "350121", - "name": "闽侯县" - }, - { - "code": "350122", - "name": "连江县" - }, - { - "code": "350123", - "name": "罗源县" - }, - { - "code": "350124", - "name": "闽清县" - }, - { - "code": "350125", - "name": "永泰县" - }, - { - "code": "350128", - "name": "平潭县" - }, - { - "code": "350181", - "name": "福清市" - } - ] - }, - { - "code": "3502", - "name": "厦门市", - "children": [ - { - "code": "350203", - "name": "思明区" - }, - { - "code": "350205", - "name": "海沧区" - }, - { - "code": "350206", - "name": "湖里区" - }, - { - "code": "350211", - "name": "集美区" - }, - { - "code": "350212", - "name": "同安区" - }, - { - "code": "350213", - "name": "翔安区" - } - ] - }, - { - "code": "3503", - "name": "莆田市", - "children": [ - { - "code": "350302", - "name": "城厢区" - }, - { - "code": "350303", - "name": "涵江区" - }, - { - "code": "350304", - "name": "荔城区" - }, - { - "code": "350305", - "name": "秀屿区" - }, - { - "code": "350322", - "name": "仙游县" - } - ] - }, - { - "code": "3504", - "name": "三明市", - "children": [ - { - "code": "350404", - "name": "三元区" - }, - { - "code": "350405", - "name": "沙县区" - }, - { - "code": "350421", - "name": "明溪县" - }, - { - "code": "350423", - "name": "清流县" - }, - { - "code": "350424", - "name": "宁化县" - }, - { - "code": "350425", - "name": "大田县" - }, - { - "code": "350426", - "name": "尤溪县" - }, - { - "code": "350428", - "name": "将乐县" - }, - { - "code": "350429", - "name": "泰宁县" - }, - { - "code": "350430", - "name": "建宁县" - }, - { - "code": "350481", - "name": "永安市" - } - ] - }, - { - "code": "3505", - "name": "泉州市", - "children": [ - { - "code": "350502", - "name": "鲤城区" - }, - { - "code": "350503", - "name": "丰泽区" - }, - { - "code": "350504", - "name": "洛江区" - }, - { - "code": "350505", - "name": "泉港区" - }, - { - "code": "350521", - "name": "惠安县" - }, - { - "code": "350524", - "name": "安溪县" - }, - { - "code": "350525", - "name": "永春县" - }, - { - "code": "350526", - "name": "德化县" - }, - { - "code": "350527", - "name": "金门县" - }, - { - "code": "350581", - "name": "石狮市" - }, - { - "code": "350582", - "name": "晋江市" - }, - { - "code": "350583", - "name": "南安市" - } - ] - }, - { - "code": "3506", - "name": "漳州市", - "children": [ - { - "code": "350602", - "name": "芗城区" - }, - { - "code": "350603", - "name": "龙文区" - }, - { - "code": "350604", - "name": "龙海区" - }, - { - "code": "350605", - "name": "长泰区" - }, - { - "code": "350622", - "name": "云霄县" - }, - { - "code": "350623", - "name": "漳浦县" - }, - { - "code": "350624", - "name": "诏安县" - }, - { - "code": "350626", - "name": "东山县" - }, - { - "code": "350627", - "name": "南靖县" - }, - { - "code": "350628", - "name": "平和县" - }, - { - "code": "350629", - "name": "华安县" - } - ] - }, - { - "code": "3507", - "name": "南平市", - "children": [ - { - "code": "350702", - "name": "延平区" - }, - { - "code": "350703", - "name": "建阳区" - }, - { - "code": "350721", - "name": "顺昌县" - }, - { - "code": "350722", - "name": "浦城县" - }, - { - "code": "350723", - "name": "光泽县" - }, - { - "code": "350724", - "name": "松溪县" - }, - { - "code": "350725", - "name": "政和县" - }, - { - "code": "350781", - "name": "邵武市" - }, - { - "code": "350782", - "name": "武夷山市" - }, - { - "code": "350783", - "name": "建瓯市" - } - ] - }, - { - "code": "3508", - "name": "龙岩市", - "children": [ - { - "code": "350802", - "name": "新罗区" - }, - { - "code": "350803", - "name": "永定区" - }, - { - "code": "350821", - "name": "长汀县" - }, - { - "code": "350823", - "name": "上杭县" - }, - { - "code": "350824", - "name": "武平县" - }, - { - "code": "350825", - "name": "连城县" - }, - { - "code": "350881", - "name": "漳平市" - } - ] - }, - { - "code": "3509", - "name": "宁德市", - "children": [ - { - "code": "350902", - "name": "蕉城区" - }, - { - "code": "350921", - "name": "霞浦县" - }, - { - "code": "350922", - "name": "古田县" - }, - { - "code": "350923", - "name": "屏南县" - }, - { - "code": "350924", - "name": "寿宁县" - }, - { - "code": "350925", - "name": "周宁县" - }, - { - "code": "350926", - "name": "柘荣县" - }, - { - "code": "350981", - "name": "福安市" - }, - { - "code": "350982", - "name": "福鼎市" - } - ] - } - ] - }, - { - "code": "36", - "name": "江西省", - "children": [ - { - "code": "3601", - "name": "南昌市", - "children": [ - { - "code": "360102", - "name": "东湖区" - }, - { - "code": "360103", - "name": "西湖区" - }, - { - "code": "360104", - "name": "青云谱区" - }, - { - "code": "360111", - "name": "青山湖区" - }, - { - "code": "360112", - "name": "新建区" - }, - { - "code": "360113", - "name": "红谷滩区" - }, - { - "code": "360121", - "name": "南昌县" - }, - { - "code": "360123", - "name": "安义县" - }, - { - "code": "360124", - "name": "进贤县" - } - ] - }, - { - "code": "3602", - "name": "景德镇市", - "children": [ - { - "code": "360202", - "name": "昌江区" - }, - { - "code": "360203", - "name": "珠山区" - }, - { - "code": "360222", - "name": "浮梁县" - }, - { - "code": "360281", - "name": "乐平市" - } - ] - }, - { - "code": "3603", - "name": "萍乡市", - "children": [ - { - "code": "360302", - "name": "安源区" - }, - { - "code": "360313", - "name": "湘东区" - }, - { - "code": "360321", - "name": "莲花县" - }, - { - "code": "360322", - "name": "上栗县" - }, - { - "code": "360323", - "name": "芦溪县" - } - ] - }, - { - "code": "3604", - "name": "九江市", - "children": [ - { - "code": "360402", - "name": "濂溪区" - }, - { - "code": "360403", - "name": "浔阳区" - }, - { - "code": "360404", - "name": "柴桑区" - }, - { - "code": "360423", - "name": "武宁县" - }, - { - "code": "360424", - "name": "修水县" - }, - { - "code": "360425", - "name": "永修县" - }, - { - "code": "360426", - "name": "德安县" - }, - { - "code": "360428", - "name": "都昌县" - }, - { - "code": "360429", - "name": "湖口县" - }, - { - "code": "360430", - "name": "彭泽县" - }, - { - "code": "360481", - "name": "瑞昌市" - }, - { - "code": "360482", - "name": "共青城市" - }, - { - "code": "360483", - "name": "庐山市" - } - ] - }, - { - "code": "3605", - "name": "新余市", - "children": [ - { - "code": "360502", - "name": "渝水区" - }, - { - "code": "360521", - "name": "分宜县" - } - ] - }, - { - "code": "3606", - "name": "鹰潭市", - "children": [ - { - "code": "360602", - "name": "月湖区" - }, - { - "code": "360603", - "name": "余江区" - }, - { - "code": "360681", - "name": "贵溪市" - } - ] - }, - { - "code": "3607", - "name": "赣州市", - "children": [ - { - "code": "360702", - "name": "章贡区" - }, - { - "code": "360703", - "name": "南康区" - }, - { - "code": "360704", - "name": "赣县区" - }, - { - "code": "360722", - "name": "信丰县" - }, - { - "code": "360723", - "name": "大余县" - }, - { - "code": "360724", - "name": "上犹县" - }, - { - "code": "360725", - "name": "崇义县" - }, - { - "code": "360726", - "name": "安远县" - }, - { - "code": "360728", - "name": "定南县" - }, - { - "code": "360729", - "name": "全南县" - }, - { - "code": "360730", - "name": "宁都县" - }, - { - "code": "360731", - "name": "于都县" - }, - { - "code": "360732", - "name": "兴国县" - }, - { - "code": "360733", - "name": "会昌县" - }, - { - "code": "360734", - "name": "寻乌县" - }, - { - "code": "360735", - "name": "石城县" - }, - { - "code": "360781", - "name": "瑞金市" - }, - { - "code": "360783", - "name": "龙南市" - } - ] - }, - { - "code": "3608", - "name": "吉安市", - "children": [ - { - "code": "360802", - "name": "吉州区" - }, - { - "code": "360803", - "name": "青原区" - }, - { - "code": "360821", - "name": "吉安县" - }, - { - "code": "360822", - "name": "吉水县" - }, - { - "code": "360823", - "name": "峡江县" - }, - { - "code": "360824", - "name": "新干县" - }, - { - "code": "360825", - "name": "永丰县" - }, - { - "code": "360826", - "name": "泰和县" - }, - { - "code": "360827", - "name": "遂川县" - }, - { - "code": "360828", - "name": "万安县" - }, - { - "code": "360829", - "name": "安福县" - }, - { - "code": "360830", - "name": "永新县" - }, - { - "code": "360881", - "name": "井冈山市" - } - ] - }, - { - "code": "3609", - "name": "宜春市", - "children": [ - { - "code": "360902", - "name": "袁州区" - }, - { - "code": "360921", - "name": "奉新县" - }, - { - "code": "360922", - "name": "万载县" - }, - { - "code": "360923", - "name": "上高县" - }, - { - "code": "360924", - "name": "宜丰县" - }, - { - "code": "360925", - "name": "靖安县" - }, - { - "code": "360926", - "name": "铜鼓县" - }, - { - "code": "360981", - "name": "丰城市" - }, - { - "code": "360982", - "name": "樟树市" - }, - { - "code": "360983", - "name": "高安市" - } - ] - }, - { - "code": "3610", - "name": "抚州市", - "children": [ - { - "code": "361002", - "name": "临川区" - }, - { - "code": "361003", - "name": "东乡区" - }, - { - "code": "361021", - "name": "南城县" - }, - { - "code": "361022", - "name": "黎川县" - }, - { - "code": "361023", - "name": "南丰县" - }, - { - "code": "361024", - "name": "崇仁县" - }, - { - "code": "361025", - "name": "乐安县" - }, - { - "code": "361026", - "name": "宜黄县" - }, - { - "code": "361027", - "name": "金溪县" - }, - { - "code": "361028", - "name": "资溪县" - }, - { - "code": "361030", - "name": "广昌县" - } - ] - }, - { - "code": "3611", - "name": "上饶市", - "children": [ - { - "code": "361102", - "name": "信州区" - }, - { - "code": "361103", - "name": "广丰区" - }, - { - "code": "361104", - "name": "广信区" - }, - { - "code": "361123", - "name": "玉山县" - }, - { - "code": "361124", - "name": "铅山县" - }, - { - "code": "361125", - "name": "横峰县" - }, - { - "code": "361126", - "name": "弋阳县" - }, - { - "code": "361127", - "name": "余干县" - }, - { - "code": "361128", - "name": "鄱阳县" - }, - { - "code": "361129", - "name": "万年县" - }, - { - "code": "361130", - "name": "婺源县" - }, - { - "code": "361181", - "name": "德兴市" - } - ] - } - ] - }, - { - "code": "37", - "name": "山东省", - "children": [ - { - "code": "3701", - "name": "济南市", - "children": [ - { - "code": "370102", - "name": "历下区" - }, - { - "code": "370103", - "name": "市中区" - }, - { - "code": "370104", - "name": "槐荫区" - }, - { - "code": "370105", - "name": "天桥区" - }, - { - "code": "370112", - "name": "历城区" - }, - { - "code": "370113", - "name": "长清区" - }, - { - "code": "370114", - "name": "章丘区" - }, - { - "code": "370115", - "name": "济阳区" - }, - { - "code": "370116", - "name": "莱芜区" - }, - { - "code": "370117", - "name": "钢城区" - }, - { - "code": "370124", - "name": "平阴县" - }, - { - "code": "370126", - "name": "商河县" - }, - { - "code": "370176", - "name": "济南高新技术产业开发区" - } - ] - }, - { - "code": "3702", - "name": "青岛市", - "children": [ - { - "code": "370202", - "name": "市南区" - }, - { - "code": "370203", - "name": "市北区" - }, - { - "code": "370211", - "name": "黄岛区" - }, - { - "code": "370212", - "name": "崂山区" - }, - { - "code": "370213", - "name": "李沧区" - }, - { - "code": "370214", - "name": "城阳区" - }, - { - "code": "370215", - "name": "即墨区" - }, - { - "code": "370281", - "name": "胶州市" - }, - { - "code": "370283", - "name": "平度市" - }, - { - "code": "370285", - "name": "莱西市" - } - ] - }, - { - "code": "3703", - "name": "淄博市", - "children": [ - { - "code": "370302", - "name": "淄川区" - }, - { - "code": "370303", - "name": "张店区" - }, - { - "code": "370304", - "name": "博山区" - }, - { - "code": "370305", - "name": "临淄区" - }, - { - "code": "370306", - "name": "周村区" - }, - { - "code": "370321", - "name": "桓台县" - }, - { - "code": "370322", - "name": "高青县" - }, - { - "code": "370323", - "name": "沂源县" - } - ] - }, - { - "code": "3704", - "name": "枣庄市", - "children": [ - { - "code": "370402", - "name": "市中区" - }, - { - "code": "370403", - "name": "薛城区" - }, - { - "code": "370404", - "name": "峄城区" - }, - { - "code": "370405", - "name": "台儿庄区" - }, - { - "code": "370406", - "name": "山亭区" - }, - { - "code": "370481", - "name": "滕州市" - } - ] - }, - { - "code": "3705", - "name": "东营市", - "children": [ - { - "code": "370502", - "name": "东营区" - }, - { - "code": "370503", - "name": "河口区" - }, - { - "code": "370505", - "name": "垦利区" - }, - { - "code": "370522", - "name": "利津县" - }, - { - "code": "370523", - "name": "广饶县" - }, - { - "code": "370571", - "name": "东营经济技术开发区" - }, - { - "code": "370572", - "name": "东营港经济开发区" - } - ] - }, - { - "code": "3706", - "name": "烟台市", - "children": [ - { - "code": "370602", - "name": "芝罘区" - }, - { - "code": "370611", - "name": "福山区" - }, - { - "code": "370612", - "name": "牟平区" - }, - { - "code": "370613", - "name": "莱山区" - }, - { - "code": "370614", - "name": "蓬莱区" - }, - { - "code": "370671", - "name": "烟台高新技术产业开发区" - }, - { - "code": "370676", - "name": "烟台经济技术开发区" - }, - { - "code": "370681", - "name": "龙口市" - }, - { - "code": "370682", - "name": "莱阳市" - }, - { - "code": "370683", - "name": "莱州市" - }, - { - "code": "370685", - "name": "招远市" - }, - { - "code": "370686", - "name": "栖霞市" - }, - { - "code": "370687", - "name": "海阳市" - } - ] - }, - { - "code": "3707", - "name": "潍坊市", - "children": [ - { - "code": "370702", - "name": "潍城区" - }, - { - "code": "370703", - "name": "寒亭区" - }, - { - "code": "370704", - "name": "坊子区" - }, - { - "code": "370705", - "name": "奎文区" - }, - { - "code": "370724", - "name": "临朐县" - }, - { - "code": "370725", - "name": "昌乐县" - }, - { - "code": "370772", - "name": "潍坊滨海经济技术开发区" - }, - { - "code": "370781", - "name": "青州市" - }, - { - "code": "370782", - "name": "诸城市" - }, - { - "code": "370783", - "name": "寿光市" - }, - { - "code": "370784", - "name": "安丘市" - }, - { - "code": "370785", - "name": "高密市" - }, - { - "code": "370786", - "name": "昌邑市" - } - ] - }, - { - "code": "3708", - "name": "济宁市", - "children": [ - { - "code": "370811", - "name": "任城区" - }, - { - "code": "370812", - "name": "兖州区" - }, - { - "code": "370826", - "name": "微山县" - }, - { - "code": "370827", - "name": "鱼台县" - }, - { - "code": "370828", - "name": "金乡县" - }, - { - "code": "370829", - "name": "嘉祥县" - }, - { - "code": "370830", - "name": "汶上县" - }, - { - "code": "370831", - "name": "泗水县" - }, - { - "code": "370832", - "name": "梁山县" - }, - { - "code": "370871", - "name": "济宁高新技术产业开发区" - }, - { - "code": "370881", - "name": "曲阜市" - }, - { - "code": "370883", - "name": "邹城市" - } - ] - }, - { - "code": "3709", - "name": "泰安市", - "children": [ - { - "code": "370902", - "name": "泰山区" - }, - { - "code": "370911", - "name": "岱岳区" - }, - { - "code": "370921", - "name": "宁阳县" - }, - { - "code": "370923", - "name": "东平县" - }, - { - "code": "370982", - "name": "新泰市" - }, - { - "code": "370983", - "name": "肥城市" - } - ] - }, - { - "code": "3710", - "name": "威海市", - "children": [ - { - "code": "371002", - "name": "环翠区" - }, - { - "code": "371003", - "name": "文登区" - }, - { - "code": "371071", - "name": "威海火炬高技术产业开发区" - }, - { - "code": "371072", - "name": "威海经济技术开发区" - }, - { - "code": "371073", - "name": "威海临港经济技术开发区" - }, - { - "code": "371082", - "name": "荣成市" - }, - { - "code": "371083", - "name": "乳山市" - } - ] - }, - { - "code": "3711", - "name": "日照市", - "children": [ - { - "code": "371102", - "name": "东港区" - }, - { - "code": "371103", - "name": "岚山区" - }, - { - "code": "371121", - "name": "五莲县" - }, - { - "code": "371122", - "name": "莒县" - }, - { - "code": "371171", - "name": "日照经济技术开发区" - } - ] - }, - { - "code": "3713", - "name": "临沂市", - "children": [ - { - "code": "371302", - "name": "兰山区" - }, - { - "code": "371311", - "name": "罗庄区" - }, - { - "code": "371312", - "name": "河东区" - }, - { - "code": "371321", - "name": "沂南县" - }, - { - "code": "371322", - "name": "郯城县" - }, - { - "code": "371323", - "name": "沂水县" - }, - { - "code": "371324", - "name": "兰陵县" - }, - { - "code": "371325", - "name": "费县" - }, - { - "code": "371326", - "name": "平邑县" - }, - { - "code": "371327", - "name": "莒南县" - }, - { - "code": "371328", - "name": "蒙阴县" - }, - { - "code": "371329", - "name": "临沭县" - }, - { - "code": "371371", - "name": "临沂高新技术产业开发区" - } - ] - }, - { - "code": "3714", - "name": "德州市", - "children": [ - { - "code": "371402", - "name": "德城区" - }, - { - "code": "371403", - "name": "陵城区" - }, - { - "code": "371422", - "name": "宁津县" - }, - { - "code": "371423", - "name": "庆云县" - }, - { - "code": "371424", - "name": "临邑县" - }, - { - "code": "371425", - "name": "齐河县" - }, - { - "code": "371426", - "name": "平原县" - }, - { - "code": "371427", - "name": "夏津县" - }, - { - "code": "371428", - "name": "武城县" - }, - { - "code": "371471", - "name": "德州天衢新区" - }, - { - "code": "371481", - "name": "乐陵市" - }, - { - "code": "371482", - "name": "禹城市" - } - ] - }, - { - "code": "3715", - "name": "聊城市", - "children": [ - { - "code": "371502", - "name": "东昌府区" - }, - { - "code": "371503", - "name": "茌平区" - }, - { - "code": "371521", - "name": "阳谷县" - }, - { - "code": "371522", - "name": "莘县" - }, - { - "code": "371524", - "name": "东阿县" - }, - { - "code": "371525", - "name": "冠县" - }, - { - "code": "371526", - "name": "高唐县" - }, - { - "code": "371581", - "name": "临清市" - } - ] - }, - { - "code": "3716", - "name": "滨州市", - "children": [ - { - "code": "371602", - "name": "滨城区" - }, - { - "code": "371603", - "name": "沾化区" - }, - { - "code": "371621", - "name": "惠民县" - }, - { - "code": "371622", - "name": "阳信县" - }, - { - "code": "371623", - "name": "无棣县" - }, - { - "code": "371625", - "name": "博兴县" - }, - { - "code": "371681", - "name": "邹平市" - } - ] - }, - { - "code": "3717", - "name": "菏泽市", - "children": [ - { - "code": "371702", - "name": "牡丹区" - }, - { - "code": "371703", - "name": "定陶区" - }, - { - "code": "371721", - "name": "曹县" - }, - { - "code": "371722", - "name": "单县" - }, - { - "code": "371723", - "name": "成武县" - }, - { - "code": "371724", - "name": "巨野县" - }, - { - "code": "371725", - "name": "郓城县" - }, - { - "code": "371726", - "name": "鄄城县" - }, - { - "code": "371728", - "name": "东明县" - }, - { - "code": "371771", - "name": "菏泽经济技术开发区" - }, - { - "code": "371772", - "name": "菏泽高新技术开发区" - } - ] - } - ] - }, - { - "code": "41", - "name": "河南省", - "children": [ - { - "code": "4101", - "name": "郑州市", - "children": [ - { - "code": "410102", - "name": "中原区" - }, - { - "code": "410103", - "name": "二七区" - }, - { - "code": "410104", - "name": "管城回族区" - }, - { - "code": "410105", - "name": "金水区" - }, - { - "code": "410106", - "name": "上街区" - }, - { - "code": "410108", - "name": "惠济区" - }, - { - "code": "410122", - "name": "中牟县" - }, - { - "code": "410171", - "name": "郑州经济技术开发区" - }, - { - "code": "410172", - "name": "郑州高新技术产业开发区" - }, - { - "code": "410173", - "name": "郑州航空港经济综合实验区" - }, - { - "code": "410181", - "name": "巩义市" - }, - { - "code": "410182", - "name": "荥阳市" - }, - { - "code": "410183", - "name": "新密市" - }, - { - "code": "410184", - "name": "新郑市" - }, - { - "code": "410185", - "name": "登封市" - } - ] - }, - { - "code": "4102", - "name": "开封市", - "children": [ - { - "code": "410202", - "name": "龙亭区" - }, - { - "code": "410203", - "name": "顺河回族区" - }, - { - "code": "410204", - "name": "鼓楼区" - }, - { - "code": "410205", - "name": "禹王台区" - }, - { - "code": "410212", - "name": "祥符区" - }, - { - "code": "410221", - "name": "杞县" - }, - { - "code": "410222", - "name": "通许县" - }, - { - "code": "410223", - "name": "尉氏县" - }, - { - "code": "410225", - "name": "兰考县" - } - ] - }, - { - "code": "4103", - "name": "洛阳市", - "children": [ - { - "code": "410302", - "name": "老城区" - }, - { - "code": "410303", - "name": "西工区" - }, - { - "code": "410304", - "name": "瀍河回族区" - }, - { - "code": "410305", - "name": "涧西区" - }, - { - "code": "410307", - "name": "偃师区" - }, - { - "code": "410308", - "name": "孟津区" - }, - { - "code": "410311", - "name": "洛龙区" - }, - { - "code": "410323", - "name": "新安县" - }, - { - "code": "410324", - "name": "栾川县" - }, - { - "code": "410325", - "name": "嵩县" - }, - { - "code": "410326", - "name": "汝阳县" - }, - { - "code": "410327", - "name": "宜阳县" - }, - { - "code": "410328", - "name": "洛宁县" - }, - { - "code": "410329", - "name": "伊川县" - }, - { - "code": "410371", - "name": "洛阳高新技术产业开发区" - } - ] - }, - { - "code": "4104", - "name": "平顶山市", - "children": [ - { - "code": "410402", - "name": "新华区" - }, - { - "code": "410403", - "name": "卫东区" - }, - { - "code": "410404", - "name": "石龙区" - }, - { - "code": "410411", - "name": "湛河区" - }, - { - "code": "410421", - "name": "宝丰县" - }, - { - "code": "410422", - "name": "叶县" - }, - { - "code": "410423", - "name": "鲁山县" - }, - { - "code": "410425", - "name": "郏县" - }, - { - "code": "410471", - "name": "平顶山高新技术产业开发区" - }, - { - "code": "410472", - "name": "平顶山市城乡一体化示范区" - }, - { - "code": "410481", - "name": "舞钢市" - }, - { - "code": "410482", - "name": "汝州市" - } - ] - }, - { - "code": "4105", - "name": "安阳市", - "children": [ - { - "code": "410502", - "name": "文峰区" - }, - { - "code": "410503", - "name": "北关区" - }, - { - "code": "410505", - "name": "殷都区" - }, - { - "code": "410506", - "name": "龙安区" - }, - { - "code": "410522", - "name": "安阳县" - }, - { - "code": "410523", - "name": "汤阴县" - }, - { - "code": "410526", - "name": "滑县" - }, - { - "code": "410527", - "name": "内黄县" - }, - { - "code": "410571", - "name": "安阳高新技术产业开发区" - }, - { - "code": "410581", - "name": "林州市" - } - ] - }, - { - "code": "4106", - "name": "鹤壁市", - "children": [ - { - "code": "410602", - "name": "鹤山区" - }, - { - "code": "410603", - "name": "山城区" - }, - { - "code": "410611", - "name": "淇滨区" - }, - { - "code": "410621", - "name": "浚县" - }, - { - "code": "410622", - "name": "淇县" - }, - { - "code": "410671", - "name": "鹤壁经济技术开发区" - } - ] - }, - { - "code": "4107", - "name": "新乡市", - "children": [ - { - "code": "410702", - "name": "红旗区" - }, - { - "code": "410703", - "name": "卫滨区" - }, - { - "code": "410704", - "name": "凤泉区" - }, - { - "code": "410711", - "name": "牧野区" - }, - { - "code": "410721", - "name": "新乡县" - }, - { - "code": "410724", - "name": "获嘉县" - }, - { - "code": "410725", - "name": "原阳县" - }, - { - "code": "410726", - "name": "延津县" - }, - { - "code": "410727", - "name": "封丘县" - }, - { - "code": "410771", - "name": "新乡高新技术产业开发区" - }, - { - "code": "410772", - "name": "新乡经济技术开发区" - }, - { - "code": "410773", - "name": "新乡市平原城乡一体化示范区" - }, - { - "code": "410781", - "name": "卫辉市" - }, - { - "code": "410782", - "name": "辉县市" - }, - { - "code": "410783", - "name": "长垣市" - } - ] - }, - { - "code": "4108", - "name": "焦作市", - "children": [ - { - "code": "410802", - "name": "解放区" - }, - { - "code": "410803", - "name": "中站区" - }, - { - "code": "410804", - "name": "马村区" - }, - { - "code": "410811", - "name": "山阳区" - }, - { - "code": "410821", - "name": "修武县" - }, - { - "code": "410822", - "name": "博爱县" - }, - { - "code": "410823", - "name": "武陟县" - }, - { - "code": "410825", - "name": "温县" - }, - { - "code": "410871", - "name": "焦作城乡一体化示范区" - }, - { - "code": "410882", - "name": "沁阳市" - }, - { - "code": "410883", - "name": "孟州市" - } - ] - }, - { - "code": "4109", - "name": "濮阳市", - "children": [ - { - "code": "410902", - "name": "华龙区" - }, - { - "code": "410922", - "name": "清丰县" - }, - { - "code": "410923", - "name": "南乐县" - }, - { - "code": "410926", - "name": "范县" - }, - { - "code": "410927", - "name": "台前县" - }, - { - "code": "410928", - "name": "濮阳县" - }, - { - "code": "410971", - "name": "河南濮阳工业园区" - }, - { - "code": "410972", - "name": "濮阳经济技术开发区" - } - ] - }, - { - "code": "4110", - "name": "许昌市", - "children": [ - { - "code": "411002", - "name": "魏都区" - }, - { - "code": "411003", - "name": "建安区" - }, - { - "code": "411024", - "name": "鄢陵县" - }, - { - "code": "411025", - "name": "襄城县" - }, - { - "code": "411071", - "name": "许昌经济技术开发区" - }, - { - "code": "411081", - "name": "禹州市" - }, - { - "code": "411082", - "name": "长葛市" - } - ] - }, - { - "code": "4111", - "name": "漯河市", - "children": [ - { - "code": "411102", - "name": "源汇区" - }, - { - "code": "411103", - "name": "郾城区" - }, - { - "code": "411104", - "name": "召陵区" - }, - { - "code": "411121", - "name": "舞阳县" - }, - { - "code": "411122", - "name": "临颍县" - }, - { - "code": "411171", - "name": "漯河经济技术开发区" - } - ] - }, - { - "code": "4112", - "name": "三门峡市", - "children": [ - { - "code": "411202", - "name": "湖滨区" - }, - { - "code": "411203", - "name": "陕州区" - }, - { - "code": "411221", - "name": "渑池县" - }, - { - "code": "411224", - "name": "卢氏县" - }, - { - "code": "411271", - "name": "河南三门峡经济开发区" - }, - { - "code": "411281", - "name": "义马市" - }, - { - "code": "411282", - "name": "灵宝市" - } - ] - }, - { - "code": "4113", - "name": "南阳市", - "children": [ - { - "code": "411302", - "name": "宛城区" - }, - { - "code": "411303", - "name": "卧龙区" - }, - { - "code": "411321", - "name": "南召县" - }, - { - "code": "411322", - "name": "方城县" - }, - { - "code": "411323", - "name": "西峡县" - }, - { - "code": "411324", - "name": "镇平县" - }, - { - "code": "411325", - "name": "内乡县" - }, - { - "code": "411326", - "name": "淅川县" - }, - { - "code": "411327", - "name": "社旗县" - }, - { - "code": "411328", - "name": "唐河县" - }, - { - "code": "411329", - "name": "新野县" - }, - { - "code": "411330", - "name": "桐柏县" - }, - { - "code": "411371", - "name": "南阳高新技术产业开发区" - }, - { - "code": "411372", - "name": "南阳市城乡一体化示范区" - }, - { - "code": "411381", - "name": "邓州市" - } - ] - }, - { - "code": "4114", - "name": "商丘市", - "children": [ - { - "code": "411402", - "name": "梁园区" - }, - { - "code": "411403", - "name": "睢阳区" - }, - { - "code": "411421", - "name": "民权县" - }, - { - "code": "411422", - "name": "睢县" - }, - { - "code": "411423", - "name": "宁陵县" - }, - { - "code": "411424", - "name": "柘城县" - }, - { - "code": "411425", - "name": "虞城县" - }, - { - "code": "411426", - "name": "夏邑县" - }, - { - "code": "411471", - "name": "豫东综合物流产业聚集区" - }, - { - "code": "411472", - "name": "河南商丘经济开发区" - }, - { - "code": "411481", - "name": "永城市" - } - ] - }, - { - "code": "4115", - "name": "信阳市", - "children": [ - { - "code": "411502", - "name": "浉河区" - }, - { - "code": "411503", - "name": "平桥区" - }, - { - "code": "411521", - "name": "罗山县" - }, - { - "code": "411522", - "name": "光山县" - }, - { - "code": "411523", - "name": "新县" - }, - { - "code": "411524", - "name": "商城县" - }, - { - "code": "411525", - "name": "固始县" - }, - { - "code": "411526", - "name": "潢川县" - }, - { - "code": "411527", - "name": "淮滨县" - }, - { - "code": "411528", - "name": "息县" - }, - { - "code": "411571", - "name": "信阳高新技术产业开发区" - } - ] - }, - { - "code": "4116", - "name": "周口市", - "children": [ - { - "code": "411602", - "name": "川汇区" - }, - { - "code": "411603", - "name": "淮阳区" - }, - { - "code": "411621", - "name": "扶沟县" - }, - { - "code": "411622", - "name": "西华县" - }, - { - "code": "411623", - "name": "商水县" - }, - { - "code": "411624", - "name": "沈丘县" - }, - { - "code": "411625", - "name": "郸城县" - }, - { - "code": "411627", - "name": "太康县" - }, - { - "code": "411628", - "name": "鹿邑县" - }, - { - "code": "411671", - "name": "周口临港开发区" - }, - { - "code": "411681", - "name": "项城市" - } - ] - }, - { - "code": "4117", - "name": "驻马店市", - "children": [ - { - "code": "411702", - "name": "驿城区" - }, - { - "code": "411721", - "name": "西平县" - }, - { - "code": "411722", - "name": "上蔡县" - }, - { - "code": "411723", - "name": "平舆县" - }, - { - "code": "411724", - "name": "正阳县" - }, - { - "code": "411725", - "name": "确山县" - }, - { - "code": "411726", - "name": "泌阳县" - }, - { - "code": "411727", - "name": "汝南县" - }, - { - "code": "411728", - "name": "遂平县" - }, - { - "code": "411729", - "name": "新蔡县" - }, - { - "code": "411771", - "name": "河南驻马店经济开发区" - } - ] - }, - { - "code": "4190", - "name": "省直辖县级行政区划", - "children": [ - { - "code": "419001", - "name": "济源市" - } - ] - } - ] - }, - { - "code": "42", - "name": "湖北省", - "children": [ - { - "code": "4201", - "name": "武汉市", - "children": [ - { - "code": "420102", - "name": "江岸区" - }, - { - "code": "420103", - "name": "江汉区" - }, - { - "code": "420104", - "name": "硚口区" - }, - { - "code": "420105", - "name": "汉阳区" - }, - { - "code": "420106", - "name": "武昌区" - }, - { - "code": "420107", - "name": "青山区" - }, - { - "code": "420111", - "name": "洪山区" - }, - { - "code": "420112", - "name": "东西湖区" - }, - { - "code": "420113", - "name": "汉南区" - }, - { - "code": "420114", - "name": "蔡甸区" - }, - { - "code": "420115", - "name": "江夏区" - }, - { - "code": "420116", - "name": "黄陂区" - }, - { - "code": "420117", - "name": "新洲区" - } - ] - }, - { - "code": "4202", - "name": "黄石市", - "children": [ - { - "code": "420202", - "name": "黄石港区" - }, - { - "code": "420203", - "name": "西塞山区" - }, - { - "code": "420204", - "name": "下陆区" - }, - { - "code": "420205", - "name": "铁山区" - }, - { - "code": "420222", - "name": "阳新县" - }, - { - "code": "420281", - "name": "大冶市" - } - ] - }, - { - "code": "4203", - "name": "十堰市", - "children": [ - { - "code": "420302", - "name": "茅箭区" - }, - { - "code": "420303", - "name": "张湾区" - }, - { - "code": "420304", - "name": "郧阳区" - }, - { - "code": "420322", - "name": "郧西县" - }, - { - "code": "420323", - "name": "竹山县" - }, - { - "code": "420324", - "name": "竹溪县" - }, - { - "code": "420325", - "name": "房县" - }, - { - "code": "420381", - "name": "丹江口市" - } - ] - }, - { - "code": "4205", - "name": "宜昌市", - "children": [ - { - "code": "420502", - "name": "西陵区" - }, - { - "code": "420503", - "name": "伍家岗区" - }, - { - "code": "420504", - "name": "点军区" - }, - { - "code": "420505", - "name": "猇亭区" - }, - { - "code": "420506", - "name": "夷陵区" - }, - { - "code": "420525", - "name": "远安县" - }, - { - "code": "420526", - "name": "兴山县" - }, - { - "code": "420527", - "name": "秭归县" - }, - { - "code": "420528", - "name": "长阳土家族自治县" - }, - { - "code": "420529", - "name": "五峰土家族自治县" - }, - { - "code": "420581", - "name": "宜都市" - }, - { - "code": "420582", - "name": "当阳市" - }, - { - "code": "420583", - "name": "枝江市" - } - ] - }, - { - "code": "4206", - "name": "襄阳市", - "children": [ - { - "code": "420602", - "name": "襄城区" - }, - { - "code": "420606", - "name": "樊城区" - }, - { - "code": "420607", - "name": "襄州区" - }, - { - "code": "420624", - "name": "南漳县" - }, - { - "code": "420625", - "name": "谷城县" - }, - { - "code": "420626", - "name": "保康县" - }, - { - "code": "420682", - "name": "老河口市" - }, - { - "code": "420683", - "name": "枣阳市" - }, - { - "code": "420684", - "name": "宜城市" - } - ] - }, - { - "code": "4207", - "name": "鄂州市", - "children": [ - { - "code": "420702", - "name": "梁子湖区" - }, - { - "code": "420703", - "name": "华容区" - }, - { - "code": "420704", - "name": "鄂城区" - } - ] - }, - { - "code": "4208", - "name": "荆门市", - "children": [ - { - "code": "420802", - "name": "东宝区" - }, - { - "code": "420804", - "name": "掇刀区" - }, - { - "code": "420822", - "name": "沙洋县" - }, - { - "code": "420881", - "name": "钟祥市" - }, - { - "code": "420882", - "name": "京山市" - } - ] - }, - { - "code": "4209", - "name": "孝感市", - "children": [ - { - "code": "420902", - "name": "孝南区" - }, - { - "code": "420921", - "name": "孝昌县" - }, - { - "code": "420922", - "name": "大悟县" - }, - { - "code": "420923", - "name": "云梦县" - }, - { - "code": "420981", - "name": "应城市" - }, - { - "code": "420982", - "name": "安陆市" - }, - { - "code": "420984", - "name": "汉川市" - } - ] - }, - { - "code": "4210", - "name": "荆州市", - "children": [ - { - "code": "421002", - "name": "沙市区" - }, - { - "code": "421003", - "name": "荆州区" - }, - { - "code": "421022", - "name": "公安县" - }, - { - "code": "421024", - "name": "江陵县" - }, - { - "code": "421071", - "name": "荆州经济技术开发区" - }, - { - "code": "421081", - "name": "石首市" - }, - { - "code": "421083", - "name": "洪湖市" - }, - { - "code": "421087", - "name": "松滋市" - }, - { - "code": "421088", - "name": "监利市" - } - ] - }, - { - "code": "4211", - "name": "黄冈市", - "children": [ - { - "code": "421102", - "name": "黄州区" - }, - { - "code": "421121", - "name": "团风县" - }, - { - "code": "421122", - "name": "红安县" - }, - { - "code": "421123", - "name": "罗田县" - }, - { - "code": "421124", - "name": "英山县" - }, - { - "code": "421125", - "name": "浠水县" - }, - { - "code": "421126", - "name": "蕲春县" - }, - { - "code": "421127", - "name": "黄梅县" - }, - { - "code": "421171", - "name": "龙感湖管理区" - }, - { - "code": "421181", - "name": "麻城市" - }, - { - "code": "421182", - "name": "武穴市" - } - ] - }, - { - "code": "4212", - "name": "咸宁市", - "children": [ - { - "code": "421202", - "name": "咸安区" - }, - { - "code": "421221", - "name": "嘉鱼县" - }, - { - "code": "421222", - "name": "通城县" - }, - { - "code": "421223", - "name": "崇阳县" - }, - { - "code": "421224", - "name": "通山县" - }, - { - "code": "421281", - "name": "赤壁市" - } - ] - }, - { - "code": "4213", - "name": "随州市", - "children": [ - { - "code": "421303", - "name": "曾都区" - }, - { - "code": "421321", - "name": "随县" - }, - { - "code": "421381", - "name": "广水市" - } - ] - }, - { - "code": "4228", - "name": "恩施土家族苗族自治州", - "children": [ - { - "code": "422801", - "name": "恩施市" - }, - { - "code": "422802", - "name": "利川市" - }, - { - "code": "422822", - "name": "建始县" - }, - { - "code": "422823", - "name": "巴东县" - }, - { - "code": "422825", - "name": "宣恩县" - }, - { - "code": "422826", - "name": "咸丰县" - }, - { - "code": "422827", - "name": "来凤县" - }, - { - "code": "422828", - "name": "鹤峰县" - } - ] - }, - { - "code": "4290", - "name": "省直辖县级行政区划", - "children": [ - { - "code": "429004", - "name": "仙桃市" - }, - { - "code": "429005", - "name": "潜江市" - }, - { - "code": "429006", - "name": "天门市" - }, - { - "code": "429021", - "name": "神农架林区" - } - ] - } - ] - }, - { - "code": "43", - "name": "湖南省", - "children": [ - { - "code": "4301", - "name": "长沙市", - "children": [ - { - "code": "430102", - "name": "芙蓉区" - }, - { - "code": "430103", - "name": "天心区" - }, - { - "code": "430104", - "name": "岳麓区" - }, - { - "code": "430105", - "name": "开福区" - }, - { - "code": "430111", - "name": "雨花区" - }, - { - "code": "430112", - "name": "望城区" - }, - { - "code": "430121", - "name": "长沙县" - }, - { - "code": "430181", - "name": "浏阳市" - }, - { - "code": "430182", - "name": "宁乡市" - } - ] - }, - { - "code": "4302", - "name": "株洲市", - "children": [ - { - "code": "430202", - "name": "荷塘区" - }, - { - "code": "430203", - "name": "芦淞区" - }, - { - "code": "430204", - "name": "石峰区" - }, - { - "code": "430211", - "name": "天元区" - }, - { - "code": "430212", - "name": "渌口区" - }, - { - "code": "430223", - "name": "攸县" - }, - { - "code": "430224", - "name": "茶陵县" - }, - { - "code": "430225", - "name": "炎陵县" - }, - { - "code": "430281", - "name": "醴陵市" - } - ] - }, - { - "code": "4303", - "name": "湘潭市", - "children": [ - { - "code": "430302", - "name": "雨湖区" - }, - { - "code": "430304", - "name": "岳塘区" - }, - { - "code": "430321", - "name": "湘潭县" - }, - { - "code": "430371", - "name": "湖南湘潭高新技术产业园区" - }, - { - "code": "430372", - "name": "湘潭昭山示范区" - }, - { - "code": "430373", - "name": "湘潭九华示范区" - }, - { - "code": "430381", - "name": "湘乡市" - }, - { - "code": "430382", - "name": "韶山市" - } - ] - }, - { - "code": "4304", - "name": "衡阳市", - "children": [ - { - "code": "430405", - "name": "珠晖区" - }, - { - "code": "430406", - "name": "雁峰区" - }, - { - "code": "430407", - "name": "石鼓区" - }, - { - "code": "430408", - "name": "蒸湘区" - }, - { - "code": "430412", - "name": "南岳区" - }, - { - "code": "430421", - "name": "衡阳县" - }, - { - "code": "430422", - "name": "衡南县" - }, - { - "code": "430423", - "name": "衡山县" - }, - { - "code": "430424", - "name": "衡东县" - }, - { - "code": "430426", - "name": "祁东县" - }, - { - "code": "430473", - "name": "湖南衡阳松木经济开发区" - }, - { - "code": "430476", - "name": "湖南衡阳高新技术产业园区" - }, - { - "code": "430481", - "name": "耒阳市" - }, - { - "code": "430482", - "name": "常宁市" - } - ] - }, - { - "code": "4305", - "name": "邵阳市", - "children": [ - { - "code": "430502", - "name": "双清区" - }, - { - "code": "430503", - "name": "大祥区" - }, - { - "code": "430511", - "name": "北塔区" - }, - { - "code": "430522", - "name": "新邵县" - }, - { - "code": "430523", - "name": "邵阳县" - }, - { - "code": "430524", - "name": "隆回县" - }, - { - "code": "430525", - "name": "洞口县" - }, - { - "code": "430527", - "name": "绥宁县" - }, - { - "code": "430528", - "name": "新宁县" - }, - { - "code": "430529", - "name": "城步苗族自治县" - }, - { - "code": "430581", - "name": "武冈市" - }, - { - "code": "430582", - "name": "邵东市" - } - ] - }, - { - "code": "4306", - "name": "岳阳市", - "children": [ - { - "code": "430602", - "name": "岳阳楼区" - }, - { - "code": "430603", - "name": "云溪区" - }, - { - "code": "430611", - "name": "君山区" - }, - { - "code": "430621", - "name": "岳阳县" - }, - { - "code": "430623", - "name": "华容县" - }, - { - "code": "430624", - "name": "湘阴县" - }, - { - "code": "430626", - "name": "平江县" - }, - { - "code": "430671", - "name": "岳阳市屈原管理区" - }, - { - "code": "430681", - "name": "汨罗市" - }, - { - "code": "430682", - "name": "临湘市" - } - ] - }, - { - "code": "4307", - "name": "常德市", - "children": [ - { - "code": "430702", - "name": "武陵区" - }, - { - "code": "430703", - "name": "鼎城区" - }, - { - "code": "430721", - "name": "安乡县" - }, - { - "code": "430722", - "name": "汉寿县" - }, - { - "code": "430723", - "name": "澧县" - }, - { - "code": "430724", - "name": "临澧县" - }, - { - "code": "430725", - "name": "桃源县" - }, - { - "code": "430726", - "name": "石门县" - }, - { - "code": "430771", - "name": "常德市西洞庭管理区" - }, - { - "code": "430781", - "name": "津市市" - } - ] - }, - { - "code": "4308", - "name": "张家界市", - "children": [ - { - "code": "430802", - "name": "永定区" - }, - { - "code": "430811", - "name": "武陵源区" - }, - { - "code": "430821", - "name": "慈利县" - }, - { - "code": "430822", - "name": "桑植县" - } - ] - }, - { - "code": "4309", - "name": "益阳市", - "children": [ - { - "code": "430902", - "name": "资阳区" - }, - { - "code": "430903", - "name": "赫山区" - }, - { - "code": "430921", - "name": "南县" - }, - { - "code": "430922", - "name": "桃江县" - }, - { - "code": "430923", - "name": "安化县" - }, - { - "code": "430971", - "name": "益阳市大通湖管理区" - }, - { - "code": "430972", - "name": "湖南益阳高新技术产业园区" - }, - { - "code": "430981", - "name": "沅江市" - } - ] - }, - { - "code": "4310", - "name": "郴州市", - "children": [ - { - "code": "431002", - "name": "北湖区" - }, - { - "code": "431003", - "name": "苏仙区" - }, - { - "code": "431021", - "name": "桂阳县" - }, - { - "code": "431022", - "name": "宜章县" - }, - { - "code": "431023", - "name": "永兴县" - }, - { - "code": "431024", - "name": "嘉禾县" - }, - { - "code": "431025", - "name": "临武县" - }, - { - "code": "431026", - "name": "汝城县" - }, - { - "code": "431027", - "name": "桂东县" - }, - { - "code": "431028", - "name": "安仁县" - }, - { - "code": "431081", - "name": "资兴市" - } - ] - }, - { - "code": "4311", - "name": "永州市", - "children": [ - { - "code": "431102", - "name": "零陵区" - }, - { - "code": "431103", - "name": "冷水滩区" - }, - { - "code": "431122", - "name": "东安县" - }, - { - "code": "431123", - "name": "双牌县" - }, - { - "code": "431124", - "name": "道县" - }, - { - "code": "431125", - "name": "江永县" - }, - { - "code": "431126", - "name": "宁远县" - }, - { - "code": "431127", - "name": "蓝山县" - }, - { - "code": "431128", - "name": "新田县" - }, - { - "code": "431129", - "name": "江华瑶族自治县" - }, - { - "code": "431171", - "name": "永州经济技术开发区" - }, - { - "code": "431173", - "name": "永州市回龙圩管理区" - }, - { - "code": "431181", - "name": "祁阳市" - } - ] - }, - { - "code": "4312", - "name": "怀化市", - "children": [ - { - "code": "431202", - "name": "鹤城区" - }, - { - "code": "431221", - "name": "中方县" - }, - { - "code": "431222", - "name": "沅陵县" - }, - { - "code": "431223", - "name": "辰溪县" - }, - { - "code": "431224", - "name": "溆浦县" - }, - { - "code": "431225", - "name": "会同县" - }, - { - "code": "431226", - "name": "麻阳苗族自治县" - }, - { - "code": "431227", - "name": "新晃侗族自治县" - }, - { - "code": "431228", - "name": "芷江侗族自治县" - }, - { - "code": "431229", - "name": "靖州苗族侗族自治县" - }, - { - "code": "431230", - "name": "通道侗族自治县" - }, - { - "code": "431271", - "name": "怀化市洪江管理区" - }, - { - "code": "431281", - "name": "洪江市" - } - ] - }, - { - "code": "4313", - "name": "娄底市", - "children": [ - { - "code": "431302", - "name": "娄星区" - }, - { - "code": "431321", - "name": "双峰县" - }, - { - "code": "431322", - "name": "新化县" - }, - { - "code": "431381", - "name": "冷水江市" - }, - { - "code": "431382", - "name": "涟源市" - } - ] - }, - { - "code": "4331", - "name": "湘西土家族苗族自治州", - "children": [ - { - "code": "433101", - "name": "吉首市" - }, - { - "code": "433122", - "name": "泸溪县" - }, - { - "code": "433123", - "name": "凤凰县" - }, - { - "code": "433124", - "name": "花垣县" - }, - { - "code": "433125", - "name": "保靖县" - }, - { - "code": "433126", - "name": "古丈县" - }, - { - "code": "433127", - "name": "永顺县" - }, - { - "code": "433130", - "name": "龙山县" - } - ] - } - ] - }, - { - "code": "44", - "name": "广东省", - "children": [ - { - "code": "4401", - "name": "广州市", - "children": [ - { - "code": "440103", - "name": "荔湾区" - }, - { - "code": "440104", - "name": "越秀区" - }, - { - "code": "440105", - "name": "海珠区" - }, - { - "code": "440106", - "name": "天河区" - }, - { - "code": "440111", - "name": "白云区" - }, - { - "code": "440112", - "name": "黄埔区" - }, - { - "code": "440113", - "name": "番禺区" - }, - { - "code": "440114", - "name": "花都区" - }, - { - "code": "440115", - "name": "南沙区" - }, - { - "code": "440117", - "name": "从化区" - }, - { - "code": "440118", - "name": "增城区" - } - ] - }, - { - "code": "4402", - "name": "韶关市", - "children": [ - { - "code": "440203", - "name": "武江区" - }, - { - "code": "440204", - "name": "浈江区" - }, - { - "code": "440205", - "name": "曲江区" - }, - { - "code": "440222", - "name": "始兴县" - }, - { - "code": "440224", - "name": "仁化县" - }, - { - "code": "440229", - "name": "翁源县" - }, - { - "code": "440232", - "name": "乳源瑶族自治县" - }, - { - "code": "440233", - "name": "新丰县" - }, - { - "code": "440281", - "name": "乐昌市" - }, - { - "code": "440282", - "name": "南雄市" - } - ] - }, - { - "code": "4403", - "name": "深圳市", - "children": [ - { - "code": "440303", - "name": "罗湖区" - }, - { - "code": "440304", - "name": "福田区" - }, - { - "code": "440305", - "name": "南山区" - }, - { - "code": "440306", - "name": "宝安区" - }, - { - "code": "440307", - "name": "龙岗区" - }, - { - "code": "440308", - "name": "盐田区" - }, - { - "code": "440309", - "name": "龙华区" - }, - { - "code": "440310", - "name": "坪山区" - }, - { - "code": "440311", - "name": "光明区" - } - ] - }, - { - "code": "4404", - "name": "珠海市", - "children": [ - { - "code": "440402", - "name": "香洲区" - }, - { - "code": "440403", - "name": "斗门区" - }, - { - "code": "440404", - "name": "金湾区" - } - ] - }, - { - "code": "4405", - "name": "汕头市", - "children": [ - { - "code": "440507", - "name": "龙湖区" - }, - { - "code": "440511", - "name": "金平区" - }, - { - "code": "440512", - "name": "濠江区" - }, - { - "code": "440513", - "name": "潮阳区" - }, - { - "code": "440514", - "name": "潮南区" - }, - { - "code": "440515", - "name": "澄海区" - }, - { - "code": "440523", - "name": "南澳县" - } - ] - }, - { - "code": "4406", - "name": "佛山市", - "children": [ - { - "code": "440604", - "name": "禅城区" - }, - { - "code": "440605", - "name": "南海区" - }, - { - "code": "440606", - "name": "顺德区" - }, - { - "code": "440607", - "name": "三水区" - }, - { - "code": "440608", - "name": "高明区" - } - ] - }, - { - "code": "4407", - "name": "江门市", - "children": [ - { - "code": "440703", - "name": "蓬江区" - }, - { - "code": "440704", - "name": "江海区" - }, - { - "code": "440705", - "name": "新会区" - }, - { - "code": "440781", - "name": "台山市" - }, - { - "code": "440783", - "name": "开平市" - }, - { - "code": "440784", - "name": "鹤山市" - }, - { - "code": "440785", - "name": "恩平市" - } - ] - }, - { - "code": "4408", - "name": "湛江市", - "children": [ - { - "code": "440802", - "name": "赤坎区" - }, - { - "code": "440803", - "name": "霞山区" - }, - { - "code": "440804", - "name": "坡头区" - }, - { - "code": "440811", - "name": "麻章区" - }, - { - "code": "440823", - "name": "遂溪县" - }, - { - "code": "440825", - "name": "徐闻县" - }, - { - "code": "440881", - "name": "廉江市" - }, - { - "code": "440882", - "name": "雷州市" - }, - { - "code": "440883", - "name": "吴川市" - } - ] - }, - { - "code": "4409", - "name": "茂名市", - "children": [ - { - "code": "440902", - "name": "茂南区" - }, - { - "code": "440904", - "name": "电白区" - }, - { - "code": "440981", - "name": "高州市" - }, - { - "code": "440982", - "name": "化州市" - }, - { - "code": "440983", - "name": "信宜市" - } - ] - }, - { - "code": "4412", - "name": "肇庆市", - "children": [ - { - "code": "441202", - "name": "端州区" - }, - { - "code": "441203", - "name": "鼎湖区" - }, - { - "code": "441204", - "name": "高要区" - }, - { - "code": "441223", - "name": "广宁县" - }, - { - "code": "441224", - "name": "怀集县" - }, - { - "code": "441225", - "name": "封开县" - }, - { - "code": "441226", - "name": "德庆县" - }, - { - "code": "441284", - "name": "四会市" - } - ] - }, - { - "code": "4413", - "name": "惠州市", - "children": [ - { - "code": "441302", - "name": "惠城区" - }, - { - "code": "441303", - "name": "惠阳区" - }, - { - "code": "441322", - "name": "博罗县" - }, - { - "code": "441323", - "name": "惠东县" - }, - { - "code": "441324", - "name": "龙门县" - } - ] - }, - { - "code": "4414", - "name": "梅州市", - "children": [ - { - "code": "441402", - "name": "梅江区" - }, - { - "code": "441403", - "name": "梅县区" - }, - { - "code": "441422", - "name": "大埔县" - }, - { - "code": "441423", - "name": "丰顺县" - }, - { - "code": "441424", - "name": "五华县" - }, - { - "code": "441426", - "name": "平远县" - }, - { - "code": "441427", - "name": "蕉岭县" - }, - { - "code": "441481", - "name": "兴宁市" - } - ] - }, - { - "code": "4415", - "name": "汕尾市", - "children": [ - { - "code": "441502", - "name": "城区" - }, - { - "code": "441521", - "name": "海丰县" - }, - { - "code": "441523", - "name": "陆河县" - }, - { - "code": "441581", - "name": "陆丰市" - } - ] - }, - { - "code": "4416", - "name": "河源市", - "children": [ - { - "code": "441602", - "name": "源城区" - }, - { - "code": "441621", - "name": "紫金县" - }, - { - "code": "441622", - "name": "龙川县" - }, - { - "code": "441623", - "name": "连平县" - }, - { - "code": "441624", - "name": "和平县" - }, - { - "code": "441625", - "name": "东源县" - } - ] - }, - { - "code": "4417", - "name": "阳江市", - "children": [ - { - "code": "441702", - "name": "江城区" - }, - { - "code": "441704", - "name": "阳东区" - }, - { - "code": "441721", - "name": "阳西县" - }, - { - "code": "441781", - "name": "阳春市" - } - ] - }, - { - "code": "4418", - "name": "清远市", - "children": [ - { - "code": "441802", - "name": "清城区" - }, - { - "code": "441803", - "name": "清新区" - }, - { - "code": "441821", - "name": "佛冈县" - }, - { - "code": "441823", - "name": "阳山县" - }, - { - "code": "441825", - "name": "连山壮族瑶族自治县" - }, - { - "code": "441826", - "name": "连南瑶族自治县" - }, - { - "code": "441881", - "name": "英德市" - }, - { - "code": "441882", - "name": "连州市" - } - ] - }, - { - "code": "4419", - "name": "东莞市", - "children": [ - { - "code": "441900003", - "name": "东城街道" - }, - { - "code": "441900004", - "name": "南城街道" - }, - { - "code": "441900005", - "name": "万江街道" - }, - { - "code": "441900006", - "name": "莞城街道" - }, - { - "code": "441900101", - "name": "石碣镇" - }, - { - "code": "441900102", - "name": "石龙镇" - }, - { - "code": "441900103", - "name": "茶山镇" - }, - { - "code": "441900104", - "name": "石排镇" - }, - { - "code": "441900105", - "name": "企石镇" - }, - { - "code": "441900106", - "name": "横沥镇" - }, - { - "code": "441900107", - "name": "桥头镇" - }, - { - "code": "441900108", - "name": "谢岗镇" - }, - { - "code": "441900109", - "name": "东坑镇" - }, - { - "code": "441900110", - "name": "常平镇" - }, - { - "code": "441900111", - "name": "寮步镇" - }, - { - "code": "441900112", - "name": "樟木头镇" - }, - { - "code": "441900113", - "name": "大朗镇" - }, - { - "code": "441900114", - "name": "黄江镇" - }, - { - "code": "441900115", - "name": "清溪镇" - }, - { - "code": "441900116", - "name": "塘厦镇" - }, - { - "code": "441900117", - "name": "凤岗镇" - }, - { - "code": "441900118", - "name": "大岭山镇" - }, - { - "code": "441900119", - "name": "长安镇" - }, - { - "code": "441900121", - "name": "虎门镇" - }, - { - "code": "441900122", - "name": "厚街镇" - }, - { - "code": "441900123", - "name": "沙田镇" - }, - { - "code": "441900124", - "name": "道滘镇" - }, - { - "code": "441900125", - "name": "洪梅镇" - }, - { - "code": "441900126", - "name": "麻涌镇" - }, - { - "code": "441900127", - "name": "望牛墩镇" - }, - { - "code": "441900128", - "name": "中堂镇" - }, - { - "code": "441900129", - "name": "高埗镇" - }, - { - "code": "441900401", - "name": "松山湖" - }, - { - "code": "441900402", - "name": "东莞港" - }, - { - "code": "441900403", - "name": "东莞生态园" - }, - { - "code": "441900404", - "name": "东莞滨海湾新区" - } - ] - }, - { - "code": "4420", - "name": "中山市", - "children": [ - { - "code": "442000001", - "name": "石岐街道" - }, - { - "code": "442000002", - "name": "东区街道" - }, - { - "code": "442000003", - "name": "中山港街道" - }, - { - "code": "442000004", - "name": "西区街道" - }, - { - "code": "442000005", - "name": "南区街道" - }, - { - "code": "442000006", - "name": "五桂山街道" - }, - { - "code": "442000007", - "name": "民众街道" - }, - { - "code": "442000008", - "name": "南朗街道" - }, - { - "code": "442000101", - "name": "黄圃镇" - }, - { - "code": "442000103", - "name": "东凤镇" - }, - { - "code": "442000105", - "name": "古镇镇" - }, - { - "code": "442000106", - "name": "沙溪镇" - }, - { - "code": "442000107", - "name": "坦洲镇" - }, - { - "code": "442000108", - "name": "港口镇" - }, - { - "code": "442000109", - "name": "三角镇" - }, - { - "code": "442000110", - "name": "横栏镇" - }, - { - "code": "442000111", - "name": "南头镇" - }, - { - "code": "442000112", - "name": "阜沙镇" - }, - { - "code": "442000114", - "name": "三乡镇" - }, - { - "code": "442000115", - "name": "板芙镇" - }, - { - "code": "442000116", - "name": "大涌镇" - }, - { - "code": "442000117", - "name": "神湾镇" - }, - { - "code": "442000118", - "name": "小榄镇" - } - ] - }, - { - "code": "4451", - "name": "潮州市", - "children": [ - { - "code": "445102", - "name": "湘桥区" - }, - { - "code": "445103", - "name": "潮安区" - }, - { - "code": "445122", - "name": "饶平县" - } - ] - }, - { - "code": "4452", - "name": "揭阳市", - "children": [ - { - "code": "445202", - "name": "榕城区" - }, - { - "code": "445203", - "name": "揭东区" - }, - { - "code": "445222", - "name": "揭西县" - }, - { - "code": "445224", - "name": "惠来县" - }, - { - "code": "445281", - "name": "普宁市" - } - ] - }, - { - "code": "4453", - "name": "云浮市", - "children": [ - { - "code": "445302", - "name": "云城区" - }, - { - "code": "445303", - "name": "云安区" - }, - { - "code": "445321", - "name": "新兴县" - }, - { - "code": "445322", - "name": "郁南县" - }, - { - "code": "445381", - "name": "罗定市" - } - ] - } - ] - }, - { - "code": "45", - "name": "广西壮族自治区", - "children": [ - { - "code": "4501", - "name": "南宁市", - "children": [ - { - "code": "450102", - "name": "兴宁区" - }, - { - "code": "450103", - "name": "青秀区" - }, - { - "code": "450105", - "name": "江南区" - }, - { - "code": "450107", - "name": "西乡塘区" - }, - { - "code": "450108", - "name": "良庆区" - }, - { - "code": "450109", - "name": "邕宁区" - }, - { - "code": "450110", - "name": "武鸣区" - }, - { - "code": "450123", - "name": "隆安县" - }, - { - "code": "450124", - "name": "马山县" - }, - { - "code": "450125", - "name": "上林县" - }, - { - "code": "450126", - "name": "宾阳县" - }, - { - "code": "450181", - "name": "横州市" - } - ] - }, - { - "code": "4502", - "name": "柳州市", - "children": [ - { - "code": "450202", - "name": "城中区" - }, - { - "code": "450203", - "name": "鱼峰区" - }, - { - "code": "450204", - "name": "柳南区" - }, - { - "code": "450205", - "name": "柳北区" - }, - { - "code": "450206", - "name": "柳江区" - }, - { - "code": "450222", - "name": "柳城县" - }, - { - "code": "450223", - "name": "鹿寨县" - }, - { - "code": "450224", - "name": "融安县" - }, - { - "code": "450225", - "name": "融水苗族自治县" - }, - { - "code": "450226", - "name": "三江侗族自治县" - } - ] - }, - { - "code": "4503", - "name": "桂林市", - "children": [ - { - "code": "450302", - "name": "秀峰区" - }, - { - "code": "450303", - "name": "叠彩区" - }, - { - "code": "450304", - "name": "象山区" - }, - { - "code": "450305", - "name": "七星区" - }, - { - "code": "450311", - "name": "雁山区" - }, - { - "code": "450312", - "name": "临桂区" - }, - { - "code": "450321", - "name": "阳朔县" - }, - { - "code": "450323", - "name": "灵川县" - }, - { - "code": "450324", - "name": "全州县" - }, - { - "code": "450325", - "name": "兴安县" - }, - { - "code": "450326", - "name": "永福县" - }, - { - "code": "450327", - "name": "灌阳县" - }, - { - "code": "450328", - "name": "龙胜各族自治县" - }, - { - "code": "450329", - "name": "资源县" - }, - { - "code": "450330", - "name": "平乐县" - }, - { - "code": "450332", - "name": "恭城瑶族自治县" - }, - { - "code": "450381", - "name": "荔浦市" - } - ] - }, - { - "code": "4504", - "name": "梧州市", - "children": [ - { - "code": "450403", - "name": "万秀区" - }, - { - "code": "450405", - "name": "长洲区" - }, - { - "code": "450406", - "name": "龙圩区" - }, - { - "code": "450421", - "name": "苍梧县" - }, - { - "code": "450422", - "name": "藤县" - }, - { - "code": "450423", - "name": "蒙山县" - }, - { - "code": "450481", - "name": "岑溪市" - } - ] - }, - { - "code": "4505", - "name": "北海市", - "children": [ - { - "code": "450502", - "name": "海城区" - }, - { - "code": "450503", - "name": "银海区" - }, - { - "code": "450512", - "name": "铁山港区" - }, - { - "code": "450521", - "name": "合浦县" - } - ] - }, - { - "code": "4506", - "name": "防城港市", - "children": [ - { - "code": "450602", - "name": "港口区" - }, - { - "code": "450603", - "name": "防城区" - }, - { - "code": "450621", - "name": "上思县" - }, - { - "code": "450681", - "name": "东兴市" - } - ] - }, - { - "code": "4507", - "name": "钦州市", - "children": [ - { - "code": "450702", - "name": "钦南区" - }, - { - "code": "450703", - "name": "钦北区" - }, - { - "code": "450721", - "name": "灵山县" - }, - { - "code": "450722", - "name": "浦北县" - } - ] - }, - { - "code": "4508", - "name": "贵港市", - "children": [ - { - "code": "450802", - "name": "港北区" - }, - { - "code": "450803", - "name": "港南区" - }, - { - "code": "450804", - "name": "覃塘区" - }, - { - "code": "450821", - "name": "平南县" - }, - { - "code": "450881", - "name": "桂平市" - } - ] - }, - { - "code": "4509", - "name": "玉林市", - "children": [ - { - "code": "450902", - "name": "玉州区" - }, - { - "code": "450903", - "name": "福绵区" - }, - { - "code": "450921", - "name": "容县" - }, - { - "code": "450922", - "name": "陆川县" - }, - { - "code": "450923", - "name": "博白县" - }, - { - "code": "450924", - "name": "兴业县" - }, - { - "code": "450981", - "name": "北流市" - } - ] - }, - { - "code": "4510", - "name": "百色市", - "children": [ - { - "code": "451002", - "name": "右江区" - }, - { - "code": "451003", - "name": "田阳区" - }, - { - "code": "451022", - "name": "田东县" - }, - { - "code": "451024", - "name": "德保县" - }, - { - "code": "451026", - "name": "那坡县" - }, - { - "code": "451027", - "name": "凌云县" - }, - { - "code": "451028", - "name": "乐业县" - }, - { - "code": "451029", - "name": "田林县" - }, - { - "code": "451030", - "name": "西林县" - }, - { - "code": "451031", - "name": "隆林各族自治县" - }, - { - "code": "451081", - "name": "靖西市" - }, - { - "code": "451082", - "name": "平果市" - } - ] - }, - { - "code": "4511", - "name": "贺州市", - "children": [ - { - "code": "451102", - "name": "八步区" - }, - { - "code": "451103", - "name": "平桂区" - }, - { - "code": "451121", - "name": "昭平县" - }, - { - "code": "451122", - "name": "钟山县" - }, - { - "code": "451123", - "name": "富川瑶族自治县" - } - ] - }, - { - "code": "4512", - "name": "河池市", - "children": [ - { - "code": "451202", - "name": "金城江区" - }, - { - "code": "451203", - "name": "宜州区" - }, - { - "code": "451221", - "name": "南丹县" - }, - { - "code": "451222", - "name": "天峨县" - }, - { - "code": "451223", - "name": "凤山县" - }, - { - "code": "451224", - "name": "东兰县" - }, - { - "code": "451225", - "name": "罗城仫佬族自治县" - }, - { - "code": "451226", - "name": "环江毛南族自治县" - }, - { - "code": "451227", - "name": "巴马瑶族自治县" - }, - { - "code": "451228", - "name": "都安瑶族自治县" - }, - { - "code": "451229", - "name": "大化瑶族自治县" - } - ] - }, - { - "code": "4513", - "name": "来宾市", - "children": [ - { - "code": "451302", - "name": "兴宾区" - }, - { - "code": "451321", - "name": "忻城县" - }, - { - "code": "451322", - "name": "象州县" - }, - { - "code": "451323", - "name": "武宣县" - }, - { - "code": "451324", - "name": "金秀瑶族自治县" - }, - { - "code": "451381", - "name": "合山市" - } - ] - }, - { - "code": "4514", - "name": "崇左市", - "children": [ - { - "code": "451402", - "name": "江州区" - }, - { - "code": "451421", - "name": "扶绥县" - }, - { - "code": "451422", - "name": "宁明县" - }, - { - "code": "451423", - "name": "龙州县" - }, - { - "code": "451424", - "name": "大新县" - }, - { - "code": "451425", - "name": "天等县" - }, - { - "code": "451481", - "name": "凭祥市" - } - ] - } - ] - }, - { - "code": "46", - "name": "海南省", - "children": [ - { - "code": "4601", - "name": "海口市", - "children": [ - { - "code": "460105", - "name": "秀英区" - }, - { - "code": "460106", - "name": "龙华区" - }, - { - "code": "460107", - "name": "琼山区" - }, - { - "code": "460108", - "name": "美兰区" - } - ] - }, - { - "code": "4602", - "name": "三亚市", - "children": [ - { - "code": "460202", - "name": "海棠区" - }, - { - "code": "460203", - "name": "吉阳区" - }, - { - "code": "460204", - "name": "天涯区" - }, - { - "code": "460205", - "name": "崖州区" - } - ] - }, - { - "code": "4603", - "name": "三沙市", - "children": [ - { - "code": "460321", - "name": "西沙群岛" - }, - { - "code": "460322", - "name": "南沙群岛" - }, - { - "code": "460323", - "name": "中沙群岛的岛礁及其海域" - } - ] - }, - { - "code": "4604", - "name": "儋州市", - "children": [ - { - "code": "460400100", - "name": "那大镇" - }, - { - "code": "460400101", - "name": "和庆镇" - }, - { - "code": "460400102", - "name": "南丰镇" - }, - { - "code": "460400103", - "name": "大成镇" - }, - { - "code": "460400104", - "name": "雅星镇" - }, - { - "code": "460400105", - "name": "兰洋镇" - }, - { - "code": "460400106", - "name": "光村镇" - }, - { - "code": "460400107", - "name": "木棠镇" - }, - { - "code": "460400108", - "name": "海头镇" - }, - { - "code": "460400109", - "name": "峨蔓镇" - }, - { - "code": "460400111", - "name": "王五镇" - }, - { - "code": "460400112", - "name": "白马井镇" - }, - { - "code": "460400113", - "name": "中和镇" - }, - { - "code": "460400114", - "name": "排浦镇" - }, - { - "code": "460400115", - "name": "东成镇" - }, - { - "code": "460400116", - "name": "新州镇" - }, - { - "code": "460400499", - "name": "洋浦经济开发区" - }, - { - "code": "460400500", - "name": "华南热作学院" - } - ] - }, - { - "code": "4690", - "name": "省直辖县级行政区划", - "children": [ - { - "code": "469001", - "name": "五指山市" - }, - { - "code": "469002", - "name": "琼海市" - }, - { - "code": "469005", - "name": "文昌市" - }, - { - "code": "469006", - "name": "万宁市" - }, - { - "code": "469007", - "name": "东方市" - }, - { - "code": "469021", - "name": "定安县" - }, - { - "code": "469022", - "name": "屯昌县" - }, - { - "code": "469023", - "name": "澄迈县" - }, - { - "code": "469024", - "name": "临高县" - }, - { - "code": "469025", - "name": "白沙黎族自治县" - }, - { - "code": "469026", - "name": "昌江黎族自治县" - }, - { - "code": "469027", - "name": "乐东黎族自治县" - }, - { - "code": "469028", - "name": "陵水黎族自治县" - }, - { - "code": "469029", - "name": "保亭黎族苗族自治县" - }, - { - "code": "469030", - "name": "琼中黎族苗族自治县" - } - ] - } - ] - }, - { - "code": "50", - "name": "重庆市", - "children": [ - { - "code": "5001", - "name": "市辖区", - "children": [ - { - "code": "500101", - "name": "万州区" - }, - { - "code": "500102", - "name": "涪陵区" - }, - { - "code": "500103", - "name": "渝中区" - }, - { - "code": "500104", - "name": "大渡口区" - }, - { - "code": "500105", - "name": "江北区" - }, - { - "code": "500106", - "name": "沙坪坝区" - }, - { - "code": "500107", - "name": "九龙坡区" - }, - { - "code": "500108", - "name": "南岸区" - }, - { - "code": "500109", - "name": "北碚区" - }, - { - "code": "500110", - "name": "綦江区" - }, - { - "code": "500111", - "name": "大足区" - }, - { - "code": "500112", - "name": "渝北区" - }, - { - "code": "500113", - "name": "巴南区" - }, - { - "code": "500114", - "name": "黔江区" - }, - { - "code": "500115", - "name": "长寿区" - }, - { - "code": "500116", - "name": "江津区" - }, - { - "code": "500117", - "name": "合川区" - }, - { - "code": "500118", - "name": "永川区" - }, - { - "code": "500119", - "name": "南川区" - }, - { - "code": "500120", - "name": "璧山区" - }, - { - "code": "500151", - "name": "铜梁区" - }, - { - "code": "500152", - "name": "潼南区" - }, - { - "code": "500153", - "name": "荣昌区" - }, - { - "code": "500154", - "name": "开州区" - }, - { - "code": "500155", - "name": "梁平区" - }, - { - "code": "500156", - "name": "武隆区" - } - ] - }, - { - "code": "5002", - "name": "县", - "children": [ - { - "code": "500229", - "name": "城口县" - }, - { - "code": "500230", - "name": "丰都县" - }, - { - "code": "500231", - "name": "垫江县" - }, - { - "code": "500233", - "name": "忠县" - }, - { - "code": "500235", - "name": "云阳县" - }, - { - "code": "500236", - "name": "奉节县" - }, - { - "code": "500237", - "name": "巫山县" - }, - { - "code": "500238", - "name": "巫溪县" - }, - { - "code": "500240", - "name": "石柱土家族自治县" - }, - { - "code": "500241", - "name": "秀山土家族苗族自治县" - }, - { - "code": "500242", - "name": "酉阳土家族苗族自治县" - }, - { - "code": "500243", - "name": "彭水苗族土家族自治县" - } - ] - } - ] - }, - { - "code": "51", - "name": "四川省", - "children": [ - { - "code": "5101", - "name": "成都市", - "children": [ - { - "code": "510104", - "name": "锦江区" - }, - { - "code": "510105", - "name": "青羊区" - }, - { - "code": "510106", - "name": "金牛区" - }, - { - "code": "510107", - "name": "武侯区" - }, - { - "code": "510108", - "name": "成华区" - }, - { - "code": "510112", - "name": "龙泉驿区" - }, - { - "code": "510113", - "name": "青白江区" - }, - { - "code": "510114", - "name": "新都区" - }, - { - "code": "510115", - "name": "温江区" - }, - { - "code": "510116", - "name": "双流区" - }, - { - "code": "510117", - "name": "郫都区" - }, - { - "code": "510118", - "name": "新津区" - }, - { - "code": "510121", - "name": "金堂县" - }, - { - "code": "510129", - "name": "大邑县" - }, - { - "code": "510131", - "name": "蒲江县" - }, - { - "code": "510181", - "name": "都江堰市" - }, - { - "code": "510182", - "name": "彭州市" - }, - { - "code": "510183", - "name": "邛崃市" - }, - { - "code": "510184", - "name": "崇州市" - }, - { - "code": "510185", - "name": "简阳市" - } - ] - }, - { - "code": "5103", - "name": "自贡市", - "children": [ - { - "code": "510302", - "name": "自流井区" - }, - { - "code": "510303", - "name": "贡井区" - }, - { - "code": "510304", - "name": "大安区" - }, - { - "code": "510311", - "name": "沿滩区" - }, - { - "code": "510321", - "name": "荣县" - }, - { - "code": "510322", - "name": "富顺县" - } - ] - }, - { - "code": "5104", - "name": "攀枝花市", - "children": [ - { - "code": "510402", - "name": "东区" - }, - { - "code": "510403", - "name": "西区" - }, - { - "code": "510411", - "name": "仁和区" - }, - { - "code": "510421", - "name": "米易县" - }, - { - "code": "510422", - "name": "盐边县" - } - ] - }, - { - "code": "5105", - "name": "泸州市", - "children": [ - { - "code": "510502", - "name": "江阳区" - }, - { - "code": "510503", - "name": "纳溪区" - }, - { - "code": "510504", - "name": "龙马潭区" - }, - { - "code": "510521", - "name": "泸县" - }, - { - "code": "510522", - "name": "合江县" - }, - { - "code": "510524", - "name": "叙永县" - }, - { - "code": "510525", - "name": "古蔺县" - } - ] - }, - { - "code": "5106", - "name": "德阳市", - "children": [ - { - "code": "510603", - "name": "旌阳区" - }, - { - "code": "510604", - "name": "罗江区" - }, - { - "code": "510623", - "name": "中江县" - }, - { - "code": "510681", - "name": "广汉市" - }, - { - "code": "510682", - "name": "什邡市" - }, - { - "code": "510683", - "name": "绵竹市" - } - ] - }, - { - "code": "5107", - "name": "绵阳市", - "children": [ - { - "code": "510703", - "name": "涪城区" - }, - { - "code": "510704", - "name": "游仙区" - }, - { - "code": "510705", - "name": "安州区" - }, - { - "code": "510722", - "name": "三台县" - }, - { - "code": "510723", - "name": "盐亭县" - }, - { - "code": "510725", - "name": "梓潼县" - }, - { - "code": "510726", - "name": "北川羌族自治县" - }, - { - "code": "510727", - "name": "平武县" - }, - { - "code": "510781", - "name": "江油市" - } - ] - }, - { - "code": "5108", - "name": "广元市", - "children": [ - { - "code": "510802", - "name": "利州区" - }, - { - "code": "510811", - "name": "昭化区" - }, - { - "code": "510812", - "name": "朝天区" - }, - { - "code": "510821", - "name": "旺苍县" - }, - { - "code": "510822", - "name": "青川县" - }, - { - "code": "510823", - "name": "剑阁县" - }, - { - "code": "510824", - "name": "苍溪县" - } - ] - }, - { - "code": "5109", - "name": "遂宁市", - "children": [ - { - "code": "510903", - "name": "船山区" - }, - { - "code": "510904", - "name": "安居区" - }, - { - "code": "510921", - "name": "蓬溪县" - }, - { - "code": "510923", - "name": "大英县" - }, - { - "code": "510981", - "name": "射洪市" - } - ] - }, - { - "code": "5110", - "name": "内江市", - "children": [ - { - "code": "511002", - "name": "市中区" - }, - { - "code": "511011", - "name": "东兴区" - }, - { - "code": "511024", - "name": "威远县" - }, - { - "code": "511025", - "name": "资中县" - }, - { - "code": "511083", - "name": "隆昌市" - } - ] - }, - { - "code": "5111", - "name": "乐山市", - "children": [ - { - "code": "511102", - "name": "市中区" - }, - { - "code": "511111", - "name": "沙湾区" - }, - { - "code": "511112", - "name": "五通桥区" - }, - { - "code": "511113", - "name": "金口河区" - }, - { - "code": "511123", - "name": "犍为县" - }, - { - "code": "511124", - "name": "井研县" - }, - { - "code": "511126", - "name": "夹江县" - }, - { - "code": "511129", - "name": "沐川县" - }, - { - "code": "511132", - "name": "峨边彝族自治县" - }, - { - "code": "511133", - "name": "马边彝族自治县" - }, - { - "code": "511181", - "name": "峨眉山市" - } - ] - }, - { - "code": "5113", - "name": "南充市", - "children": [ - { - "code": "511302", - "name": "顺庆区" - }, - { - "code": "511303", - "name": "高坪区" - }, - { - "code": "511304", - "name": "嘉陵区" - }, - { - "code": "511321", - "name": "南部县" - }, - { - "code": "511322", - "name": "营山县" - }, - { - "code": "511323", - "name": "蓬安县" - }, - { - "code": "511324", - "name": "仪陇县" - }, - { - "code": "511325", - "name": "西充县" - }, - { - "code": "511381", - "name": "阆中市" - } - ] - }, - { - "code": "5114", - "name": "眉山市", - "children": [ - { - "code": "511402", - "name": "东坡区" - }, - { - "code": "511403", - "name": "彭山区" - }, - { - "code": "511421", - "name": "仁寿县" - }, - { - "code": "511423", - "name": "洪雅县" - }, - { - "code": "511424", - "name": "丹棱县" - }, - { - "code": "511425", - "name": "青神县" - } - ] - }, - { - "code": "5115", - "name": "宜宾市", - "children": [ - { - "code": "511502", - "name": "翠屏区" - }, - { - "code": "511503", - "name": "南溪区" - }, - { - "code": "511504", - "name": "叙州区" - }, - { - "code": "511523", - "name": "江安县" - }, - { - "code": "511524", - "name": "长宁县" - }, - { - "code": "511525", - "name": "高县" - }, - { - "code": "511526", - "name": "珙县" - }, - { - "code": "511527", - "name": "筠连县" - }, - { - "code": "511528", - "name": "兴文县" - }, - { - "code": "511529", - "name": "屏山县" - } - ] - }, - { - "code": "5116", - "name": "广安市", - "children": [ - { - "code": "511602", - "name": "广安区" - }, - { - "code": "511603", - "name": "前锋区" - }, - { - "code": "511621", - "name": "岳池县" - }, - { - "code": "511622", - "name": "武胜县" - }, - { - "code": "511623", - "name": "邻水县" - }, - { - "code": "511681", - "name": "华蓥市" - } - ] - }, - { - "code": "5117", - "name": "达州市", - "children": [ - { - "code": "511702", - "name": "通川区" - }, - { - "code": "511703", - "name": "达川区" - }, - { - "code": "511722", - "name": "宣汉县" - }, - { - "code": "511723", - "name": "开江县" - }, - { - "code": "511724", - "name": "大竹县" - }, - { - "code": "511725", - "name": "渠县" - }, - { - "code": "511781", - "name": "万源市" - } - ] - }, - { - "code": "5118", - "name": "雅安市", - "children": [ - { - "code": "511802", - "name": "雨城区" - }, - { - "code": "511803", - "name": "名山区" - }, - { - "code": "511822", - "name": "荥经县" - }, - { - "code": "511823", - "name": "汉源县" - }, - { - "code": "511824", - "name": "石棉县" - }, - { - "code": "511825", - "name": "天全县" - }, - { - "code": "511826", - "name": "芦山县" - }, - { - "code": "511827", - "name": "宝兴县" - } - ] - }, - { - "code": "5119", - "name": "巴中市", - "children": [ - { - "code": "511902", - "name": "巴州区" - }, - { - "code": "511903", - "name": "恩阳区" - }, - { - "code": "511921", - "name": "通江县" - }, - { - "code": "511922", - "name": "南江县" - }, - { - "code": "511923", - "name": "平昌县" - } - ] - }, - { - "code": "5120", - "name": "资阳市", - "children": [ - { - "code": "512002", - "name": "雁江区" - }, - { - "code": "512021", - "name": "安岳县" - }, - { - "code": "512022", - "name": "乐至县" - } - ] - }, - { - "code": "5132", - "name": "阿坝藏族羌族自治州", - "children": [ - { - "code": "513201", - "name": "马尔康市" - }, - { - "code": "513221", - "name": "汶川县" - }, - { - "code": "513222", - "name": "理县" - }, - { - "code": "513223", - "name": "茂县" - }, - { - "code": "513224", - "name": "松潘县" - }, - { - "code": "513225", - "name": "九寨沟县" - }, - { - "code": "513226", - "name": "金川县" - }, - { - "code": "513227", - "name": "小金县" - }, - { - "code": "513228", - "name": "黑水县" - }, - { - "code": "513230", - "name": "壤塘县" - }, - { - "code": "513231", - "name": "阿坝县" - }, - { - "code": "513232", - "name": "若尔盖县" - }, - { - "code": "513233", - "name": "红原县" - } - ] - }, - { - "code": "5133", - "name": "甘孜藏族自治州", - "children": [ - { - "code": "513301", - "name": "康定市" - }, - { - "code": "513322", - "name": "泸定县" - }, - { - "code": "513323", - "name": "丹巴县" - }, - { - "code": "513324", - "name": "九龙县" - }, - { - "code": "513325", - "name": "雅江县" - }, - { - "code": "513326", - "name": "道孚县" - }, - { - "code": "513327", - "name": "炉霍县" - }, - { - "code": "513328", - "name": "甘孜县" - }, - { - "code": "513329", - "name": "新龙县" - }, - { - "code": "513330", - "name": "德格县" - }, - { - "code": "513331", - "name": "白玉县" - }, - { - "code": "513332", - "name": "石渠县" - }, - { - "code": "513333", - "name": "色达县" - }, - { - "code": "513334", - "name": "理塘县" - }, - { - "code": "513335", - "name": "巴塘县" - }, - { - "code": "513336", - "name": "乡城县" - }, - { - "code": "513337", - "name": "稻城县" - }, - { - "code": "513338", - "name": "得荣县" - } - ] - }, - { - "code": "5134", - "name": "凉山彝族自治州", - "children": [ - { - "code": "513401", - "name": "西昌市" - }, - { - "code": "513402", - "name": "会理市" - }, - { - "code": "513422", - "name": "木里藏族自治县" - }, - { - "code": "513423", - "name": "盐源县" - }, - { - "code": "513424", - "name": "德昌县" - }, - { - "code": "513426", - "name": "会东县" - }, - { - "code": "513427", - "name": "宁南县" - }, - { - "code": "513428", - "name": "普格县" - }, - { - "code": "513429", - "name": "布拖县" - }, - { - "code": "513430", - "name": "金阳县" - }, - { - "code": "513431", - "name": "昭觉县" - }, - { - "code": "513432", - "name": "喜德县" - }, - { - "code": "513433", - "name": "冕宁县" - }, - { - "code": "513434", - "name": "越西县" - }, - { - "code": "513435", - "name": "甘洛县" - }, - { - "code": "513436", - "name": "美姑县" - }, - { - "code": "513437", - "name": "雷波县" - } - ] - } - ] - }, - { - "code": "52", - "name": "贵州省", - "children": [ - { - "code": "5201", - "name": "贵阳市", - "children": [ - { - "code": "520102", - "name": "南明区" - }, - { - "code": "520103", - "name": "云岩区" - }, - { - "code": "520111", - "name": "花溪区" - }, - { - "code": "520112", - "name": "乌当区" - }, - { - "code": "520113", - "name": "白云区" - }, - { - "code": "520115", - "name": "观山湖区" - }, - { - "code": "520121", - "name": "开阳县" - }, - { - "code": "520122", - "name": "息烽县" - }, - { - "code": "520123", - "name": "修文县" - }, - { - "code": "520181", - "name": "清镇市" - } - ] - }, - { - "code": "5202", - "name": "六盘水市", - "children": [ - { - "code": "520201", - "name": "钟山区" - }, - { - "code": "520203", - "name": "六枝特区" - }, - { - "code": "520204", - "name": "水城区" - }, - { - "code": "520281", - "name": "盘州市" - } - ] - }, - { - "code": "5203", - "name": "遵义市", - "children": [ - { - "code": "520302", - "name": "红花岗区" - }, - { - "code": "520303", - "name": "汇川区" - }, - { - "code": "520304", - "name": "播州区" - }, - { - "code": "520322", - "name": "桐梓县" - }, - { - "code": "520323", - "name": "绥阳县" - }, - { - "code": "520324", - "name": "正安县" - }, - { - "code": "520325", - "name": "道真仡佬族苗族自治县" - }, - { - "code": "520326", - "name": "务川仡佬族苗族自治县" - }, - { - "code": "520327", - "name": "凤冈县" - }, - { - "code": "520328", - "name": "湄潭县" - }, - { - "code": "520329", - "name": "余庆县" - }, - { - "code": "520330", - "name": "习水县" - }, - { - "code": "520381", - "name": "赤水市" - }, - { - "code": "520382", - "name": "仁怀市" - } - ] - }, - { - "code": "5204", - "name": "安顺市", - "children": [ - { - "code": "520402", - "name": "西秀区" - }, - { - "code": "520403", - "name": "平坝区" - }, - { - "code": "520422", - "name": "普定县" - }, - { - "code": "520423", - "name": "镇宁布依族苗族自治县" - }, - { - "code": "520424", - "name": "关岭布依族苗族自治县" - }, - { - "code": "520425", - "name": "紫云苗族布依族自治县" - } - ] - }, - { - "code": "5205", - "name": "毕节市", - "children": [ - { - "code": "520502", - "name": "七星关区" - }, - { - "code": "520521", - "name": "大方县" - }, - { - "code": "520523", - "name": "金沙县" - }, - { - "code": "520524", - "name": "织金县" - }, - { - "code": "520525", - "name": "纳雍县" - }, - { - "code": "520526", - "name": "威宁彝族回族苗族自治县" - }, - { - "code": "520527", - "name": "赫章县" - }, - { - "code": "520581", - "name": "黔西市" - } - ] - }, - { - "code": "5206", - "name": "铜仁市", - "children": [ - { - "code": "520602", - "name": "碧江区" - }, - { - "code": "520603", - "name": "万山区" - }, - { - "code": "520621", - "name": "江口县" - }, - { - "code": "520622", - "name": "玉屏侗族自治县" - }, - { - "code": "520623", - "name": "石阡县" - }, - { - "code": "520624", - "name": "思南县" - }, - { - "code": "520625", - "name": "印江土家族苗族自治县" - }, - { - "code": "520626", - "name": "德江县" - }, - { - "code": "520627", - "name": "沿河土家族自治县" - }, - { - "code": "520628", - "name": "松桃苗族自治县" - } - ] - }, - { - "code": "5223", - "name": "黔西南布依族苗族自治州", - "children": [ - { - "code": "522301", - "name": "兴义市" - }, - { - "code": "522302", - "name": "兴仁市" - }, - { - "code": "522323", - "name": "普安县" - }, - { - "code": "522324", - "name": "晴隆县" - }, - { - "code": "522325", - "name": "贞丰县" - }, - { - "code": "522326", - "name": "望谟县" - }, - { - "code": "522327", - "name": "册亨县" - }, - { - "code": "522328", - "name": "安龙县" - } - ] - }, - { - "code": "5226", - "name": "黔东南苗族侗族自治州", - "children": [ - { - "code": "522601", - "name": "凯里市" - }, - { - "code": "522622", - "name": "黄平县" - }, - { - "code": "522623", - "name": "施秉县" - }, - { - "code": "522624", - "name": "三穗县" - }, - { - "code": "522625", - "name": "镇远县" - }, - { - "code": "522626", - "name": "岑巩县" - }, - { - "code": "522627", - "name": "天柱县" - }, - { - "code": "522628", - "name": "锦屏县" - }, - { - "code": "522629", - "name": "剑河县" - }, - { - "code": "522630", - "name": "台江县" - }, - { - "code": "522631", - "name": "黎平县" - }, - { - "code": "522632", - "name": "榕江县" - }, - { - "code": "522633", - "name": "从江县" - }, - { - "code": "522634", - "name": "雷山县" - }, - { - "code": "522635", - "name": "麻江县" - }, - { - "code": "522636", - "name": "丹寨县" - } - ] - }, - { - "code": "5227", - "name": "黔南布依族苗族自治州", - "children": [ - { - "code": "522701", - "name": "都匀市" - }, - { - "code": "522702", - "name": "福泉市" - }, - { - "code": "522722", - "name": "荔波县" - }, - { - "code": "522723", - "name": "贵定县" - }, - { - "code": "522725", - "name": "瓮安县" - }, - { - "code": "522726", - "name": "独山县" - }, - { - "code": "522727", - "name": "平塘县" - }, - { - "code": "522728", - "name": "罗甸县" - }, - { - "code": "522729", - "name": "长顺县" - }, - { - "code": "522730", - "name": "龙里县" - }, - { - "code": "522731", - "name": "惠水县" - }, - { - "code": "522732", - "name": "三都水族自治县" - } - ] - } - ] - }, - { - "code": "53", - "name": "云南省", - "children": [ - { - "code": "5301", - "name": "昆明市", - "children": [ - { - "code": "530102", - "name": "五华区" - }, - { - "code": "530103", - "name": "盘龙区" - }, - { - "code": "530111", - "name": "官渡区" - }, - { - "code": "530112", - "name": "西山区" - }, - { - "code": "530113", - "name": "东川区" - }, - { - "code": "530114", - "name": "呈贡区" - }, - { - "code": "530115", - "name": "晋宁区" - }, - { - "code": "530124", - "name": "富民县" - }, - { - "code": "530125", - "name": "宜良县" - }, - { - "code": "530126", - "name": "石林彝族自治县" - }, - { - "code": "530127", - "name": "嵩明县" - }, - { - "code": "530128", - "name": "禄劝彝族苗族自治县" - }, - { - "code": "530129", - "name": "寻甸回族彝族自治县" - }, - { - "code": "530181", - "name": "安宁市" - } - ] - }, - { - "code": "5303", - "name": "曲靖市", - "children": [ - { - "code": "530302", - "name": "麒麟区" - }, - { - "code": "530303", - "name": "沾益区" - }, - { - "code": "530304", - "name": "马龙区" - }, - { - "code": "530322", - "name": "陆良县" - }, - { - "code": "530323", - "name": "师宗县" - }, - { - "code": "530324", - "name": "罗平县" - }, - { - "code": "530325", - "name": "富源县" - }, - { - "code": "530326", - "name": "会泽县" - }, - { - "code": "530381", - "name": "宣威市" - } - ] - }, - { - "code": "5304", - "name": "玉溪市", - "children": [ - { - "code": "530402", - "name": "红塔区" - }, - { - "code": "530403", - "name": "江川区" - }, - { - "code": "530423", - "name": "通海县" - }, - { - "code": "530424", - "name": "华宁县" - }, - { - "code": "530425", - "name": "易门县" - }, - { - "code": "530426", - "name": "峨山彝族自治县" - }, - { - "code": "530427", - "name": "新平彝族傣族自治县" - }, - { - "code": "530428", - "name": "元江哈尼族彝族傣族自治县" - }, - { - "code": "530481", - "name": "澄江市" - } - ] - }, - { - "code": "5305", - "name": "保山市", - "children": [ - { - "code": "530502", - "name": "隆阳区" - }, - { - "code": "530521", - "name": "施甸县" - }, - { - "code": "530523", - "name": "龙陵县" - }, - { - "code": "530524", - "name": "昌宁县" - }, - { - "code": "530581", - "name": "腾冲市" - } - ] - }, - { - "code": "5306", - "name": "昭通市", - "children": [ - { - "code": "530602", - "name": "昭阳区" - }, - { - "code": "530621", - "name": "鲁甸县" - }, - { - "code": "530622", - "name": "巧家县" - }, - { - "code": "530623", - "name": "盐津县" - }, - { - "code": "530624", - "name": "大关县" - }, - { - "code": "530625", - "name": "永善县" - }, - { - "code": "530626", - "name": "绥江县" - }, - { - "code": "530627", - "name": "镇雄县" - }, - { - "code": "530628", - "name": "彝良县" - }, - { - "code": "530629", - "name": "威信县" - }, - { - "code": "530681", - "name": "水富市" - } - ] - }, - { - "code": "5307", - "name": "丽江市", - "children": [ - { - "code": "530702", - "name": "古城区" - }, - { - "code": "530721", - "name": "玉龙纳西族自治县" - }, - { - "code": "530722", - "name": "永胜县" - }, - { - "code": "530723", - "name": "华坪县" - }, - { - "code": "530724", - "name": "宁蒗彝族自治县" - } - ] - }, - { - "code": "5308", - "name": "普洱市", - "children": [ - { - "code": "530802", - "name": "思茅区" - }, - { - "code": "530821", - "name": "宁洱哈尼族彝族自治县" - }, - { - "code": "530822", - "name": "墨江哈尼族自治县" - }, - { - "code": "530823", - "name": "景东彝族自治县" - }, - { - "code": "530824", - "name": "景谷傣族彝族自治县" - }, - { - "code": "530825", - "name": "镇沅彝族哈尼族拉祜族自治县" - }, - { - "code": "530826", - "name": "江城哈尼族彝族自治县" - }, - { - "code": "530827", - "name": "孟连傣族拉祜族佤族自治县" - }, - { - "code": "530828", - "name": "澜沧拉祜族自治县" - }, - { - "code": "530829", - "name": "西盟佤族自治县" - } - ] - }, - { - "code": "5309", - "name": "临沧市", - "children": [ - { - "code": "530902", - "name": "临翔区" - }, - { - "code": "530921", - "name": "凤庆县" - }, - { - "code": "530922", - "name": "云县" - }, - { - "code": "530923", - "name": "永德县" - }, - { - "code": "530924", - "name": "镇康县" - }, - { - "code": "530925", - "name": "双江拉祜族佤族布朗族傣族自治县" - }, - { - "code": "530926", - "name": "耿马傣族佤族自治县" - }, - { - "code": "530927", - "name": "沧源佤族自治县" - } - ] - }, - { - "code": "5323", - "name": "楚雄彝族自治州", - "children": [ - { - "code": "532301", - "name": "楚雄市" - }, - { - "code": "532302", - "name": "禄丰市" - }, - { - "code": "532322", - "name": "双柏县" - }, - { - "code": "532323", - "name": "牟定县" - }, - { - "code": "532324", - "name": "南华县" - }, - { - "code": "532325", - "name": "姚安县" - }, - { - "code": "532326", - "name": "大姚县" - }, - { - "code": "532327", - "name": "永仁县" - }, - { - "code": "532328", - "name": "元谋县" - }, - { - "code": "532329", - "name": "武定县" - } - ] - }, - { - "code": "5325", - "name": "红河哈尼族彝族自治州", - "children": [ - { - "code": "532501", - "name": "个旧市" - }, - { - "code": "532502", - "name": "开远市" - }, - { - "code": "532503", - "name": "蒙自市" - }, - { - "code": "532504", - "name": "弥勒市" - }, - { - "code": "532523", - "name": "屏边苗族自治县" - }, - { - "code": "532524", - "name": "建水县" - }, - { - "code": "532525", - "name": "石屏县" - }, - { - "code": "532527", - "name": "泸西县" - }, - { - "code": "532528", - "name": "元阳县" - }, - { - "code": "532529", - "name": "红河县" - }, - { - "code": "532530", - "name": "金平苗族瑶族傣族自治县" - }, - { - "code": "532531", - "name": "绿春县" - }, - { - "code": "532532", - "name": "河口瑶族自治县" - } - ] - }, - { - "code": "5326", - "name": "文山壮族苗族自治州", - "children": [ - { - "code": "532601", - "name": "文山市" - }, - { - "code": "532622", - "name": "砚山县" - }, - { - "code": "532623", - "name": "西畴县" - }, - { - "code": "532624", - "name": "麻栗坡县" - }, - { - "code": "532625", - "name": "马关县" - }, - { - "code": "532626", - "name": "丘北县" - }, - { - "code": "532627", - "name": "广南县" - }, - { - "code": "532628", - "name": "富宁县" - } - ] - }, - { - "code": "5328", - "name": "西双版纳傣族自治州", - "children": [ - { - "code": "532801", - "name": "景洪市" - }, - { - "code": "532822", - "name": "勐海县" - }, - { - "code": "532823", - "name": "勐腊县" - } - ] - }, - { - "code": "5329", - "name": "大理白族自治州", - "children": [ - { - "code": "532901", - "name": "大理市" - }, - { - "code": "532922", - "name": "漾濞彝族自治县" - }, - { - "code": "532923", - "name": "祥云县" - }, - { - "code": "532924", - "name": "宾川县" - }, - { - "code": "532925", - "name": "弥渡县" - }, - { - "code": "532926", - "name": "南涧彝族自治县" - }, - { - "code": "532927", - "name": "巍山彝族回族自治县" - }, - { - "code": "532928", - "name": "永平县" - }, - { - "code": "532929", - "name": "云龙县" - }, - { - "code": "532930", - "name": "洱源县" - }, - { - "code": "532931", - "name": "剑川县" - }, - { - "code": "532932", - "name": "鹤庆县" - } - ] - }, - { - "code": "5331", - "name": "德宏傣族景颇族自治州", - "children": [ - { - "code": "533102", - "name": "瑞丽市" - }, - { - "code": "533103", - "name": "芒市" - }, - { - "code": "533122", - "name": "梁河县" - }, - { - "code": "533123", - "name": "盈江县" - }, - { - "code": "533124", - "name": "陇川县" - } - ] - }, - { - "code": "5333", - "name": "怒江傈僳族自治州", - "children": [ - { - "code": "533301", - "name": "泸水市" - }, - { - "code": "533323", - "name": "福贡县" - }, - { - "code": "533324", - "name": "贡山独龙族怒族自治县" - }, - { - "code": "533325", - "name": "兰坪白族普米族自治县" - } - ] - }, - { - "code": "5334", - "name": "迪庆藏族自治州", - "children": [ - { - "code": "533401", - "name": "香格里拉市" - }, - { - "code": "533422", - "name": "德钦县" - }, - { - "code": "533423", - "name": "维西傈僳族自治县" - } - ] - } - ] - }, - { - "code": "54", - "name": "西藏自治区", - "children": [ - { - "code": "5401", - "name": "拉萨市", - "children": [ - { - "code": "540102", - "name": "城关区" - }, - { - "code": "540103", - "name": "堆龙德庆区" - }, - { - "code": "540104", - "name": "达孜区" - }, - { - "code": "540121", - "name": "林周县" - }, - { - "code": "540122", - "name": "当雄县" - }, - { - "code": "540123", - "name": "尼木县" - }, - { - "code": "540124", - "name": "曲水县" - }, - { - "code": "540127", - "name": "墨竹工卡县" - }, - { - "code": "540171", - "name": "格尔木藏青工业园区" - }, - { - "code": "540172", - "name": "拉萨经济技术开发区" - }, - { - "code": "540173", - "name": "西藏文化旅游创意园区" - }, - { - "code": "540174", - "name": "达孜工业园区" - } - ] - }, - { - "code": "5402", - "name": "日喀则市", - "children": [ - { - "code": "540202", - "name": "桑珠孜区" - }, - { - "code": "540221", - "name": "南木林县" - }, - { - "code": "540222", - "name": "江孜县" - }, - { - "code": "540223", - "name": "定日县" - }, - { - "code": "540224", - "name": "萨迦县" - }, - { - "code": "540225", - "name": "拉孜县" - }, - { - "code": "540226", - "name": "昂仁县" - }, - { - "code": "540227", - "name": "谢通门县" - }, - { - "code": "540228", - "name": "白朗县" - }, - { - "code": "540229", - "name": "仁布县" - }, - { - "code": "540230", - "name": "康马县" - }, - { - "code": "540231", - "name": "定结县" - }, - { - "code": "540232", - "name": "仲巴县" - }, - { - "code": "540233", - "name": "亚东县" - }, - { - "code": "540234", - "name": "吉隆县" - }, - { - "code": "540235", - "name": "聂拉木县" - }, - { - "code": "540236", - "name": "萨嘎县" - }, - { - "code": "540237", - "name": "岗巴县" - } - ] - }, - { - "code": "5403", - "name": "昌都市", - "children": [ - { - "code": "540302", - "name": "卡若区" - }, - { - "code": "540321", - "name": "江达县" - }, - { - "code": "540322", - "name": "贡觉县" - }, - { - "code": "540323", - "name": "类乌齐县" - }, - { - "code": "540324", - "name": "丁青县" - }, - { - "code": "540325", - "name": "察雅县" - }, - { - "code": "540326", - "name": "八宿县" - }, - { - "code": "540327", - "name": "左贡县" - }, - { - "code": "540328", - "name": "芒康县" - }, - { - "code": "540329", - "name": "洛隆县" - }, - { - "code": "540330", - "name": "边坝县" - } - ] - }, - { - "code": "5404", - "name": "林芝市", - "children": [ - { - "code": "540402", - "name": "巴宜区" - }, - { - "code": "540421", - "name": "工布江达县" - }, - { - "code": "540423", - "name": "墨脱县" - }, - { - "code": "540424", - "name": "波密县" - }, - { - "code": "540425", - "name": "察隅县" - }, - { - "code": "540426", - "name": "朗县" - }, - { - "code": "540481", - "name": "米林市" - } - ] - }, - { - "code": "5405", - "name": "山南市", - "children": [ - { - "code": "540502", - "name": "乃东区" - }, - { - "code": "540521", - "name": "扎囊县" - }, - { - "code": "540522", - "name": "贡嘎县" - }, - { - "code": "540523", - "name": "桑日县" - }, - { - "code": "540524", - "name": "琼结县" - }, - { - "code": "540525", - "name": "曲松县" - }, - { - "code": "540526", - "name": "措美县" - }, - { - "code": "540527", - "name": "洛扎县" - }, - { - "code": "540528", - "name": "加查县" - }, - { - "code": "540529", - "name": "隆子县" - }, - { - "code": "540531", - "name": "浪卡子县" - }, - { - "code": "540581", - "name": "错那市" - } - ] - }, - { - "code": "5406", - "name": "那曲市", - "children": [ - { - "code": "540602", - "name": "色尼区" - }, - { - "code": "540621", - "name": "嘉黎县" - }, - { - "code": "540622", - "name": "比如县" - }, - { - "code": "540623", - "name": "聂荣县" - }, - { - "code": "540624", - "name": "安多县" - }, - { - "code": "540625", - "name": "申扎县" - }, - { - "code": "540626", - "name": "索县" - }, - { - "code": "540627", - "name": "班戈县" - }, - { - "code": "540628", - "name": "巴青县" - }, - { - "code": "540629", - "name": "尼玛县" - }, - { - "code": "540630", - "name": "双湖县" - } - ] - }, - { - "code": "5425", - "name": "阿里地区", - "children": [ - { - "code": "542521", - "name": "普兰县" - }, - { - "code": "542522", - "name": "札达县" - }, - { - "code": "542523", - "name": "噶尔县" - }, - { - "code": "542524", - "name": "日土县" - }, - { - "code": "542525", - "name": "革吉县" - }, - { - "code": "542526", - "name": "改则县" - }, - { - "code": "542527", - "name": "措勤县" - } - ] - } - ] - }, - { - "code": "61", - "name": "陕西省", - "children": [ - { - "code": "6101", - "name": "西安市", - "children": [ - { - "code": "610102", - "name": "新城区" - }, - { - "code": "610103", - "name": "碑林区" - }, - { - "code": "610104", - "name": "莲湖区" - }, - { - "code": "610111", - "name": "灞桥区" - }, - { - "code": "610112", - "name": "未央区" - }, - { - "code": "610113", - "name": "雁塔区" - }, - { - "code": "610114", - "name": "阎良区" - }, - { - "code": "610115", - "name": "临潼区" - }, - { - "code": "610116", - "name": "长安区" - }, - { - "code": "610117", - "name": "高陵区" - }, - { - "code": "610118", - "name": "鄠邑区" - }, - { - "code": "610122", - "name": "蓝田县" - }, - { - "code": "610124", - "name": "周至县" - } - ] - }, - { - "code": "6102", - "name": "铜川市", - "children": [ - { - "code": "610202", - "name": "王益区" - }, - { - "code": "610203", - "name": "印台区" - }, - { - "code": "610204", - "name": "耀州区" - }, - { - "code": "610222", - "name": "宜君县" - } - ] - }, - { - "code": "6103", - "name": "宝鸡市", - "children": [ - { - "code": "610302", - "name": "渭滨区" - }, - { - "code": "610303", - "name": "金台区" - }, - { - "code": "610304", - "name": "陈仓区" - }, - { - "code": "610305", - "name": "凤翔区" - }, - { - "code": "610323", - "name": "岐山县" - }, - { - "code": "610324", - "name": "扶风县" - }, - { - "code": "610326", - "name": "眉县" - }, - { - "code": "610327", - "name": "陇县" - }, - { - "code": "610328", - "name": "千阳县" - }, - { - "code": "610329", - "name": "麟游县" - }, - { - "code": "610330", - "name": "凤县" - }, - { - "code": "610331", - "name": "太白县" - } - ] - }, - { - "code": "6104", - "name": "咸阳市", - "children": [ - { - "code": "610402", - "name": "秦都区" - }, - { - "code": "610403", - "name": "杨陵区" - }, - { - "code": "610404", - "name": "渭城区" - }, - { - "code": "610422", - "name": "三原县" - }, - { - "code": "610423", - "name": "泾阳县" - }, - { - "code": "610424", - "name": "乾县" - }, - { - "code": "610425", - "name": "礼泉县" - }, - { - "code": "610426", - "name": "永寿县" - }, - { - "code": "610428", - "name": "长武县" - }, - { - "code": "610429", - "name": "旬邑县" - }, - { - "code": "610430", - "name": "淳化县" - }, - { - "code": "610431", - "name": "武功县" - }, - { - "code": "610481", - "name": "兴平市" - }, - { - "code": "610482", - "name": "彬州市" - } - ] - }, - { - "code": "6105", - "name": "渭南市", - "children": [ - { - "code": "610502", - "name": "临渭区" - }, - { - "code": "610503", - "name": "华州区" - }, - { - "code": "610522", - "name": "潼关县" - }, - { - "code": "610523", - "name": "大荔县" - }, - { - "code": "610524", - "name": "合阳县" - }, - { - "code": "610525", - "name": "澄城县" - }, - { - "code": "610526", - "name": "蒲城县" - }, - { - "code": "610527", - "name": "白水县" - }, - { - "code": "610528", - "name": "富平县" - }, - { - "code": "610581", - "name": "韩城市" - }, - { - "code": "610582", - "name": "华阴市" - } - ] - }, - { - "code": "6106", - "name": "延安市", - "children": [ - { - "code": "610602", - "name": "宝塔区" - }, - { - "code": "610603", - "name": "安塞区" - }, - { - "code": "610621", - "name": "延长县" - }, - { - "code": "610622", - "name": "延川县" - }, - { - "code": "610625", - "name": "志丹县" - }, - { - "code": "610626", - "name": "吴起县" - }, - { - "code": "610627", - "name": "甘泉县" - }, - { - "code": "610628", - "name": "富县" - }, - { - "code": "610629", - "name": "洛川县" - }, - { - "code": "610630", - "name": "宜川县" - }, - { - "code": "610631", - "name": "黄龙县" - }, - { - "code": "610632", - "name": "黄陵县" - }, - { - "code": "610681", - "name": "子长市" - } - ] - }, - { - "code": "6107", - "name": "汉中市", - "children": [ - { - "code": "610702", - "name": "汉台区" - }, - { - "code": "610703", - "name": "南郑区" - }, - { - "code": "610722", - "name": "城固县" - }, - { - "code": "610723", - "name": "洋县" - }, - { - "code": "610724", - "name": "西乡县" - }, - { - "code": "610725", - "name": "勉县" - }, - { - "code": "610726", - "name": "宁强县" - }, - { - "code": "610727", - "name": "略阳县" - }, - { - "code": "610728", - "name": "镇巴县" - }, - { - "code": "610729", - "name": "留坝县" - }, - { - "code": "610730", - "name": "佛坪县" - } - ] - }, - { - "code": "6108", - "name": "榆林市", - "children": [ - { - "code": "610802", - "name": "榆阳区" - }, - { - "code": "610803", - "name": "横山区" - }, - { - "code": "610822", - "name": "府谷县" - }, - { - "code": "610824", - "name": "靖边县" - }, - { - "code": "610825", - "name": "定边县" - }, - { - "code": "610826", - "name": "绥德县" - }, - { - "code": "610827", - "name": "米脂县" - }, - { - "code": "610828", - "name": "佳县" - }, - { - "code": "610829", - "name": "吴堡县" - }, - { - "code": "610830", - "name": "清涧县" - }, - { - "code": "610831", - "name": "子洲县" - }, - { - "code": "610881", - "name": "神木市" - } - ] - }, - { - "code": "6109", - "name": "安康市", - "children": [ - { - "code": "610902", - "name": "汉滨区" - }, - { - "code": "610921", - "name": "汉阴县" - }, - { - "code": "610922", - "name": "石泉县" - }, - { - "code": "610923", - "name": "宁陕县" - }, - { - "code": "610924", - "name": "紫阳县" - }, - { - "code": "610925", - "name": "岚皋县" - }, - { - "code": "610926", - "name": "平利县" - }, - { - "code": "610927", - "name": "镇坪县" - }, - { - "code": "610929", - "name": "白河县" - }, - { - "code": "610981", - "name": "旬阳市" - } - ] - }, - { - "code": "6110", - "name": "商洛市", - "children": [ - { - "code": "611002", - "name": "商州区" - }, - { - "code": "611021", - "name": "洛南县" - }, - { - "code": "611022", - "name": "丹凤县" - }, - { - "code": "611023", - "name": "商南县" - }, - { - "code": "611024", - "name": "山阳县" - }, - { - "code": "611025", - "name": "镇安县" - }, - { - "code": "611026", - "name": "柞水县" - } - ] - } - ] - }, - { - "code": "62", - "name": "甘肃省", - "children": [ - { - "code": "6201", - "name": "兰州市", - "children": [ - { - "code": "620102", - "name": "城关区" - }, - { - "code": "620103", - "name": "七里河区" - }, - { - "code": "620104", - "name": "西固区" - }, - { - "code": "620105", - "name": "安宁区" - }, - { - "code": "620111", - "name": "红古区" - }, - { - "code": "620121", - "name": "永登县" - }, - { - "code": "620122", - "name": "皋兰县" - }, - { - "code": "620123", - "name": "榆中县" - }, - { - "code": "620171", - "name": "兰州新区" - } - ] - }, - { - "code": "6202", - "name": "嘉峪关市", - "children": [ - { - "code": "620201001", - "name": "雄关街道" - }, - { - "code": "620201002", - "name": "钢城街道" - }, - { - "code": "620201100", - "name": "新城镇" - }, - { - "code": "620201101", - "name": "峪泉镇" - }, - { - "code": "620201102", - "name": "文殊镇" - } - ] - }, - { - "code": "6203", - "name": "金昌市", - "children": [ - { - "code": "620302", - "name": "金川区" - }, - { - "code": "620321", - "name": "永昌县" - } - ] - }, - { - "code": "6204", - "name": "白银市", - "children": [ - { - "code": "620402", - "name": "白银区" - }, - { - "code": "620403", - "name": "平川区" - }, - { - "code": "620421", - "name": "靖远县" - }, - { - "code": "620422", - "name": "会宁县" - }, - { - "code": "620423", - "name": "景泰县" - } - ] - }, - { - "code": "6205", - "name": "天水市", - "children": [ - { - "code": "620502", - "name": "秦州区" - }, - { - "code": "620503", - "name": "麦积区" - }, - { - "code": "620521", - "name": "清水县" - }, - { - "code": "620522", - "name": "秦安县" - }, - { - "code": "620523", - "name": "甘谷县" - }, - { - "code": "620524", - "name": "武山县" - }, - { - "code": "620525", - "name": "张家川回族自治县" - } - ] - }, - { - "code": "6206", - "name": "武威市", - "children": [ - { - "code": "620602", - "name": "凉州区" - }, - { - "code": "620621", - "name": "民勤县" - }, - { - "code": "620622", - "name": "古浪县" - }, - { - "code": "620623", - "name": "天祝藏族自治县" - } - ] - }, - { - "code": "6207", - "name": "张掖市", - "children": [ - { - "code": "620702", - "name": "甘州区" - }, - { - "code": "620721", - "name": "肃南裕固族自治县" - }, - { - "code": "620722", - "name": "民乐县" - }, - { - "code": "620723", - "name": "临泽县" - }, - { - "code": "620724", - "name": "高台县" - }, - { - "code": "620725", - "name": "山丹县" - } - ] - }, - { - "code": "6208", - "name": "平凉市", - "children": [ - { - "code": "620802", - "name": "崆峒区" - }, - { - "code": "620821", - "name": "泾川县" - }, - { - "code": "620822", - "name": "灵台县" - }, - { - "code": "620823", - "name": "崇信县" - }, - { - "code": "620825", - "name": "庄浪县" - }, - { - "code": "620826", - "name": "静宁县" - }, - { - "code": "620881", - "name": "华亭市" - } - ] - }, - { - "code": "6209", - "name": "酒泉市", - "children": [ - { - "code": "620902", - "name": "肃州区" - }, - { - "code": "620921", - "name": "金塔县" - }, - { - "code": "620922", - "name": "瓜州县" - }, - { - "code": "620923", - "name": "肃北蒙古族自治县" - }, - { - "code": "620924", - "name": "阿克塞哈萨克族自治县" - }, - { - "code": "620981", - "name": "玉门市" - }, - { - "code": "620982", - "name": "敦煌市" - } - ] - }, - { - "code": "6210", - "name": "庆阳市", - "children": [ - { - "code": "621002", - "name": "西峰区" - }, - { - "code": "621021", - "name": "庆城县" - }, - { - "code": "621022", - "name": "环县" - }, - { - "code": "621023", - "name": "华池县" - }, - { - "code": "621024", - "name": "合水县" - }, - { - "code": "621025", - "name": "正宁县" - }, - { - "code": "621026", - "name": "宁县" - }, - { - "code": "621027", - "name": "镇原县" - } - ] - }, - { - "code": "6211", - "name": "定西市", - "children": [ - { - "code": "621102", - "name": "安定区" - }, - { - "code": "621121", - "name": "通渭县" - }, - { - "code": "621122", - "name": "陇西县" - }, - { - "code": "621123", - "name": "渭源县" - }, - { - "code": "621124", - "name": "临洮县" - }, - { - "code": "621125", - "name": "漳县" - }, - { - "code": "621126", - "name": "岷县" - } - ] - }, - { - "code": "6212", - "name": "陇南市", - "children": [ - { - "code": "621202", - "name": "武都区" - }, - { - "code": "621221", - "name": "成县" - }, - { - "code": "621222", - "name": "文县" - }, - { - "code": "621223", - "name": "宕昌县" - }, - { - "code": "621224", - "name": "康县" - }, - { - "code": "621225", - "name": "西和县" - }, - { - "code": "621226", - "name": "礼县" - }, - { - "code": "621227", - "name": "徽县" - }, - { - "code": "621228", - "name": "两当县" - } - ] - }, - { - "code": "6229", - "name": "临夏回族自治州", - "children": [ - { - "code": "622901", - "name": "临夏市" - }, - { - "code": "622921", - "name": "临夏县" - }, - { - "code": "622922", - "name": "康乐县" - }, - { - "code": "622923", - "name": "永靖县" - }, - { - "code": "622924", - "name": "广河县" - }, - { - "code": "622925", - "name": "和政县" - }, - { - "code": "622926", - "name": "东乡族自治县" - }, - { - "code": "622927", - "name": "积石山保安族东乡族撒拉族自治县" - } - ] - }, - { - "code": "6230", - "name": "甘南藏族自治州", - "children": [ - { - "code": "623001", - "name": "合作市" - }, - { - "code": "623021", - "name": "临潭县" - }, - { - "code": "623022", - "name": "卓尼县" - }, - { - "code": "623023", - "name": "舟曲县" - }, - { - "code": "623024", - "name": "迭部县" - }, - { - "code": "623025", - "name": "玛曲县" - }, - { - "code": "623026", - "name": "碌曲县" - }, - { - "code": "623027", - "name": "夏河县" - } - ] - } - ] - }, - { - "code": "63", - "name": "青海省", - "children": [ - { - "code": "6301", - "name": "西宁市", - "children": [ - { - "code": "630102", - "name": "城东区" - }, - { - "code": "630103", - "name": "城中区" - }, - { - "code": "630104", - "name": "城西区" - }, - { - "code": "630105", - "name": "城北区" - }, - { - "code": "630106", - "name": "湟中区" - }, - { - "code": "630121", - "name": "大通回族土族自治县" - }, - { - "code": "630123", - "name": "湟源县" - } - ] - }, - { - "code": "6302", - "name": "海东市", - "children": [ - { - "code": "630202", - "name": "乐都区" - }, - { - "code": "630203", - "name": "平安区" - }, - { - "code": "630222", - "name": "民和回族土族自治县" - }, - { - "code": "630223", - "name": "互助土族自治县" - }, - { - "code": "630224", - "name": "化隆回族自治县" - }, - { - "code": "630225", - "name": "循化撒拉族自治县" - } - ] - }, - { - "code": "6322", - "name": "海北藏族自治州", - "children": [ - { - "code": "632221", - "name": "门源回族自治县" - }, - { - "code": "632222", - "name": "祁连县" - }, - { - "code": "632223", - "name": "海晏县" - }, - { - "code": "632224", - "name": "刚察县" - } - ] - }, - { - "code": "6323", - "name": "黄南藏族自治州", - "children": [ - { - "code": "632301", - "name": "同仁市" - }, - { - "code": "632322", - "name": "尖扎县" - }, - { - "code": "632323", - "name": "泽库县" - }, - { - "code": "632324", - "name": "河南蒙古族自治县" - } - ] - }, - { - "code": "6325", - "name": "海南藏族自治州", - "children": [ - { - "code": "632521", - "name": "共和县" - }, - { - "code": "632522", - "name": "同德县" - }, - { - "code": "632523", - "name": "贵德县" - }, - { - "code": "632524", - "name": "兴海县" - }, - { - "code": "632525", - "name": "贵南县" - } - ] - }, - { - "code": "6326", - "name": "果洛藏族自治州", - "children": [ - { - "code": "632621", - "name": "玛沁县" - }, - { - "code": "632622", - "name": "班玛县" - }, - { - "code": "632623", - "name": "甘德县" - }, - { - "code": "632624", - "name": "达日县" - }, - { - "code": "632625", - "name": "久治县" - }, - { - "code": "632626", - "name": "玛多县" - } - ] - }, - { - "code": "6327", - "name": "玉树藏族自治州", - "children": [ - { - "code": "632701", - "name": "玉树市" - }, - { - "code": "632722", - "name": "杂多县" - }, - { - "code": "632723", - "name": "称多县" - }, - { - "code": "632724", - "name": "治多县" - }, - { - "code": "632725", - "name": "囊谦县" - }, - { - "code": "632726", - "name": "曲麻莱县" - } - ] - }, - { - "code": "6328", - "name": "海西蒙古族藏族自治州", - "children": [ - { - "code": "632801", - "name": "格尔木市" - }, - { - "code": "632802", - "name": "德令哈市" - }, - { - "code": "632803", - "name": "茫崖市" - }, - { - "code": "632821", - "name": "乌兰县" - }, - { - "code": "632822", - "name": "都兰县" - }, - { - "code": "632823", - "name": "天峻县" - }, - { - "code": "632857", - "name": "大柴旦行政委员会" - } - ] - } - ] - }, - { - "code": "64", - "name": "宁夏回族自治区", - "children": [ - { - "code": "6401", - "name": "银川市", - "children": [ - { - "code": "640104", - "name": "兴庆区" - }, - { - "code": "640105", - "name": "西夏区" - }, - { - "code": "640106", - "name": "金凤区" - }, - { - "code": "640121", - "name": "永宁县" - }, - { - "code": "640122", - "name": "贺兰县" - }, - { - "code": "640181", - "name": "灵武市" - } - ] - }, - { - "code": "6402", - "name": "石嘴山市", - "children": [ - { - "code": "640202", - "name": "大武口区" - }, - { - "code": "640205", - "name": "惠农区" - }, - { - "code": "640221", - "name": "平罗县" - } - ] - }, - { - "code": "6403", - "name": "吴忠市", - "children": [ - { - "code": "640302", - "name": "利通区" - }, - { - "code": "640303", - "name": "红寺堡区" - }, - { - "code": "640323", - "name": "盐池县" - }, - { - "code": "640324", - "name": "同心县" - }, - { - "code": "640381", - "name": "青铜峡市" - } - ] - }, - { - "code": "6404", - "name": "固原市", - "children": [ - { - "code": "640402", - "name": "原州区" - }, - { - "code": "640422", - "name": "西吉县" - }, - { - "code": "640423", - "name": "隆德县" - }, - { - "code": "640424", - "name": "泾源县" - }, - { - "code": "640425", - "name": "彭阳县" - } - ] - }, - { - "code": "6405", - "name": "中卫市", - "children": [ - { - "code": "640502", - "name": "沙坡头区" - }, - { - "code": "640521", - "name": "中宁县" - }, - { - "code": "640522", - "name": "海原县" - } - ] - } - ] - }, - { - "code": "65", - "name": "新疆维吾尔自治区", - "children": [ - { - "code": "6501", - "name": "乌鲁木齐市", - "children": [ - { - "code": "650102", - "name": "天山区" - }, - { - "code": "650103", - "name": "沙依巴克区" - }, - { - "code": "650104", - "name": "新市区" - }, - { - "code": "650105", - "name": "水磨沟区" - }, - { - "code": "650106", - "name": "头屯河区" - }, - { - "code": "650107", - "name": "达坂城区" - }, - { - "code": "650109", - "name": "米东区" - }, - { - "code": "650121", - "name": "乌鲁木齐县" - } - ] - }, - { - "code": "6502", - "name": "克拉玛依市", - "children": [ - { - "code": "650202", - "name": "独山子区" - }, - { - "code": "650203", - "name": "克拉玛依区" - }, - { - "code": "650204", - "name": "白碱滩区" - }, - { - "code": "650205", - "name": "乌尔禾区" - } - ] - }, - { - "code": "6504", - "name": "吐鲁番市", - "children": [ - { - "code": "650402", - "name": "高昌区" - }, - { - "code": "650421", - "name": "鄯善县" - }, - { - "code": "650422", - "name": "托克逊县" - } - ] - }, - { - "code": "6505", - "name": "哈密市", - "children": [ - { - "code": "650502", - "name": "伊州区" - }, - { - "code": "650521", - "name": "巴里坤哈萨克自治县" - }, - { - "code": "650522", - "name": "伊吾县" - } - ] - }, - { - "code": "6523", - "name": "昌吉回族自治州", - "children": [ - { - "code": "652301", - "name": "昌吉市" - }, - { - "code": "652302", - "name": "阜康市" - }, - { - "code": "652323", - "name": "呼图壁县" - }, - { - "code": "652324", - "name": "玛纳斯县" - }, - { - "code": "652325", - "name": "奇台县" - }, - { - "code": "652327", - "name": "吉木萨尔县" - }, - { - "code": "652328", - "name": "木垒哈萨克自治县" - } - ] - }, - { - "code": "6527", - "name": "博尔塔拉蒙古自治州", - "children": [ - { - "code": "652701", - "name": "博乐市" - }, - { - "code": "652702", - "name": "阿拉山口市" - }, - { - "code": "652722", - "name": "精河县" - }, - { - "code": "652723", - "name": "温泉县" - } - ] - }, - { - "code": "6528", - "name": "巴音郭楞蒙古自治州", - "children": [ - { - "code": "652801", - "name": "库尔勒市" - }, - { - "code": "652822", - "name": "轮台县" - }, - { - "code": "652823", - "name": "尉犁县" - }, - { - "code": "652824", - "name": "若羌县" - }, - { - "code": "652825", - "name": "且末县" - }, - { - "code": "652826", - "name": "焉耆回族自治县" - }, - { - "code": "652827", - "name": "和静县" - }, - { - "code": "652828", - "name": "和硕县" - }, - { - "code": "652829", - "name": "博湖县" - } - ] - }, - { - "code": "6529", - "name": "阿克苏地区", - "children": [ - { - "code": "652901", - "name": "阿克苏市" - }, - { - "code": "652902", - "name": "库车市" - }, - { - "code": "652922", - "name": "温宿县" - }, - { - "code": "652924", - "name": "沙雅县" - }, - { - "code": "652925", - "name": "新和县" - }, - { - "code": "652926", - "name": "拜城县" - }, - { - "code": "652927", - "name": "乌什县" - }, - { - "code": "652928", - "name": "阿瓦提县" - }, - { - "code": "652929", - "name": "柯坪县" - } - ] - }, - { - "code": "6530", - "name": "克孜勒苏柯尔克孜自治州", - "children": [ - { - "code": "653001", - "name": "阿图什市" - }, - { - "code": "653022", - "name": "阿克陶县" - }, - { - "code": "653023", - "name": "阿合奇县" - }, - { - "code": "653024", - "name": "乌恰县" - } - ] - }, - { - "code": "6531", - "name": "喀什地区", - "children": [ - { - "code": "653101", - "name": "喀什市" - }, - { - "code": "653121", - "name": "疏附县" - }, - { - "code": "653122", - "name": "疏勒县" - }, - { - "code": "653123", - "name": "英吉沙县" - }, - { - "code": "653124", - "name": "泽普县" - }, - { - "code": "653125", - "name": "莎车县" - }, - { - "code": "653126", - "name": "叶城县" - }, - { - "code": "653127", - "name": "麦盖提县" - }, - { - "code": "653128", - "name": "岳普湖县" - }, - { - "code": "653129", - "name": "伽师县" - }, - { - "code": "653130", - "name": "巴楚县" - }, - { - "code": "653131", - "name": "塔什库尔干塔吉克自治县" - } - ] - }, - { - "code": "6532", - "name": "和田地区", - "children": [ - { - "code": "653201", - "name": "和田市" - }, - { - "code": "653221", - "name": "和田县" - }, - { - "code": "653222", - "name": "墨玉县" - }, - { - "code": "653223", - "name": "皮山县" - }, - { - "code": "653224", - "name": "洛浦县" - }, - { - "code": "653225", - "name": "策勒县" - }, - { - "code": "653226", - "name": "于田县" - }, - { - "code": "653227", - "name": "民丰县" - } - ] - }, - { - "code": "6540", - "name": "伊犁哈萨克自治州", - "children": [ - { - "code": "654002", - "name": "伊宁市" - }, - { - "code": "654003", - "name": "奎屯市" - }, - { - "code": "654004", - "name": "霍尔果斯市" - }, - { - "code": "654021", - "name": "伊宁县" - }, - { - "code": "654022", - "name": "察布查尔锡伯自治县" - }, - { - "code": "654023", - "name": "霍城县" - }, - { - "code": "654024", - "name": "巩留县" - }, - { - "code": "654025", - "name": "新源县" - }, - { - "code": "654026", - "name": "昭苏县" - }, - { - "code": "654027", - "name": "特克斯县" - }, - { - "code": "654028", - "name": "尼勒克县" - } - ] - }, - { - "code": "6542", - "name": "塔城地区", - "children": [ - { - "code": "654201", - "name": "塔城市" - }, - { - "code": "654202", - "name": "乌苏市" - }, - { - "code": "654203", - "name": "沙湾市" - }, - { - "code": "654221", - "name": "额敏县" - }, - { - "code": "654224", - "name": "托里县" - }, - { - "code": "654225", - "name": "裕民县" - }, - { - "code": "654226", - "name": "和布克赛尔蒙古自治县" - } - ] - }, - { - "code": "6543", - "name": "阿勒泰地区", - "children": [ - { - "code": "654301", - "name": "阿勒泰市" - }, - { - "code": "654321", - "name": "布尔津县" - }, - { - "code": "654322", - "name": "富蕴县" - }, - { - "code": "654323", - "name": "福海县" - }, - { - "code": "654324", - "name": "哈巴河县" - }, - { - "code": "654325", - "name": "青河县" - }, - { - "code": "654326", - "name": "吉木乃县" - } - ] - }, - { - "code": "6590", - "name": "自治区直辖县级行政区划", - "children": [ - { - "code": "659001", - "name": "石河子市" - }, - { - "code": "659002", - "name": "阿拉尔市" - }, - { - "code": "659003", - "name": "图木舒克市" - }, - { - "code": "659004", - "name": "五家渠市" - }, - { - "code": "659005", - "name": "北屯市" - }, - { - "code": "659006", - "name": "铁门关市" - }, - { - "code": "659007", - "name": "双河市" - }, - { - "code": "659008", - "name": "可克达拉市" - }, - { - "code": "659009", - "name": "昆玉市" - }, - { - "code": "659010", - "name": "胡杨河市" - }, - { - "code": "659011", - "name": "新星市" - }, - { - "code": "659012", - "name": "白杨市" - } - ] - } - ] - }, - { - "code": "81", - "name": "香港特别行政区", - "children": [ - { - "code": "8101", - "name": "香港岛", - "children": [ - {"code": "810101", "name": "中西区"}, - {"code": "810102", "name": "湾仔区"}, - {"code": "810103", "name": "东区"}, - {"code": "810104", "name": "南区"} - ] - }, - { - "code": "8102", - "name": "九龙", - "children": [ - {"code": "810201", "name": "油尖旺区"}, - {"code": "810202", "name": "深水埗区"}, - {"code": "810203", "name": "九龙城区"}, - {"code": "810204", "name": "黄大仙区"}, - {"code": "810205", "name": "观塘区"} - ] - }, - { - "code": "8103", - "name": "新界", - "children": [ - {"code": "810301", "name": "北区"}, - {"code": "810302", "name": "大埔区"}, - {"code": "810303", "name": "沙田区"}, - {"code": "810304", "name": "西贡区"}, - {"code": "810305", "name": "元朗区"}, - {"code": "810306", "name": "屯门区"}, - {"code": "810307", "name": "荃湾区"}, - {"code": "810308", "name": "葵青区"}, - {"code": "810309", "name": "离岛区"} - ] - } - ] - }, - { - "code": "82", - "name": "澳门特别行政区", - "children": [ - { - "code": "8201", - "name": "澳门半岛", - "children": [ - {"code": "820101", "name": "花地玛堂区"}, - {"code": "820102", "name": "圣安多尼堂区"}, - {"code": "820103", "name": "大堂区"}, - {"code": "820104", "name": "望德堂区"}, - {"code": "820105", "name": "风顺堂区"} - ] - }, - { - "code": "8202", - "name": "离岛", - "children": [ - {"code": "820201", "name": "嘉模堂区"}, - {"code": "820202", "name": "圣方济各堂区"} - ] - } - ] - }, - { - "code": "71", - "name": "台湾省", - "children": [ - { - "code": "7101", - "name": "台北市", - "children": [ - {"code": "710101", "name": "中正区"}, - {"code": "710102", "name": "大同区"}, - {"code": "710103", "name": "中山区"}, - {"code": "710104", "name": "松山区"}, - {"code": "710105", "name": "大安区"}, - {"code": "710106", "name": "万华区"}, - {"code": "710107", "name": "信义区"}, - {"code": "710108", "name": "士林区"}, - {"code": "710109", "name": "北投区"}, - {"code": "710110", "name": "内湖区"}, - {"code": "710111", "name": "南港区"}, - {"code": "710112", "name": "文山区"} - ] - }, - { - "code": "7102", - "name": "高雄市", - "children": [ - {"code": "710201", "name": "新兴区"}, - {"code": "710202", "name": "前金区"}, - {"code": "710203", "name": "苓雅区"}, - {"code": "710204", "name": "盐埕区"}, - {"code": "710205", "name": "鼓山区"}, - {"code": "710206", "name": "旗津区"}, - {"code": "710207", "name": "前镇区"}, - {"code": "710208", "name": "三民区"}, - {"code": "710209", "name": "左营区"}, - {"code": "710210", "name": "楠梓区"} - ] - }, - { - "code": "7103", - "name": "台中市", - "children": [ - {"code": "710301", "name": "中区"}, - {"code": "710302", "name": "东区"}, - {"code": "710303", "name": "南区"}, - {"code": "710304", "name": "西区"}, - {"code": "710305", "name": "北区"}, - {"code": "710306", "name": "北屯区"}, - {"code": "710307", "name": "西屯区"}, - {"code": "710308", "name": "南屯区"} - ] - }, - { - "code": "7104", - "name": "台南市", - "children": [ - {"code": "710401", "name": "中西区"}, - {"code": "710402", "name": "东区"}, - {"code": "710403", "name": "南区"}, - {"code": "710404", "name": "北区"}, - {"code": "710405", "name": "安平区"}, - {"code": "710406", "name": "安南区"} - ] - }, - { - "code": "7105", - "name": "新北市", - "children": [ - {"code": "710501", "name": "万里区"}, - {"code": "710502", "name": "金山区"}, - {"code": "710503", "name": "板桥区"}, - {"code": "710504", "name": "汐止区"}, - {"code": "710505", "name": "深坑区"}, - {"code": "710506", "name": "石碇区"}, - {"code": "710507", "name": "瑞芳区"}, - {"code": "710508", "name": "平溪区"} - ] - }, - { - "code": "7106", - "name": "桃园市", - "children": [ - {"code": "710601", "name": "中坜区"}, - {"code": "710602", "name": "平镇区"}, - {"code": "710603", "name": "龙潭区"}, - {"code": "710604", "name": "杨梅区"}, - {"code": "710605", "name": "新屋区"}, - {"code": "710606", "name": "观音区"}, - {"code": "710607", "name": "桃园区"}, - {"code": "710608", "name": "龟山区"} - ] - } - ] - } -] \ No newline at end of file diff --git a/scripts/update_code_references.js b/scripts/update_code_references.js deleted file mode 100644 index eb9a509..0000000 --- a/scripts/update_code_references.js +++ /dev/null @@ -1,273 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -/** - * 代码更新脚本:将所有 order_allocations 表引用更新为 transfers 表 - * - * 更新策略: - * 1. 将 order_allocations 表名替换为 transfers - * 2. 更新相关的字段映射和查询逻辑 - * 3. 添加必要的 WHERE 条件来过滤 allocation 类型的记录 - */ - -class CodeUpdater { - constructor() { - this.filesToUpdate = [ - 'services/matchingService.js', - 'routes/matchingAdmin.js', - 'routes/transfers.js', - 'routes/matching.js' - ]; - - // 字段映射关系 - this.fieldMappings = { - // order_allocations 字段 -> transfers 字段 - 'matching_order_id': 'matching_order_id', - 'from_user_id': 'from_user_id', - 'to_user_id': 'to_user_id', - 'amount': 'amount', - 'cycle_number': 'cycle_number', - 'status': 'status', - 'transfer_id': 'id', // order_allocations.transfer_id 对应 transfers.id - 'created_at': 'created_at', - 'updated_at': 'updated_at', - 'confirmed_at': 'confirmed_at', - 'outbound_date': 'outbound_date', - 'return_date': 'return_date', - 'can_return_after': 'can_return_after' - }; - } - - /** - * 更新单个文件 - * @param {string} filePath - 文件路径 - */ - async updateFile(filePath) { - const fullPath = path.join(process.cwd(), filePath); - - if (!fs.existsSync(fullPath)) { - console.log(`文件不存在: ${filePath}`); - return; - } - - console.log(`正在更新文件: ${filePath}`); - - let content = fs.readFileSync(fullPath, 'utf8'); - let originalContent = content; - - // 1. 替换表名 - content = content.replace(/\border_allocations\b/g, 'transfers'); - - // 2. 添加 source_type 过滤条件 - content = this.addSourceTypeFilters(content); - - // 3. 更新 INSERT 语句 - content = this.updateInsertStatements(content); - - // 4. 更新特定的查询逻辑 - content = this.updateSpecificQueries(content, filePath); - - // 5. 更新注释 - content = this.updateComments(content); - - if (content !== originalContent) { - // 创建备份 - fs.writeFileSync(fullPath + '.backup', originalContent); - - // 写入更新后的内容 - fs.writeFileSync(fullPath, content); - - console.log(`✓ 已更新: ${filePath}`); - console.log(`✓ 备份已创建: ${filePath}.backup`); - } else { - console.log(`- 无需更新: ${filePath}`); - } - } - - /** - * 添加 source_type 过滤条件 - * @param {string} content - 文件内容 - * @returns {string} 更新后的内容 - */ - addSourceTypeFilters(content) { - // 在 FROM transfers 后添加 WHERE source_type = 'allocation' 条件 - // 但要避免重复添加 - - // 匹配 FROM transfers 但不包含 source_type 的情况 - content = content.replace( - /FROM transfers(?!.*source_type)([\s\S]*?)(?=WHERE|ORDER|GROUP|LIMIT|;|$)/gi, - (match, afterFrom) => { - // 如果已经有 WHERE 子句,添加 AND 条件 - if (afterFrom.includes('WHERE')) { - return match.replace(/WHERE/, 'WHERE source_type = \'allocation\' AND'); - } else { - // 如果没有 WHERE 子句,添加新的 WHERE 条件 - const beforeNextClause = match.match(/(ORDER|GROUP|LIMIT|;|$)/); - if (beforeNextClause) { - return match.replace(beforeNextClause[0], ` WHERE source_type = 'allocation' ${beforeNextClause[0]}`); - } else { - return match + " WHERE source_type = 'allocation'"; - } - } - } - ); - - return content; - } - - /** - * 更新 INSERT 语句 - * @param {string} content - 文件内容 - * @returns {string} 更新后的内容 - */ - updateInsertStatements(content) { - // 更新 INSERT INTO transfers 语句,添加必要的字段 - content = content.replace( - /INSERT INTO transfers\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/gi, - (match, fields, values) => { - // 如果字段列表中没有 source_type,添加它 - if (!fields.includes('source_type')) { - const fieldList = fields.trim() + ', source_type'; - const valueList = values.trim() + ', \'allocation\''; - return `INSERT INTO transfers (${fieldList}) VALUES (${valueList})`; - } - return match; - } - ); - - return content; - } - - /** - * 更新特定的查询逻辑 - * @param {string} content - 文件内容 - * @param {string} filePath - 文件路径 - * @returns {string} 更新后的内容 - */ - updateSpecificQueries(content, filePath) { - // 根据不同文件进行特定更新 - - if (filePath.includes('matchingService.js')) { - // 更新 matchingService.js 中的特定逻辑 - content = this.updateMatchingServiceQueries(content); - } - - if (filePath.includes('matchingAdmin.js')) { - // 更新 matchingAdmin.js 中的特定逻辑 - content = this.updateMatchingAdminQueries(content); - } - - return content; - } - - /** - * 更新 matchingService.js 中的查询 - * @param {string} content - 文件内容 - * @returns {string} 更新后的内容 - */ - updateMatchingServiceQueries(content) { - // 更新确认分配的逻辑 - content = content.replace( - /UPDATE transfers SET status = "confirmed", transfer_id = \?, confirmed_at = NOW\(\) WHERE id = \?/g, - 'UPDATE transfers SET status = "confirmed", confirmed_at = NOW() WHERE id = ? AND source_type = \'allocation\'' - ); - - // 更新获取分配记录的查询 - content = content.replace( - /SELECT \* FROM transfers WHERE id = \? AND from_user_id = \?/g, - 'SELECT * FROM transfers WHERE id = ? AND from_user_id = ? AND source_type = \'allocation\'' - ); - - return content; - } - - /** - * 更新 matchingAdmin.js 中的查询 - * @param {string} content - 文件内容 - * @returns {string} 更新后的内容 - */ - updateMatchingAdminQueries(content) { - // 更新管理员查询逻辑,确保只查询 allocation 类型的记录 - content = content.replace( - /FROM transfers oa/g, - 'FROM transfers oa WHERE oa.source_type = \'allocation\'' - ); - - return content; - } - - /** - * 更新注释 - * @param {string} content - 文件内容 - * @returns {string} 更新后的内容 - */ - updateComments(content) { - content = content.replace(/order_allocations/g, 'transfers (allocation type)'); - content = content.replace(/订单分配/g, '转账分配'); - content = content.replace(/分配表/g, '转账表(分配类型)'); - - return content; - } - - /** - * 执行所有文件的更新 - */ - async updateAllFiles() { - console.log('开始更新代码引用...'); - console.log('=' .repeat(60)); - - for (const filePath of this.filesToUpdate) { - try { - await this.updateFile(filePath); - } catch (error) { - console.error(`更新文件 ${filePath} 失败:`, error.message); - } - } - - console.log('\n' + '=' .repeat(60)); - console.log('✓ 代码更新完成!'); - console.log('\n注意事项:'); - console.log('1. 所有原始文件已备份为 .backup 文件'); - console.log('2. 请测试更新后的代码功能是否正常'); - console.log('3. 如有问题,可以使用备份文件恢复'); - console.log('4. 确认无误后可删除 .backup 文件'); - } - - /** - * 恢复所有备份文件 - */ - async restoreBackups() { - console.log('开始恢复备份文件...'); - - for (const filePath of this.filesToUpdate) { - const fullPath = path.join(process.cwd(), filePath); - const backupPath = fullPath + '.backup'; - - if (fs.existsSync(backupPath)) { - fs.copyFileSync(backupPath, fullPath); - console.log(`✓ 已恢复: ${filePath}`); - } - } - - console.log('✓ 备份恢复完成!'); - } -} - -async function main() { - const updater = new CodeUpdater(); - - const args = process.argv.slice(2); - - if (args.includes('--restore')) { - await updater.restoreBackups(); - } else { - await updater.updateAllFiles(); - } -} - -// 如果直接运行此脚本 -if (require.main === module) { - main().catch(console.error); -} - -module.exports = CodeUpdater; \ No newline at end of file diff --git a/scripts/verify_data.js b/scripts/verify_data.js deleted file mode 100644 index 470682a..0000000 --- a/scripts/verify_data.js +++ /dev/null @@ -1,37 +0,0 @@ -const { initDB, getDB } = require('../database'); - -async function verifyData() { - try { - await initDB(); - - // 检查省份数据 - const [provinces] = await getDB().query('SELECT code, name FROM china_regions WHERE level = 1 ORDER BY code LIMIT 10'); - console.log('省份数据样本:'); - provinces.forEach(p => console.log(` ${p.code} - ${p.name}`)); - - // 检查城市数据 - const [cities] = await getDB().query('SELECT code, name, parent_code FROM china_regions WHERE level = 2 ORDER BY code LIMIT 10'); - console.log('\n城市数据样本:'); - cities.forEach(c => console.log(` ${c.code} - ${c.name} (${c.parent_code})`)); - - // 检查区县数据 - const [districts] = await getDB().query('SELECT code, name, parent_code FROM china_regions WHERE level = 3 ORDER BY code LIMIT 10'); - console.log('\n区县数据样本:'); - districts.forEach(d => console.log(` ${d.code} - ${d.name} (${d.parent_code})`)); - - // 统计各级别数量 - const [stats] = await getDB().query('SELECT level, COUNT(*) as count FROM china_regions GROUP BY level ORDER BY level'); - console.log('\n各级别统计:'); - stats.forEach(row => { - const levelName = row.level === 1 ? '省份' : row.level === 2 ? '城市' : '区县'; - console.log(` ${levelName}(level ${row.level}): ${row.count} 个`); - }); - - } catch (error) { - console.error('验证失败:', error); - } finally { - process.exit(); - } -} - -verifyData(); \ No newline at end of file diff --git a/scripts/verify_merge.js b/scripts/verify_merge.js deleted file mode 100644 index e3e4995..0000000 --- a/scripts/verify_merge.js +++ /dev/null @@ -1,115 +0,0 @@ -const mysql = require('mysql2/promise'); -const { dbConfig } = require('../config/config'); - -/** - * 验证表合并结果的脚本 - * 检查 order_allocations 表和 transfers 表的数据一致性 - */ -async function verifyMerge() { - console.log('开始验证表合并结果...'); - console.log('=' .repeat(60)); - - let connection; - - try { - // 创建数据库连接 - connection = await mysql.createConnection({ - host: dbConfig.host, - user: dbConfig.user, - password: dbConfig.password, - database: dbConfig.database - }); - - // 1. 检查 order_allocations 表中有多少条记录 - const [allocationCount] = await connection.execute( - 'SELECT COUNT(*) as count FROM order_allocations' - ); - console.log(`order_allocations 表总记录数: ${allocationCount[0].count}`); - - // 2. 检查 transfers 表中有多少条 allocation 类型的记录 - const [transferCount] = await connection.execute( - 'SELECT COUNT(*) as count FROM transfers WHERE source_type = \'allocation\'' - ); - console.log(`transfers 表中 allocation 类型记录数: ${transferCount[0].count}`); - - // 3. 检查 order_allocations 表中有多少条记录没有关联的 transfer_id - const [unlinkedCount] = await connection.execute( - 'SELECT COUNT(*) as count FROM order_allocations WHERE transfer_id IS NULL' - ); - console.log(`order_allocations 表中无关联 transfer_id 的记录数: ${unlinkedCount[0].count}`); - - // 4. 检查数据一致性 - 抽样检查 - console.log('\n数据一致性检查(抽样):'); - const [sampleAllocations] = await connection.execute( - 'SELECT * FROM order_allocations WHERE transfer_id IS NOT NULL LIMIT 5' - ); - - for (const allocation of sampleAllocations) { - const [transfer] = await connection.execute( - 'SELECT * FROM transfers WHERE id = ?', - [allocation.transfer_id] - ); - - if (transfer.length === 0) { - console.log(` ✗ 错误: allocation_id=${allocation.id} 关联的 transfer_id=${allocation.transfer_id} 不存在`); - continue; - } - - const transferRecord = transfer[0]; - const isConsistent = - transferRecord.from_user_id == allocation.from_user_id && - transferRecord.to_user_id == allocation.to_user_id && - transferRecord.amount == allocation.amount && - transferRecord.matching_order_id == allocation.matching_order_id && - transferRecord.cycle_number == allocation.cycle_number; - - if (isConsistent) { - console.log(` ✓ allocation_id=${allocation.id} 与 transfer_id=${allocation.transfer_id} 数据一致`); - } else { - console.log(` ✗ 错误: allocation_id=${allocation.id} 与 transfer_id=${allocation.transfer_id} 数据不一致`); - console.log(' allocation:', { - from_user_id: allocation.from_user_id, - to_user_id: allocation.to_user_id, - amount: allocation.amount, - matching_order_id: allocation.matching_order_id, - cycle_number: allocation.cycle_number - }); - console.log(' transfer:', { - from_user_id: transferRecord.from_user_id, - to_user_id: transferRecord.to_user_id, - amount: transferRecord.amount, - matching_order_id: transferRecord.matching_order_id, - cycle_number: transferRecord.cycle_number - }); - } - } - - console.log('\n' + '=' .repeat(60)); - - // 总结 - if (allocationCount[0].count === transferCount[0].count && unlinkedCount[0].count === 0) { - console.log('✓ 验证成功! 所有 order_allocations 记录都已正确迁移到 transfers 表'); - } else { - console.log('⚠ 验证结果: 可能存在未完全迁移的数据'); - console.log(` - order_allocations 总数: ${allocationCount[0].count}`); - console.log(` - transfers 中 allocation 类型数: ${transferCount[0].count}`); - console.log(` - 未关联记录数: ${unlinkedCount[0].count}`); - } - - } catch (error) { - console.error('验证失败:', error); - throw error; - } finally { - // 关闭数据库连接 - if (connection) { - await connection.end(); - } - } -} - -// 如果直接运行此脚本 -if (require.main === module) { - verifyMerge().catch(console.error); -} - -module.exports = verifyMerge; \ No newline at end of file diff --git a/server.js b/server.js index 61eeb6c..fe047e7 100644 --- a/server.js +++ b/server.js @@ -86,210 +86,22 @@ const limiter = rateLimit({ } } }); -app.use('/api', limiter); - -// 静态文件服务 - 必须在API路由之前 -// 为uploads路径配置CORS和静态文件服务 -app.use('/uploads', express.static(path.join(__dirname, 'uploads'), { - setHeaders: (res, filePath) => { - // 设置CORS头部 - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - - // 设置缓存和内容类型 - if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) { - res.setHeader('Content-Type', 'image/jpeg'); - } else if (filePath.endsWith('.png')) { - res.setHeader('Content-Type', 'image/png'); - } else if (filePath.endsWith('.gif')) { - res.setHeader('Content-Type', 'image/gif'); - } else if (filePath.endsWith('.webp')) { - res.setHeader('Content-Type', 'image/webp'); - } - res.setHeader('Cache-Control', 'public, max-age=86400'); // 1天缓存 - } -})); - -// 处理vite.svg请求 -app.get('/vite.svg', (req, res) => { - const referer = req.get('Referer'); - if (referer && referer.includes('/admin')) { - // 为admin页面提供logo.svg - res.setHeader('Content-Type', 'image/svg+xml'); - res.sendFile(path.join(__dirname, 'admin/dist/logo.svg')); - } else { - // 前端页面没有vite.svg,返回404 - res.status(404).send('File not found'); - } -}); - -// 静态文件服务配置 -// 专门处理admin路径下的assets -app.use('/admin/assets', express.static(path.join(__dirname, 'admin/dist/assets'), { - setHeaders: (res, filePath) => { - res.removeHeader('Origin-Agent-Cluster'); - if (filePath.endsWith('.css')) { - res.setHeader('Content-Type', 'text/css; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } else if (filePath.endsWith('.js')) { - res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } - } -})); - -// 为admin页面的assets提供服务(当从admin页面访问/assets/时) -app.use('/assets', (req, res, next) => { - // 检查referer来判断是否来自admin页面 - const referer = req.get('Referer'); - if (referer && referer.includes('/admin')) { - // 如果来自admin页面,从admin/dist/assets提供文件 - express.static(path.join(__dirname, 'admin/dist/assets'), { - setHeaders: (res, filePath) => { - res.removeHeader('Origin-Agent-Cluster'); - if (filePath.endsWith('.css')) { - res.setHeader('Content-Type', 'text/css; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } else if (filePath.endsWith('.js')) { - res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } - } - })(req, res, next); - } else { - // 否则从frontend/dist/assets提供文件 - express.static(path.join(__dirname, 'frontend/dist/assets'), { - setHeaders: (res, filePath) => { - res.removeHeader('Origin-Agent-Cluster'); - if (filePath.endsWith('.css')) { - res.setHeader('Content-Type', 'text/css; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } else if (filePath.endsWith('.js')) { - res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); - } - } - })(req, res, next); - } -}); - -app.use('/admin', express.static(path.join(__dirname, 'admin/dist'), { - setHeaders: (res, filePath) => { - // 移除Origin-Agent-Cluster头部以避免冲突 - res.removeHeader('Origin-Agent-Cluster'); - - if (filePath.endsWith('.css')) { - res.setHeader('Content-Type', 'text/css; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 - } else if (filePath.endsWith('.js')) { - res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 - } else if (filePath.endsWith('.html')) { - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // HTML文件不缓存 - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - } else if (filePath.endsWith('.svg')) { - res.setHeader('Content-Type', 'image/svg+xml'); - } - } -})); - -app.use(express.static(path.join(__dirname, 'frontend/dist'), { - setHeaders: (res, filePath) => { - // 移除Origin-Agent-Cluster头部以避免冲突 - res.removeHeader('Origin-Agent-Cluster'); - - if (filePath.endsWith('.css')) { - res.setHeader('Content-Type', 'text/css; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 - } else if (filePath.endsWith('.js')) { - res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 - } else if (filePath.endsWith('.html')) { - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // HTML文件不缓存 - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - } else if (filePath.endsWith('.svg')) { - res.setHeader('Content-Type', 'image/svg+xml'); - } - } -})); +app.use('/', limiter); // 引入数据库初始化模块 const { initDatabase } = require('./config/database-init'); -// API路由 -app.use('/api/auth', require('./routes/auth')); -app.use('/api/users', require('./routes/users')); -app.use('/api/user', require('./routes/users')); // 添加单数形式的路由映射 -app.use('/api/products', require('./routes/products')); -app.use('/api/specifications', require('./routes/specifications')); -app.use('/api/orders', require('./routes/orders')); -app.use('/api/points', require('./routes/points')); -app.use('/api/captcha', require('./routes/captcha')); // 验证码路由 -app.use('/api/sms', require('./routes/sms')); // 短信验证路由 - -app.use('/api/upload', require('./routes/upload')); -app.use('/api/transfers', require('./routes/transfers')); -app.use('/api/matching', require('./routes/matching')); -app.use('/api/admin/matching', require('./routes/matchingAdmin')); -app.use('/api/system', require('./routes/system')); -app.use('/api/risk', require('./routes/riskManagement')); -app.use('/api/agents', require('./routes/agents')); -app.use('/api/admin/agents', require('./routes/agents/agents')); -app.use('/api/admin/withdrawals', require('./routes/agents/withdrawals')); -app.use('/api/agent-withdrawals', require('./routes/agent-withdrawals')); -app.use('/api/regions', require('./routes/regions')); -app.use('/api/addresses', require('./routes/addresses')); -app.use('/api/address-labels', require('./routes/address-labels')); -app.use('/api/cart', require('./routes/cart')); -app.use('/api/announcements', require('./routes/announcements')); // 通知公告路由 -app.use('/api/wechat-pay', require('./routes/wechatPay')); // 只保留微信支付 -app.use('/api/payment', require('./routes/payment')); - // 商城后台相关接口 -app.use('/api/shopbackend', require('./routes/shopbackend')); - -// 前端路由 - 必须在最后,作为fallback -app.get('/', (req, res) => { - res.removeHeader('Origin-Agent-Cluster'); - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); -}); - -app.get('/admin*', (req, res) => { - res.removeHeader('Origin-Agent-Cluster'); - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.sendFile(path.join(__dirname, 'admin/dist/index.html')); -}); - -app.get('/frontend*', (req, res) => { - res.removeHeader('Origin-Agent-Cluster'); - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); -}); - -// SPA fallback - 处理前端路由 -app.get('*', (req, res) => { - // 如果请求的是静态资源但找不到,返回404(不返回JSON) - if (req.path.includes('.')) { - return res.status(404).send('File not found'); - } - // 否则返回前端应用的index.html - res.removeHeader('Origin-Agent-Cluster'); - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.sendFile(path.join(__dirname, 'frontend/dist/index.html')); -}); +app.use('/shopbackend', require('./routes/shopbackend')); +//订单管理接口 +app.use('/orders', require('./routes/orders')) +//商品列表 +app.use('/products', require('./routes/products')) +//供应商接口 +app.use('/supplier',require('./routes/supplier')) +//规格处理 +app.use('/specifications', require('./routes/specifications')); // 404处理 app.use(notFound); @@ -309,16 +121,6 @@ app.listen(PORT, async () => { console.log(`服务器运行在 http://localhost:${PORT}`); await initDatabase(); - // global.sqlReq = mysql.createConnection() - // 启动转账超时检查服务 - const timeoutService = require('./services/timeoutService'); - timeoutService.startTimeoutChecker(); - console.log('转账超时检查服务已启动'); - - // 启动数据库连接监控 - // const dbMonitor = require('./db-monitor'); - // dbMonitor.startMonitoring(60000); // 每分钟监控一次 - // console.log('数据库连接监控已启动'); global.captchaStore = new Map(); logger.info('Server started successfully', { port: PORT, diff --git a/services/alipayservice.js b/services/alipayservice.js deleted file mode 100644 index 1e9385c..0000000 --- a/services/alipayservice.js +++ /dev/null @@ -1,306 +0,0 @@ -const { AlipaySdk } = require('alipay-sdk'); -const { getDB } = require('../database'); -const crypto = require('crypto'); -const path = require('path'); -const fs = require('fs'); - -class AlipayService { - constructor() { - this.privateKey = null; - this.alipayPublicKey = null; - this.alipaySdk = null; - this.isInitialized = false; - - this.initializeAlipay(); - } - - /** - * 初始化支付宝服务 - */ - initializeAlipay() { - try { - // 读取密钥文件 - const privateKeyPath = this.resolveCertPath('../certs/alipay-private-key.pem'); - const publicKeyPath = this.resolveCertPath('../certs/alipay-public-key.pem'); - - console.log('支付宝私钥路径:', privateKeyPath); - console.log('支付宝公钥路径:', publicKeyPath); - this.privateKey = fs.readFileSync(privateKeyPath, 'utf8'); - this.alipayPublicKey = fs.readFileSync(publicKeyPath, 'utf8'); - this.initializeSDK(); - - } catch (error) { - console.error('支付宝服务初始化失败:', error.message); - console.error('支付宝功能将不可用'); - // 不抛出错误,允许服务继续运行 - } - } - - /** - * 初始化支付宝SDK - */ - initializeSDK() { - if (!this.privateKey || !this.alipayPublicKey) { - console.warn('支付宝密钥未加载,跳过SDK初始化'); - return; - } - - // 支付宝配置 - this.config = { - appId: process.env.ALIPAY_APP_ID || '2021001161683774', // 替换为实际的应用ID - privateKey: this.privateKey, // 从文件读取的应用私钥 - alipayPublicKey: this.alipayPublicKey, // 从文件读取的支付宝公钥 - gateway: 'https://openapi.alipay.com/gateway.do', // 支付宝网关地址 - signType: 'RSA2', - charset: 'utf-8', - version: '1.0', - timeout: 5000 - }; - - // 初始化支付宝SDK - this.alipaySdk = new AlipaySdk({ - appId: this.config.appId, - privateKey: this.config.privateKey, - alipayPublicKey: this.config.alipayPublicKey, - gateway: this.config.gateway, - signType: this.config.signType, - timeout: this.config.timeout - }); - - this.isInitialized = true; - console.log('支付宝SDK初始化成功'); - } - - /** - * 解析证书文件路径 - * @param {string} relativePath - 相对路径 - * @returns {string} 绝对路径 - */ - resolveCertPath(relativePath) { - return path.resolve(__dirname, relativePath); - } - - /** - * 验证文件是否有效 - * @param {string} filePath - 文件路径 - * @returns {boolean} 是否为有效文件 - */ - isValidFile(filePath) { - try { - const stats = fs.statSync(filePath); - return stats.isFile(); - } catch (error) { - return false; - } - } - - /** - * 检查支付宝服务是否已初始化 - * @returns {boolean} 是否已初始化 - */ - isServiceAvailable() { - return this.isInitialized && this.alipaySdk !== null; - } - - /** - * 创建注册支付订单 - * @param {Object} params - 支付参数 - * @param {string} params.userId - 用户ID - * @param {string} params.username - 用户名 - * @param {string} params.phone - 手机号 - * @param {string} params.clientIp - 客户端IP - * @returns {Promise} 支付结果 - */ - async createRegistrationPayOrder({ userId, username, phone, clientIp }) { - // 检查服务是否可用 - if (!this.isServiceAvailable()) { - throw new Error('支付宝服务未初始化或不可用'); - } - - try { - const db = getDB(); - - // 生成订单号 - const outTradeNo = this.generateOrderNo(); - const totalFee = 39900; // 399元,单位:分 - const subject = '用户注册激活费用'; - const body = `用户${username}(${phone})注册激活费用`; - - // 业务参数 - const bizContent = { - out_trade_no: outTradeNo, - total_amount: (totalFee / 100).toFixed(2), // 转换为元 - subject: subject, - body: body, - product_code: 'QUICK_WAP_WAY', - quit_url: process.env.ALIPAY_QUIT_URL - }; - - // 使用新版SDK的pageExecute方法生成支付URL - const payUrl = this.alipaySdk.pageExecute('alipay.trade.wap.pay', 'GET', { - bizContent: bizContent, - notifyUrl: process.env.ALIPAY_NOTIFY_URL, - returnUrl: process.env.ALIPAY_RETURN_URL - }); - - // 保存订单到数据库 - await db.execute( - `INSERT INTO payment_orders - (user_id, out_trade_no, total_fee, body, trade_type, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, NOW())`, - [userId, outTradeNo, totalFee, body, 'ALIPAY_WAP', 'pending'] - ); - - console.log('支付宝支付订单创建成功:', { - userId, - outTradeNo, - totalFee, - payUrl - }); - - return { - success: true, - data: { - outTradeNo, - payUrl, - paymentType: 'alipay_wap', - totalFee - } - }; - } catch (error) { - console.error('创建支付宝支付订单失败:', error); - return { - success: false, - message: error.message || '创建支付订单失败' - }; - } - } - - /** - * 查询支付状态 - * @param {string} outTradeNo - 商户订单号 - * @returns {Promise} 查询结果 - */ - async queryPaymentStatus(outTradeNo) { - // 检查服务是否可用 - if (!this.isServiceAvailable()) { - throw new Error('支付宝服务未初始化或不可用'); - } - - try { - const result = await this.alipaySdk.exec('alipay.trade.query', { - bizContent: { - out_trade_no: outTradeNo - } - }); - - if (result.code === '10000') { - // 查询成功 - const tradeStatus = result.tradeStatus; - - // 如果支付成功,更新数据库 - if (tradeStatus === 'TRADE_SUCCESS') { - await this.updatePaymentStatus(outTradeNo, { - status: 'paid', - transactionId: result.tradeNo, - paidAt: new Date() - }); - } - - return { - success: true, - data: { - trade_status: tradeStatus, - trade_no: result.tradeNo, - total_amount: result.totalAmount, - buyer_pay_amount: result.buyerPayAmount, - gmt_payment: result.gmtPayment - } - }; - } else { - return { - success: false, - message: result.msg || '查询支付状态失败' - }; - } - } catch (error) { - console.error('查询支付宝支付状态失败:', error); - return { - success: false, - message: error.message || '查询支付状态失败' - }; - } - } - - /** - * 更新支付状态 - * @param {string} outTradeNo - 商户订单号 - * @param {Object} updateData - 更新数据 - */ - async updatePaymentStatus(outTradeNo, updateData) { - try { - const db = getDB(); - - // 更新订单状态 - await db.execute( - `UPDATE payment_orders - SET status = ?, transaction_id = ?, paid_at = ? - WHERE out_trade_no = ?`, - [updateData.status, updateData.transactionId, updateData.paidAt, outTradeNo] - ); - - // 如果支付成功,更新用户支付状态 - if (updateData.status === 'paid') { - const [orders] = await db.execute( - 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', - [outTradeNo] - ); - - if (orders.length > 0) { - const userId = orders[0].user_id; - await db.execute( - 'UPDATE users SET payment_status = ? WHERE id = ?', - ['paid', userId] - ); - - console.log('用户支付状态更新成功:', { userId, outTradeNo }); - } - } - } catch (error) { - console.error('更新支付状态失败:', error); - throw error; - } - } - - /** - * 验证支付宝回调签名 - * @param {Object} params - 回调参数 - * @returns {boolean} 验证结果 - */ - verifyNotifySign(params) { - // 检查服务是否可用 - if (!this.isServiceAvailable()) { - console.error('支付宝服务未初始化,无法验证签名'); - return false; - } - - try { - return this.alipaySdk.checkNotifySign(params); - } catch (error) { - console.error('验证支付宝回调签名失败:', error); - return false; - } - } - - /** - * 生成订单号 - * @returns {string} 订单号 - */ - generateOrderNo() { - const timestamp = Date.now(); - const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0'); - return `ALI${timestamp}${random}`; - } -} - -module.exports = AlipayService; \ No newline at end of file diff --git a/services/matchingService.js b/services/matchingService.js deleted file mode 100644 index 380696e..0000000 --- a/services/matchingService.js +++ /dev/null @@ -1,1572 +0,0 @@ -const { getDB } = require('../database'); -const timeoutService = require('./timeoutService'); -const dayjs = require('dayjs'); - -/** - * 获取本地时区的日期字符串(YYYY-MM-DD格式) - * 确保在晚上12点正确切换到第二天 - * @param {Date} date - 可选的日期对象,默认为当前时间 - * @returns {string} 格式化的日期字符串 - */ -function getLocalDateString(date = new Date()) { - return dayjs(date).format('YYYY-MM-DD'); -} - -class MatchingService { - // 创建匹配订单(支持两种模式) - async createMatchingOrder(userId, matchingType = 'small', customAmount = null) { - const db = getDB(); - - try { - // 检查用户是否被拉黑 - const isBlacklisted = await timeoutService.isUserBlacklisted(userId); - if (isBlacklisted) { - throw new Error('您已被拉黑,无法参与匹配。如有疑问请联系管理员。'); - } - - // 检查用户审核状态、必要信息和余额 - const [userResult] = await db.execute( - `SELECT audit_status, - balance, - wechat_qr, - alipay_qr, - unionpay_qr, - bank_card, - business_license, - id_card_front, - id_card_back - FROM users - WHERE id = ?`, - [userId] - ); - - if (userResult.length === 0) { - throw new Error('用户不存在'); - } - - const user = userResult[0]; - - // 检查用户余额:只有负余额用户才能发起匹配 - // if (user.balance > 0) { - // throw new Error('只有余额为负数的用户才能发起匹配,这是为了防止公司资金损失的重要规则'); - // } - - // 检查用户审核状态 - if (user.audit_status !== 'approved') { - if (user.audit_status === 'pending') { - throw new Error('您的账户正在审核中,审核通过后才能参与匹配'); - } else if (user.audit_status === 'rejected') { - throw new Error('您的账户审核未通过,请联系管理员'); - } - } - - // 检查必要的收款信息是否已上传 - const missingItems = []; - if (!user.wechat_qr && !user.alipay_qr && !user.unionpay_qr) { - missingItems.push('收款码(微信/支付宝/云闪付至少一种)'); - } - if (!user.bank_card) { - missingItems.push('银行卡号'); - } - if (!user.business_license) { - missingItems.push('营业执照'); - } - if (!user.id_card_front || !user.id_card_back) { - missingItems.push('身份证正反面'); - } - - if (missingItems.length > 0) { - throw new Error(`请先上传以下信息:${missingItems.join('、')}`); - } - - await db.query('START TRANSACTION'); - - let totalAmount, maxCycles; - - if (matchingType === 'small') { - // 小额匹配:固定5000元金额 - totalAmount = 5000; - maxCycles = 1; - } else if (matchingType === 'large') { - // 大额匹配:用户自定义金额(最高5万) - if (!customAmount || customAmount < 3000 || customAmount > 50000) { - throw new Error('大额匹配金额必须在3000-50000之间'); - } - totalAmount = customAmount; - maxCycles = 1; - } else { - throw new Error('不支持的匹配类型'); - } - - // 创建匹配订单 - const [result] = await db.execute( - 'INSERT INTO matching_orders (initiator_id, amount, status, max_cycles, matching_type) VALUES (?, ?, "matching", ?, ?)', - [userId, totalAmount, maxCycles, matchingType] - ); - - const orderId = result.insertId; - - // 记录用户参与 - await db.execute( - 'INSERT INTO matching_records (matching_order_id, user_id, action, amount) VALUES (?, ?, "join", ?)', - [orderId, userId, totalAmount] - ); - - await db.query('COMMIT'); - - // 立即生成智能分配 - const allocations = await this.generateSmartAllocationsWithDB(orderId, userId); - - // 检查并触发系统账户反向匹配 - // await this.checkAndTriggerSystemMatching(); - - return { - orderId, - matchingType, - totalAmount, - allocations: allocations || [], - allocationCount: allocations ? allocations.length : 0 - }; - } catch (error) { - await db.query('ROLLBACK'); - console.error('创建匹配订单失败:', error); - throw error; - } - } - - /** - * 检查资金平衡并触发系统账户反向匹配 - * 当收款需求大于打款资金时,系统账户主动发起匹配 - */ - async checkAndTriggerSystemMatching() { - const db = getDB(); - - try { - // 计算当前待收款总额(负余额用户的资金缺口) - const [negativeBalanceResult] = await db.execute(` - SELECT SUM(ABS(balance)) as total_deficit - FROM users - WHERE is_system_account = FALSE AND balance < 0 - `); - - const totalDeficit = negativeBalanceResult[0].total_deficit || 0; - - // 计算当前待打款总额(pending状态的分配) - const [pendingPaymentsResult] = await db.execute(` - SELECT SUM(oa.amount) as total_pending - FROM transfers oa - JOIN users u ON oa.from_user_id = u.id - WHERE oa.status = 'pending' AND u.is_system_account = FALSE - `); - - const totalPendingPayments = pendingPaymentsResult[0].total_pending || 0; - - console.log(`资金平衡检查: 总资金缺口=${totalDeficit}, 待打款总额=${totalPendingPayments}`); - - // 如果收款需求大于打款资金,触发系统账户反向匹配 - if (totalDeficit > totalPendingPayments) { - const shortfall = totalDeficit - totalPendingPayments; - console.log(`检测到资金缺口: ${shortfall}元,触发系统账户反向匹配`); - - await this.createSystemReverseMatching(shortfall); - } - } catch (error) { - console.error('检查资金平衡失败:', error); - // 不抛出错误,避免影响主流程 - } - } - - /** - * 创建系统账户反向匹配 - * 系统账户作为付款方,向有资金缺口的用户打款 - * @param {number} targetAmount - 目标匹配金额 - */ - async createSystemReverseMatching(targetAmount) { - const db = getDB(); - - try { - // 获取可用的系统账户 - const [systemAccounts] = await db.execute(` - SELECT id, balance FROM users - WHERE is_system_account = TRUE AND balance > 1000 - ORDER BY balance DESC - LIMIT 1 - `); - - if (systemAccounts.length === 0) { - console.log('没有可用的系统账户进行反向匹配'); - return; - } - - const systemAccount = systemAccounts[0]; - - // 确定实际匹配金额(不超过系统账户余额的80%) - const maxMatchAmount = Math.min(targetAmount, systemAccount.balance * 0.8); - - if (maxMatchAmount < 1000) { - console.log('系统账户余额不足,无法进行反向匹配'); - return; - } - - // 创建系统反向匹配订单 - const [result] = await db.execute( - 'INSERT INTO matching_orders (initiator_id, amount, status, max_cycles, matching_type, is_system_reverse) VALUES (?, ?, "matching", 1, "system_reverse", TRUE)', - [systemAccount.id, maxMatchAmount] - ); - - const orderId = result.insertId; - - // 生成分配给负余额用户 - await this.generateSystemReverseAllocations(orderId, maxMatchAmount, systemAccount.id); - - console.log(`系统反向匹配创建成功: 订单ID=${orderId}, 金额=${maxMatchAmount}`); - - } catch (error) { - console.error('创建系统反向匹配失败:', error); - } - } - - /** - * 为系统反向匹配生成分配 - * @param {number} orderId - 匹配订单ID - * @param {number} totalAmount - 总金额 - * @param {number} systemUserId - 系统账户ID - */ - async generateSystemReverseAllocations(orderId, totalAmount, systemUserId) { - const db = getDB(); - - try { - // 获取负余额用户,按缺口大小排序 - const [negativeUsers] = await db.execute(` - SELECT id, balance, ABS(balance) as deficit - FROM users - WHERE is_system_account = FALSE AND balance < 0 - ORDER BY deficit DESC - LIMIT 10 - `); - - if (negativeUsers.length === 0) { - console.log('没有负余额用户需要反向匹配'); - return; - } - - // 按比例分配金额给负余额用户 - const totalDeficit = negativeUsers.reduce((sum, user) => sum + user.deficit, 0); - let remainingAmount = totalAmount; - - for (let i = 0; i < negativeUsers.length && remainingAmount > 0; i++) { - const user = negativeUsers[i]; - let allocationAmount; - - if (i === negativeUsers.length - 1) { - // 最后一个用户分配剩余金额 - allocationAmount = remainingAmount; - } else { - // 按比例分配 - const proportion = user.deficit / totalDeficit; - allocationAmount = Math.min( - Math.floor(totalAmount * proportion), - user.deficit, - remainingAmount - ); - } - - if (allocationAmount > 0) { - // 创建分配记录(系统账户向用户转账) - await db.execute( - 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status) VALUES (?, ?, ?, ?, 1, "pending")', - [orderId, systemUserId, user.id, allocationAmount] - ); - - remainingAmount -= allocationAmount; - console.log(`系统反向分配: ${allocationAmount}元 从系统账户${systemUserId} 到用户${user.id}`); - } - } - - } catch (error) { - console.error('生成系统反向分配失败:', error); - throw error; - } - } - - /** - * 生成大额匹配的随机金额分拆(15000以上) - * @param {number} totalAmount - 总金额 - * @returns {Array} 分拆后的金额数组 - */ - generateRandomLargeAmounts(totalAmount) { - const amounts = []; - let remaining = totalAmount; - const minAmount = 1000; // 最小单笔金额 - const maxAmount = 8000; // 最大单笔金额 - - while (remaining > maxAmount) { - // 生成随机金额,确保剩余金额至少还能分一笔 - const maxThisAmount = Math.min(maxAmount, remaining - minAmount); - const amount = Math.floor(Math.random() * (maxThisAmount - minAmount + 1)) + minAmount; - amounts.push(amount); - remaining -= amount; - } - - // 最后一笔是剩余金额 - if (remaining > 0) { - amounts.push(remaining); - } - - return amounts; - } - - /** - * 生成3笔分配(兼容旧版本接口) - * 确保不会分配给同一个用户 - * @param {number} orderId - 订单ID - * @param {Array} amounts - 金额数组 - * @param {number} initiatorId - 发起人ID - */ - /** - * 验证匹配金额是否符合业务规则 - * @param {number} userId - 用户ID - * @param {number} totalAmount - 总匹配金额 - * @param {Array} amounts - 分配金额数组 - * @returns {Object} 验证结果和建议金额 - */ - async validateMatchingAmount(userId, totalAmount, amounts) { - const db = getDB(); - - try { - // 获取昨天的日期(本地时区) - const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); - - // 获取前一天所有用户的出款总数(系统总出款) - const [systemOutboundResult] = await db.execute( - `SELECT SUM(oa.amount) as total_outbound - FROM transfers oa - JOIN users u ON oa.from_user_id = u.id - WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`, - [yesterdayStr] - ); - - const systemOutbound = systemOutboundResult[0].total_outbound || 0; - - // 获取前一天所有用户的具体出款金额(用于检查重复) - const [yesterdayAmountsResult] = await db.execute( - `SELECT DISTINCT oa.amount - FROM transfers oa - JOIN users u ON oa.from_user_id = u.id - WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`, - [yesterdayStr] - ); - - const yesterdayAmounts = yesterdayAmountsResult.map(row => row.amount); - - // 检查每笔金额是否与前一天的金额不同 - const duplicateAmounts = []; - for (const amount of amounts) { - if (yesterdayAmounts.includes(amount)) { - duplicateAmounts.push(amount); - } - } - - return { - isValid: duplicateAmounts.length === 0, - systemOutbound, - duplicateAmounts, - suggestedAmount: systemOutbound, - message: duplicateAmounts.length > 0 - ? `以下金额与前一天重复: ${duplicateAmounts.join(', ')}元` - : '匹配金额验证通过' - }; - } catch (error) { - console.error('验证匹配金额失败:', error); - return { - isValid: false, - systemOutbound: 0, - duplicateAmounts: [], - suggestedAmount: 0, - message: '验证匹配金额时发生错误' - }; - } - } - - /** - * 生成智能分配并创建数据库记录 - * @param {number} orderId - 订单ID - * @param {number} initiatorId - 发起人ID - * @returns {Promise} 返回分配结果数组 - */ - async generateSmartAllocationsWithDB(orderId, initiatorId) { - const db = getDB(); - - try { - // 获取订单总金额 - const [orderResult] = await db.execute( - 'SELECT amount FROM matching_orders WHERE id = ?', - [orderId] - ); - - if (orderResult.length === 0) { - throw new Error('匹配订单不存在'); - } - - const totalAmount = orderResult[0].amount; - - // 使用智能分配算法生成分配方案 - const allocations = await this.generateSmartAllocations(totalAmount, initiatorId); - - if (allocations.length === 0) { - throw new Error('无法生成有效的分配方案'); - } - - // 验证总金额(简化版验证) - const totalAllocated = allocations.reduce((sum, allocation) => sum + allocation.amount, 0); - if (Math.abs(totalAllocated - totalAmount) > 0.01) { - throw new Error(`分配金额不匹配:期望${totalAmount}元,实际分配${totalAllocated}元`); - } - - console.log(`智能分配验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 分配${allocations.length}笔`); - - // 创建分配记录 - const createdAllocations = []; - for (let i = 0; i < allocations.length; i++) { - const allocation = allocations[i]; - - // 设置出款日期为今天,可回款时间为明天的00:00:00 - const today = dayjs(); - const tomorrow = dayjs().add(1, 'day').startOf('day'); - - const [result] = await db.execute( - 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status, outbound_date, can_return_after) VALUES (?, ?, ?, ?, 1, "pending", CURDATE(), ?)', - [orderId, initiatorId, allocation.userId, allocation.amount, tomorrow.format('YYYY-MM-DD HH:mm:ss')] - ); - - // 添加分配ID到结果中 - const createdAllocation = { - ...allocation, - allocationId: result.insertId, - status: 'pending', - outboundDate: today.format('YYYY-MM-DD'), - canReturnAfter: tomorrow.toISOString() - }; - - createdAllocations.push(createdAllocation); - - console.log(`创建智能分配: ${allocation.amount}元 从用户${initiatorId} 到用户${allocation.userId}(${allocation.username}) [${allocation.userType}]`); - } - - return createdAllocations; - } catch (error) { - console.error('生成智能分配失败:', error); - throw error; - } - } - - /** - * 生成传统三笔分配(保留原方法用于兼容性) - * @param {number} orderId - 订单ID - * @param {Array} amounts - 分配金额数组 - * @param {number} initiatorId - 发起人ID - * @returns {Promise} - */ - async generateThreeAllocations(orderId, amounts, initiatorId) { - const db = getDB(); - - try { - // 获取订单总金额 - const [orderResult] = await db.execute( - 'SELECT amount FROM matching_orders WHERE id = ?', - [orderId] - ); - - if (orderResult.length === 0) { - throw new Error('匹配订单不存在'); - } - - const totalAmount = orderResult[0].amount; - - // 验证匹配金额是否符合业务规则 - const validation = await this.validateMatchingAmount(initiatorId, totalAmount, amounts); - if (!validation.isValid) { - throw new Error(`匹配金额不符合业务规则:${validation.message}。建议匹配金额:${validation.suggestedAmount}元`); - } - - // 记录验证信息 - console.log(`匹配金额验证通过: 用户${initiatorId}, 匹配金额${totalAmount}元, 前一天系统出款${validation.systemOutbound}元`); - - const usedTargetUsers = new Set(); // 记录已使用的目标用户 - - for (let i = 0; i < amounts.length; i++) { - const amount = amounts[i]; - - // 获取匹配目标,排除已使用的用户 - const targetUser = await this.getMatchingTargetExcluding(initiatorId, usedTargetUsers); - - if (!targetUser) { - throw new Error(`无法为第${i + 1}笔分配找到匹配目标`); - } - - // 记录已使用的目标用户 - usedTargetUsers.add(targetUser); - - // 创建分配记录,默认为第1轮 - // 设置出款日期为今天,可回款时间为明天的00:00:00 - const today = new Date(); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); - tomorrow.setHours(0, 0, 0, 0); - - // 将Date对象转换为MySQL兼容的字符串格式 - const tomorrowStr = tomorrow.toISOString().slice(0, 19).replace('T', ' '); - - await db.execute( - 'INSERT INTO transfers (matching_order_id, from_user_id, to_user_id, amount, cycle_number, status, outbound_date, can_return_after) VALUES (?, ?, ?, ?, 1, "pending", CURDATE(), ?)', - [orderId, initiatorId, targetUser, amount, tomorrowStr] - ); - - console.log(`创建分配: ${amount}元 从用户${initiatorId} 到用户${targetUser}`); - } - } catch (error) { - console.error('生成分配失败:', error); - throw error; - } - } - - /** - * 获取匹配目标用户 - * @param {number} excludeUserId - 要排除的用户ID - * @returns {number} 目标用户ID - */ - - - /** - * 获取匹配目标用户(排除指定用户集合) - * @param {number} excludeUserId - 要排除的发起人用户ID - * @param {Set} excludeUserIds - 要排除的用户ID集合 - * @returns {number} 目标用户ID - */ - async getMatchingTargetExcluding(excludeUserId, excludeUserIds = new Set()) { - const db = getDB(); - - try { - // 获取今天和昨天的日期(本地时区) - const today = new Date(); - const todayStr = getLocalDateString(today); - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayStr = getLocalDateString(yesterday); - - // 获取前一天打款的用户ID列表(需要排除) - const [yesterdayPayersResult] = await db.execute( - `SELECT DISTINCT oa.from_user_id - FROM transfers oa - WHERE DATE(oa.outbound_date) = ? AND oa.status = 'confirmed'`, - [yesterdayStr] - ); - const yesterdayPayers = yesterdayPayersResult.map(row => row.from_user_id); - - // 获取待确认/待处理/即将生成匹配金额总和超过0的普通用户(需要排除) - const [pendingUsersResult] = await db.execute( - `SELECT oa.to_user_id, SUM(oa.amount) as pending_amount - FROM transfers oa - JOIN users u ON oa.to_user_id = u.id - WHERE oa.status IN ('pending', 'processing', 'generating') - AND u.is_system_account = FALSE - GROUP BY oa.to_user_id - HAVING pending_amount > 0` - ); - const pendingUsers = pendingUsersResult.map(row => row.to_user_id); - - // 获取当天有转出订单的普通用户(需要排除) - const [todayPayersResult] = await db.execute( - `SELECT DISTINCT oa.from_user_id - FROM transfers oa - JOIN users u ON oa.from_user_id = u.id - WHERE DATE(oa.created_at) = ? - AND oa.status IN ('confirmed', 'pending', 'processing') - AND u.is_system_account = FALSE`, - [todayStr] - ); - const todayPayers = todayPayersResult.map(row => row.from_user_id); - - // 构建排除用户的条件(包括发起人、已使用的用户、昨天打款的用户、待处理用户、当天转出用户) - const excludeList = [ - excludeUserId, - ...Array.from(excludeUserIds), - ...yesterdayPayers, - ...pendingUsers, - ...todayPayers - ]; - const placeholders = excludeList.map(() => '?').join(','); - - // 第一优先级:最早成为负数且通过前面检查的普通用户 - // 使用最早的转出记录时间作为成为负数的参考时间 - const [earliestNegativeUsers] = await db.execute( - `SELECT u.id, u.balance, - (SELECT MIN(t.created_at) FROM transfers t - WHERE t.from_user_id = u.id AND t.status IN ('confirmed', 'received')) as first_transfer_time - FROM users u - WHERE u.id NOT IN (${placeholders}) - AND u.is_system_account = FALSE - AND u.balance < 0 - AND (SELECT COUNT(*) FROM transfers t - WHERE t.from_user_id = u.id AND t.status IN ('confirmed', 'received')) > 0 - ORDER BY first_transfer_time ASC, u.balance ASC, RAND() - LIMIT 1`, - excludeList - ); - - if (earliestNegativeUsers.length > 0) { - return earliestNegativeUsers[0].id; - } - - // 第二优先级:有可回款分配的普通用户(昨天或更早出款,今天可以回款) - // 但必须是负余额用户,且通过前面的检查 - const [returnableUsers] = await db.execute( - `SELECT DISTINCT oa.from_user_id as id, u.balance - FROM transfers oa - JOIN matching_orders mo ON oa.id = mo.id - JOIN users u ON oa.from_user_id = u.id - WHERE oa.from_user_id NOT IN (${placeholders}) - AND oa.status = 'confirmed' - AND oa.can_return_after <= NOW() - AND oa.return_date IS NULL - AND mo.status = 'matching' - AND u.balance < 0 - AND u.is_system_account = FALSE - ORDER BY oa.can_return_after ASC, u.balance ASC, RAND() - LIMIT 1`, - excludeList - ); - - if (returnableUsers.length > 0) { - return returnableUsers[0].id; - } - - // 第三优先级:其他负余额普通用户(余额为负数说明他们给其他用户转过钱,钱还没收回来) - const [negativeBalanceUsers] = await db.execute( - `SELECT id FROM users - WHERE id NOT IN (${placeholders}) - AND is_system_account = FALSE - AND balance < 0 - ORDER BY balance ASC, RAND() - LIMIT 1`, - excludeList - ); - - if (negativeBalanceUsers.length > 0) { - return negativeBalanceUsers[0].id; - } - - // 最后优先级:虚拟用户(系统账户) - const [systemUsers] = await db.execute( - `SELECT id FROM users - WHERE is_system_account = TRUE AND id NOT IN (${placeholders}) - ORDER BY balance DESC, RAND() - LIMIT 1`, - excludeList - ); - - if (systemUsers.length > 0) { - return systemUsers[0].id; - } - - // 如果连系统账户都没有,抛出错误 - throw new Error('没有可用的匹配目标:所有符合条件的用户都被排除'); - } catch (error) { - console.error('获取匹配目标失败:', error); - throw error; - } - } - - // 获取可用用户 - - - /** - * 生成智能分配金额 - * 1. 排除今天打款的用户 - * 2. 优先分配给负余额用户(余额+待确认收款为负数) - * 3. 每笔最高5000,不够再分配给虚拟用户 - * 4. 笔数3-10笔 - * @param {number} totalAmount - 总金额 - * @param {number} excludeUserId - 排除的用户ID(发起人) - * @returns {Promise} 分配金额数组 - */ - async generateSmartAllocations(totalAmount, excludeUserId) { - const db = getDB(); - const minAmount = 100; - const maxAmountPerTransfer = totalAmount; - const minTransfers = totalAmount > 5000 ? 4 : 3; - const maxTransfers = 10; - - try { - // 首先获取当前用户的城市、省份和区域信息 - const [currentUserResult] = await db.execute( - `SELECT city, province, district_id FROM users WHERE id = ?`, - [excludeUserId] - ); - - const currentUserCity = currentUserResult[0]?.city; - const currentUserProvince = currentUserResult[0]?.province; - const currentUserDistrictId = currentUserResult[0]?.district_id; - - // 获取负余额用户,按区县、城市、省份优先级排序 - let [userBalanceResult] = await db.execute( - `SELECT - u.id as user_id, - u.balance as current_balance, - u.city, - u.province, - u.district_id - FROM users u - WHERE u.is_system_account = FALSE - AND u.is_distribute = TRUE - AND u.id != ? - AND u.balance < -100 - AND u.audit_status = 'approved' - AND u.user_type != 'directly_operated' - AND u.payment_status = 'paid' - AND u.province = ? - ORDER BY - CASE - WHEN u.city = ? AND u.district_id = ? THEN 1 -- 相同城市且相同区县排第一 - WHEN u.city = ? THEN 2 -- 相同城市但不同区县排第二 - WHEN u.province = ? THEN 3 -- 相同省份但不同城市排第三 - ELSE 4 -- 其他省份排第四 - END, - u.balance ASC`, - [excludeUserId,currentUserProvince, currentUserCity, currentUserDistrictId, currentUserCity, currentUserProvince] - ); - - // 处理查询到的负余额用户 - const availableUsers = []; - - for (const user of userBalanceResult) { - // 确保余额是数字类型 - const currentBalance = parseFloat(user.current_balance) || 0; - - // 更新用户对象 - user.current_balance = currentBalance; - - // 查询用户的分配订单金额统计 - const [orderStatusResult] = await db.execute( - `SELECT - SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) as pending_amount - FROM transfers - WHERE to_user_id = ?`, - [user.user_id] - ); - - // 查询用户的分配订单金待确认金额统计 - const [orderStatusConfirmedResult] = await db.execute( - `SELECT - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as confirmed_amount - FROM transfers - WHERE to_user_id = ?`, - [user.user_id] - ); - //查询用户给其他用户已确认的金额统计(要减去,因为款项还没回来) - const [orderStatusConfirmedResultFrom] = await db.execute( - `SELECT - SUM(CASE WHEN status = 'confirmed' THEN amount ELSE 0 END) as confirmed_amount - FROM transfers - WHERE from_user_id = ?`, - [user.user_id] - ); - // 查询用户当天在matching_orders表中打出去的款项 - const today = getLocalDateString(); - const [todayOutflowResult] = await db.execute( - `SELECT - SUM(amount) as today_outflow - FROM matching_orders - WHERE initiator_id = ? AND DATE(updated_at) = ?`, - [user.user_id, today] - ); - - // 添加分配金额信息到用户对象 - const orderStatus = orderStatusResult[0] || { pending_amount: 0 }; - const todayOutflow = todayOutflowResult[0] || { today_outflow: 0 }; - const orderStatusConfirmedFrom = orderStatusConfirmedResultFrom[0] || { confirmed_amount: 0 }; - const orderStatusConfirmed = orderStatusConfirmedResult[0] || { confirmed_amount: 0 }; - user.today_outflow = parseFloat(todayOutflow.today_outflow) || 0; - user.pending_amount = parseFloat(orderStatus.pending_amount) || 0; - user.confirmed_amount = parseFloat(orderStatusConfirmed.confirmed_amount) || 0; - user.has_active_allocations = user.current_balance + user.pending_amount + user.confirmed_amount + user.today_outflow - orderStatusConfirmedFrom.confirmed_amount; - - - - // 所有查询到的用户都是负余额用户,直接添加到可用列表 - } - userBalanceResult = userBalanceResult.filter(user => user.has_active_allocations < -100); - userBalanceResult = userBalanceResult.sort((a, b) => { - const getPriority = (user) => { - if (user.district_id === currentUserDistrictId) return 1; // 同区县 - if (user.city === currentUserCity) return 2; // 同城市 - return 3; // 其他 - }; - - const priorityA = getPriority(a); - const priorityB = getPriority(b); - - if (priorityA !== priorityB) { - return priorityA - priorityB; // 优先级小的排前 - } - - // 同优先级里:越接近0(数值越大)排前 -> 使用降序 - return b.has_active_allocations - a.has_active_allocations; - }); - for (const user of userBalanceResult) { - if (maxTransfers > availableUsers.length + 1) { - if (minTransfers === 3 && availableUsers.length < 3) { - availableUsers.push(user); - } - if (minTransfers === 4) { - availableUsers.push(user); - } - } - console.log(user, '普通用户'); - } - - - console.log(`可参与分配的负余额用户数量: ${availableUsers.length}`); - - // 第二步:由于第一步已经筛选了余额小于0的用户,这里直接使用可用用户作为优先分配用户 - // 用户已按余额升序排列(最负的优先),然后按可分配金额降序排列 - const priorityUsers = availableUsers; // 所有可用用户都是负余额用户,无需再次筛选 - - // 第三步:获取虚拟用户作为备选 - const [virtualUsersResult] = await db.execute( - `SELECT id, username, balance FROM users - WHERE is_system_account = TRUE - ORDER BY balance DESC, RAND()` - ); - - // 计算分配方案 - const allocations = []; - let remainingAmount = totalAmount; - - // 优先分配给当前余额为负的用户 - for (const user of priorityUsers) { - if (remainingAmount <= 0 || allocations.length >= maxTransfers) break; - - // 计算该用户可接受的最大分配金额 - // 确保分配后用户余额不会变成正数 - const currentBalance = Math.abs(user.has_active_allocations); - // 使用随机分配而不是平均分配 - const remainingTransfers = minTransfers - allocations.length; - const minRequiredForRemaining = Math.max(0, (remainingTransfers - 1) * minAmount); // 为剩余转账预留的最小金额,确保不为负数 - - console.log(`用户${user.user_id}分配计算: remainingAmount=${remainingAmount}, remainingTransfers=${remainingTransfers}, minRequiredForRemaining=${minRequiredForRemaining}`); - - const maxRandomAllocation = Math.min( - currentBalance, // 不能超过安全分配额度,确保接收后余额不会变成正数 - maxAmountPerTransfer, // 单笔最大金额限制 - remainingAmount - minRequiredForRemaining // 确保剩余金额足够分配给后续转账 - ); - - // 生成随机分配金额,使用极度偏向大值的算法 - let maxUserAllocation = 0; - if (maxRandomAllocation >= minAmount) { - const range = maxRandomAllocation - minAmount; - if (range <= 0) { - maxUserAllocation = minAmount; - } else { - if (maxRandomAllocation > 1000) { - // 使用更均匀的分配策略 - const randomFactor = Math.random(); // 使用均匀分布 - - // 基础分配:在整个范围内更均匀分布,减少偏向性 - const baseOffset = Math.floor(range * 0.15); // 降低到15%的基础偏移 - const adjustedRange = range - baseOffset; - maxUserAllocation = Math.floor(randomFactor * adjustedRange) + minAmount + baseOffset; - - // 进一步减少额外增量的影响 - const bonusRange = Math.min(range * 0.1, maxRandomAllocation - maxUserAllocation); // 降低到10% - if (bonusRange > 0 && Math.random() > 0.7) { // 30%概率获得额外增量,进一步降低 - const bonus = Math.floor(Math.random() * bonusRange * 0.3); // 使用30%的bonus范围 - maxUserAllocation += bonus; - } - }else{ - maxUserAllocation = maxRandomAllocation - } - - - // 确保不超过最大限制 - maxUserAllocation = Math.min(maxUserAllocation, maxRandomAllocation); - } - } - console.log(maxUserAllocation, minAmount, '+++++++++++++++'); - - if (maxUserAllocation >= minAmount) { - allocations.push({ - userId: user.user_id, - username: user.username || `用户${user.user_id}`, - amount: maxUserAllocation, - userType: 'priority_user', - currentBalance: user.current_balance, - historicalNetBalance: user.historical_net_balance, - totalPendingInflow: user.total_pending_inflow, - availableForAllocation: user.available_for_allocation, - todayOutflow: user.today_outflow, - has_active_allocations: user.has_active_allocations - }); - remainingAmount -= maxUserAllocation; - } - } - - // 如果还有剩余金额且分配数量不足最小笔数,最后分配给虚拟用户 - const availableVirtualUsers = virtualUsersResult - - // 如果有剩余金额,优先检查现有非虚拟用户是否还能消化 - if (remainingAmount > 0) { - // 筛选出非虚拟用户分配记录 - - if (allocations.length > 0) { - let totalAvailableCapacity = 0; - const userCapacities = []; - - // 计算每个用户的剩余可分配容量 - for (const allocation of allocations) { - // 获取用户当前的实际余额状态(使用has_active_allocations作为实际可分配余额) - const maxSafeAmount = Math.abs(allocation.has_active_allocations); - const remainingCapacity = maxSafeAmount - allocation.amount; - - if (remainingCapacity > 0) { - userCapacities.push({ - allocation, - capacity: remainingCapacity - }); - totalAvailableCapacity += remainingCapacity; - } - } - - console.log(`现有用户剩余容量: ${totalAvailableCapacity}, 待分配金额: ${remainingAmount}`); - - // 如果现有用户能够消化剩余金额 - if (totalAvailableCapacity >= remainingAmount && userCapacities.length > 0 && allocations.length >= 3) { - // 按平均分配给这些用户,但需要检查每个用户的分配上限 - const averageAmount = Math.floor(remainingAmount / userCapacities.length); - let distributedAmount = 0; - let remainingToDistribute = remainingAmount; - - for (let i = 0; i < userCapacities.length; i++) { - const { allocation, capacity } = userCapacities[i]; - - // 计算本次可分配的金额 - let amountToAdd = 0; - - if (i === userCapacities.length - 1) { - // 最后一个用户分配剩余的所有金额,但不能超过其容量 - amountToAdd = Math.min(remainingToDistribute, capacity); - } else { - // 其他用户按平均分配,但不能超过其容量 - amountToAdd = Math.min(averageAmount, capacity); - } - - if (amountToAdd > 0) { - allocation.amount += amountToAdd; - distributedAmount += amountToAdd; - remainingToDistribute -= amountToAdd; - console.log(`为用户${allocation.userId}追加分配${amountToAdd}元,总分配${allocation.amount}元,剩余容量${capacity - amountToAdd}元`); - } - } - - // 更新实际分配的剩余金额 - remainingAmount = remainingToDistribute; - - if (remainingAmount === 0) { - console.log('剩余金额已全部分配给现有用户'); - } else { - console.log(`部分剩余金额已分配给现有用户,仍有${remainingAmount}元未分配`); - } - } - } - } - - // 如果仍有剩余金额,检查是否有未分配的用户可以消化剩余金额 - if (remainingAmount > 0) { - // 获取已分配的用户ID列表 - const allocatedUserIds = new Set(allocations.map(a => a.userId)); - - // 从原始用户列表中找到未分配的用户 - const unallocatedUsers = userBalanceResult.filter(user => !allocatedUserIds.has(user.user_id)); - - if (unallocatedUsers.length > 0) { - console.log(`发现${unallocatedUsers.length}个未分配的用户,剩余金额: ${remainingAmount}`); - - // 查找可分配金额大于剩余金额的用户 - for (const user of unallocatedUsers) { - const maxSafeAmount = Math.abs(user.has_active_allocations); - - if (maxSafeAmount >= remainingAmount) { - // 找到合适的用户,分配剩余金额 - allocations.push({ - userId: user.user_id, - username: user.username || `User${user.user_id}`, - amount: remainingAmount, - userType: 'priority_user', - currentBalance: user.current_balance, - availableForAllocation: user.has_active_allocations - }); - - console.log(`为未分配用户${user.user_id}分配剩余金额${remainingAmount}元`); - remainingAmount = 0; - break; - } - } - } - } - - // 如果仍有剩余金额,分配给虚拟用户 - if (remainingAmount > 0 && availableVirtualUsers.length > 0) { - const maxPossibleTransfers = Math.min((minTransfers - allocations.length) <= 0 ? 1 : minTransfers - allocations.length, availableVirtualUsers.length); - - // 生成随机分配金额数组 - const randomAmounts = this.generateRandomAmounts(remainingAmount, maxPossibleTransfers, minAmount, maxAmountPerTransfer); - - // 为每个随机金额分配虚拟用户 - for (let i = 0; i < randomAmounts.length && availableVirtualUsers.length > 0; i++) { - const randomIndex = Math.floor(Math.random() * availableVirtualUsers.length); - const virtualUser = availableVirtualUsers[randomIndex]; - - allocations.push({ - userId: virtualUser.id, - username: virtualUser.username, - amount: randomAmounts[i], - userType: 'virtual', - balance: virtualUser.balance - }); - - remainingAmount -= randomAmounts[i]; - availableVirtualUsers.splice(randomIndex, 1); - } - } - - // 检查是否有足够的用户来完成分配 - if (remainingAmount > 0 && allocations.length < minTransfers && availableVirtualUsers.length === 0) { - throw new Error('没有足够的可用用户来完成分配(避免重复分配给同一用户)'); - } - - // 确保至少有最小笔数 - // if (allocations.length < minTransfers) { - // throw new Error(`无法生成足够的分配:需要至少${minTransfers}笔,但只能生成${allocations.length}笔`); - // } - - // 精确控制总金额,避免超出预期 - const currentTotal = allocations.reduce((sum, a) => sum + a.amount, 0); - console.log('剩余金额处理前:', remainingAmount, '当前总分配金额:', currentTotal, '期望总金额:', totalAmount); - - if (remainingAmount > 0 && allocations.length > 0) { - // 检查是否会超出总金额 - if (currentTotal + remainingAmount <= totalAmount) { - console.log('将剩余金额', remainingAmount, '加到最后一笔分配'); - allocations[allocations.length - 1].amount += remainingAmount; - } else { - // 如果会超出,只加到刚好等于总金额的部分 - const allowedAmount = totalAmount - currentTotal; - if (allowedAmount > 0) { - - console.log('调整最后一笔分配,增加', allowedAmount, '元以达到精确总金额'); - allocations[allocations.length - 1].amount += allowedAmount; - } - } - remainingAmount = 0; // 重置剩余金额 - } - - console.log(`智能分配完成: 总金额${totalAmount}元,分配${allocations.length}笔`); - console.log('分配详情:', allocations.map(a => - `${a.amount}元 -> 用户${a.userId}(${a.username}) [${a.userType}]` - ).join(', ')); - - return allocations; - - } catch (error) { - console.error('智能分配失败:', error); - throw error; - } - } - - /** - * 生成随机分配金额数组(更均匀的分配策略) - * @param {number} totalAmount - 总金额 - * @param {number} transferCount - 分配笔数 - * @param {number} minAmount - 最小金额 - * @param {number} maxAmount - 最大金额 - * @returns {number[]} 随机金额数组 - */ - generateRandomAmounts(totalAmount, transferCount, minAmount, maxAmount) { - if (transferCount <= 0 || totalAmount < minAmount * transferCount) { - return []; - } - - // 使用更均匀的分配策略 - const amounts = []; - - // 首先为每笔分配最小金额 - for (let i = 0; i < transferCount; i++) { - amounts.push(minAmount); - } - - let remainingToDistribute = totalAmount - (minAmount * transferCount); - - // 计算平均每笔应该额外分配的金额 - const averageExtra = Math.floor(remainingToDistribute / transferCount); - - // 为每笔添加平均额外金额,但加入一些随机性 - for (let i = 0; i < transferCount && remainingToDistribute > 0; i++) { - // 计算这笔最多还能增加多少(不超过maxAmount) - const maxPossibleIncrease = Math.min( - maxAmount - amounts[i], - remainingToDistribute - ); - - if (maxPossibleIncrease > 0) { - // 在平均值附近随机分配,但控制在更小的范围内以保证更均匀 - const baseIncrease = Math.min(averageExtra, maxPossibleIncrease); - const randomVariation = Math.floor(baseIncrease * 0.15); // 减少到15%的随机变化 - const minIncrease = Math.max(0, baseIncrease - randomVariation); - const maxIncrease = Math.min(maxPossibleIncrease, baseIncrease + randomVariation); - - const increase = Math.floor(Math.random() * (maxIncrease - minIncrease + 1)) + minIncrease; - amounts[i] += increase; - remainingToDistribute -= increase; - } - } - - // 如果还有剩余金额,尽量均匀分配给还能接受的笔数 - while (remainingToDistribute > 0) { - const availableIndices = []; - for (let i = 0; i < transferCount; i++) { - if (amounts[i] < maxAmount) { - availableIndices.push(i); - } - } - - if (availableIndices.length === 0) { - break; // 无法继续分配 - } - - // 计算每个可用位置应该分配多少 - const perIndexAmount = Math.floor(remainingToDistribute / availableIndices.length); - const remainder = remainingToDistribute % availableIndices.length; - - // 为每个可用位置分配相等的金额 - for (let i = 0; i < availableIndices.length && remainingToDistribute > 0; i++) { - const index = availableIndices[i]; - const maxIncrease = Math.min(maxAmount - amounts[index], remainingToDistribute); - - if (maxIncrease > 0) { - // 基础分配金额 - let increase = Math.min(perIndexAmount, maxIncrease); - - // 如果是前几个位置,额外分配余数 - if (i < remainder) { - increase = Math.min(increase + 1, maxIncrease); - } - - amounts[index] += increase; - remainingToDistribute -= increase; - } - } - - // 如果所有位置都已达到最大值,退出循环 - if (perIndexAmount === 0 && remainder === 0) { - break; - } - } - - // 如果还有剩余金额无法分配,返回空数组表示失败 - if (remainingToDistribute > 0) { - return []; - } - - return amounts; - } - - // 生成3笔随机金额,总计指定金额(保留原方法用于兼容性) - generateThreeRandomAmounts(totalAmount) { - // 确保总金额足够分配三笔最小金额 - const minAmount = 500; - const maxAmount = Math.min(5000, totalAmount - 2 * minAmount); - - // 生成第一笔金额 (500-5000) - const amount1 = Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; - - // 生成第二笔金额 (500-剩余金额-500) - const remaining1 = totalAmount - amount1; - const maxAmount2 = Math.min(5000, remaining1 - minAmount); - const amount2 = Math.floor(Math.random() * (maxAmount2 - minAmount + 1)) + minAmount; - - // 第三笔是剩余金额 - const amount3 = totalAmount - amount1 - amount2; - - return [amount1, amount2, amount3]; - } - - // 生成随机金额(保留原方法用于其他地方) - - - /** - * 确认分配并创建转账记录 - * @param {number} allocationId - 分配ID - * @param {number} userId - 用户ID - * @param {number} transferAmount - 实际转账金额(用于校验) - * @param {string} description - 转账描述 - * @param {string} voucher - 转账凭证URL - * @returns {number} 转账记录ID - */ - async confirmAllocation(allocationId, userId, transferAmount = null, description = null, voucher = null) { - const db = getDB(); - - try { - await db.query('START TRANSACTION'); - - // 获取分配信息 - const [allocations] = await db.execute( - 'SELECT * FROM transfers WHERE id = ? AND from_user_id = ?', - [allocationId, userId] - ); - - if (allocations.length === 0) { - throw new Error('分配不存在或无权限'); - } - - const allocation = allocations[0]; - - // 校验转账金额(如果提供了转账金额) - if (transferAmount !== null) { - const expectedAmount = parseFloat(allocation.amount); - const actualAmount = parseFloat(transferAmount); - - if (Math.abs(expectedAmount - actualAmount) > 0.01) { - throw new Error(`转账金额不匹配!应转账 ${expectedAmount} 元,实际转账 ${actualAmount} 元`); - } - } - - // 检查分配状态 - if (allocation.status !== 'pending') { - throw new Error('该分配已处理,无法重复确认'); - } - - // 检查匹配订单是否已超时 - const [matchingOrder] = await db.execute( - 'SELECT * FROM matching_orders WHERE id = ?', - [allocation.matching_order_id] - ); - - if (matchingOrder.length === 0) { - throw new Error('匹配订单不存在'); - } - - // 检查订单是否已被取消(超时会导致订单被取消) - if (matchingOrder[0].status === 'cancelled') { - throw new Error('该匹配订单已超时取消,无法进行转账'); - } - - // 检查是否存在相关的超时转账记录 - const [timeoutTransfers] = await db.execute( - `SELECT COUNT(*) as count FROM transfers - WHERE (from_user_id = ? OR to_user_id = ?) - AND is_overdue = 1 - AND description LIKE ?`, - [userId, userId, `%匹配订单 ${allocation.matching_order_id}%`] - ); - - if (timeoutTransfers[0].count > 0) { - throw new Error('该匹配订单存在超时记录,无法继续转账。请联系管理员处理'); - } - - // 计算3小时后的截止时间 - const deadline = dayjs().add(3, 'hour').toDate(); - - // 更新转账记录状态为confirmed,跳过待确认环节 - const transferDescription = description || `匹配订单 ${allocation.matching_order_id} 第 ${allocation.cycle_number} 轮转账`; - const [transferResult] = await db.execute( - `UPDATE transfers - SET status = "confirmed", description = ?, deadline_at = ?, confirmed_at = NOW(), voucher_url = ? - WHERE id = ?`, - [ - transferDescription, - deadline, - voucher, - allocationId - ] - ); - - // 注意:发送方余额将在接收方确认收款时扣除,而不是在确认转账时扣除 - // 这样可以避免资金被锁定但收款方未确认的情况 - - // 记录确认动作 - await db.execute( - 'INSERT INTO matching_records (matching_order_id, user_id, action, amount, note) VALUES (?, ?, "confirm", ?, ?)', - [ - allocation.matching_order_id, - userId, - allocation.amount, - transferAmount ? `实际转账金额: ${transferAmount}` : null - ] - ); - - await db.query('COMMIT'); - - // 检查是否需要进入下一轮 - await this.checkCycleCompletion(allocation.matching_order_id, allocation.cycle_number); - - return transferResult.insertId; - } catch (error) { - await db.query('ROLLBACK'); - throw error; - } - } - - // 检查轮次完成情况 - async checkCycleCompletion(matchingOrderId, cycleNumber) { - const db = getDB(); - - try { - // 检查当前轮次是否全部确认 - const [pending] = await db.execute( - 'SELECT COUNT(*) as count FROM transfers WHERE matching_order_id = ? AND cycle_number = ? AND status = "pending"', - [matchingOrderId, cycleNumber] - ); - - if (pending[0].count === 0) { - // 当前轮次完成,检查是否需要下一轮 - const [order] = await db.execute( - 'SELECT * FROM matching_orders WHERE id = ?', - [matchingOrderId] - ); - - const currentOrder = order[0]; - - if (currentOrder.cycle_count + 1 < currentOrder.max_cycles) { - // 开始下一轮 - await db.execute( - 'UPDATE matching_orders SET cycle_count = cycle_count + 1 WHERE id = ?', - [matchingOrderId] - ); - - // 生成下一轮分配 - const amounts = this.generateThreeRandomAmounts(currentOrder.amount); - await this.generateThreeAllocations(matchingOrderId, amounts, currentOrder.initiator_id); - } else { - // 完成所有轮次 - await db.execute( - 'UPDATE matching_orders SET status = "completed" WHERE id = ?', - [matchingOrderId] - ); - - console.log(`匹配订单 ${matchingOrderId} 已完成所有轮次`); - - // 检查用户是否完成第三次匹配,如果是则给代理分佣 - await this.checkAndProcessAgentCommission(currentOrder.initiator_id); - } - } - } catch (error) { - console.error('检查轮次完成情况失败:', error); - throw error; - } - } - - // 获取用户的匹配订单 - async getUserMatchingOrders(userId, page = 1, limit = 10) { - const db = getDB(); - const offset = (parseInt(page) - 1) * parseInt(limit); - - - - try { - // 获取用户发起的订单 - const [orders] = await db.execute( - `SELECT mo.*, u.username as initiator_name,u.real_name as initiator_real_name - FROM matching_orders mo - JOIN users u ON mo.initiator_id = u.id - WHERE mo.initiator_id = ? - ORDER BY mo.created_at DESC - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, - [userId] - ); - - // 同时获取系统反向匹配订单(如果用户参与了分配) - const [systemOrders] = await db.execute( - `SELECT DISTINCT mo.*, u.username as initiator_name - FROM matching_orders mo - JOIN users u ON mo.initiator_id = u.id - JOIN transfers oa ON mo.id = oa.id - WHERE mo.is_system_reverse = TRUE AND oa.to_user_id = ? - ORDER BY mo.created_at DESC - LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`, - [userId] - ); - - // 合并订单列表 - const allOrders = [...orders, ...systemOrders]; - - // 按创建时间排序 - allOrders.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - - // 为每个订单获取分配信息 - for (let order of allOrders) { - const [allocations] = await db.execute( - `SELECT * FROM transfers WHERE matching_order_id = ? ORDER BY cycle_number, created_at`, - [order.id] - ); - order.allocations = allocations; - } - - return allOrders; - } catch (error) { - console.error('获取用户匹配订单失败:', error); - throw error; - } - } - - - - // 获取用户待处理的分配 - async getUserPendingAllocations(userId) { - const db = getDB(); - - try { - const [allocations] = await db.execute( - `(SELECT oa.*, mo.amount as total_amount, mo.status as order_status, u.username as to_user_name, - u.real_name as to_user_real_name, - DATE_ADD(oa.created_at, INTERVAL 150 MINUTE) as expected_deadline, - oa.outbound_date, - oa.return_date, - oa.can_return_after, - oa.confirmed_at - FROM transfers oa - JOIN matching_orders mo ON oa.matching_order_id = mo.id - JOIN users u ON oa.to_user_id = u.id - WHERE oa.from_user_id = ? AND oa.status = "pending" AND mo.status != "cancelled" - AND (oa.source_type IS NULL)) - UNION ALL - (SELECT oa.*, oa.amount as total_amount, 'active' as order_status, u.username as to_user_name, - u.real_name as to_user_real_name, - DATE_ADD(oa.created_at, INTERVAL 150 MINUTE) as expected_deadline, - oa.outbound_date, - oa.return_date, - oa.can_return_after, - oa.confirmed_at - FROM transfers oa - JOIN users u ON oa.to_user_id = u.id - WHERE oa.from_user_id = ? AND oa.status = "pending") - ORDER BY created_at ASC`, - [userId, userId] - ); - - // 检查每个分配的超时状态,但不过滤掉 - const allocationsWithTimeoutStatus = []; - for (const allocation of allocations) { - // 检查是否存在相关的超时转账记录 - const [timeoutTransfers] = await db.execute( - `SELECT COUNT(*) as count FROM transfers - WHERE (from_user_id = ? OR to_user_id = ?) - AND is_overdue = 1 - AND description LIKE ?`, - [userId, userId, `%匹配订单 ${allocation.matching_order_id}%`] - ); - - // 添加超时状态标记 - allocation.has_timeout_record = timeoutTransfers[0].count > 0; - allocationsWithTimeoutStatus.push(allocation); - } - // 检查并处理超时订单 - const now = new Date(); - // 隐藏系统账户身份并添加时效状态 - const processedAllocations = allocationsWithTimeoutStatus.map(allocation => { - const deadline = allocation.transfer_deadline || allocation.expected_deadline; - const deadlineDate = new Date(deadline); - const timeLeft = deadlineDate - now; - - // 计算剩余时间 - let timeStatus = 'normal'; - let timeLeftText = ''; - - if (timeLeft <= 0) { - timeStatus = 'expired'; - timeLeftText = '已超时'; - } else if (timeLeft <= 2.5 * 60 * 60 * 1000) { // 2.5小时内 - timeStatus = 'urgent'; - const hours = Math.floor(timeLeft / (60 * 60 * 1000)); - const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000)); - timeLeftText = hours > 0 ? `${hours}小时${minutes}分钟` : `${minutes}分钟`; - } else { - const hours = Math.floor(timeLeft / (60 * 60 * 1000)); - const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000)); - timeLeftText = `${hours}小时${minutes}分钟`; - } - - return { - ...allocation, - to_user_name: allocation.to_user_name || '匿名用户', - is_system_account: undefined, // 移除系统账户标识 - deadline: deadline, - time_status: timeStatus, - time_left: timeLeftText, - can_transfer: !allocation.has_timeout_record, // 是否可以转账 - timeout_reason: allocation.has_timeout_record ? '该匹配订单存在超时记录,无法继续转账' : null - }; - }); - - return processedAllocations; - } catch (error) { - console.error('获取用户待处理分配失败:', error); - throw error; - } - } - - // 检查并处理代理佣金 - async checkAndProcessAgentCommission(userId) { - const db = getDB(); - - try { - // 检查用户是否有代理关系 - const [agentRelation] = await db.execute( - 'SELECT agent_id, created_at FROM agent_merchants WHERE merchant_id = ?', - [userId] - ); - - if (agentRelation.length === 0) { - return; // 用户没有代理,无需处理 - } - - const agentId = agentRelation[0].agent_id; - const agentJoinTime = agentRelation[0].created_at; - - // 检查用户给他人转账的次数(状态为已收款,且转账时间在代理商入驻之后) - const [completedTransfers] = await db.execute( - 'SELECT COUNT(*) as count FROM transfers WHERE from_user_id = ? AND status = "received" AND created_at >= ?', - [userId, agentJoinTime] - ); - - const transferCount = completedTransfers[0].count; - - // 如果完成至少三次转账,给代理分佣 - if (transferCount >= 3) { - // 检查是否已经给过佣金(防止重复分佣) - const [existingCommission] = await db.execute( - 'SELECT id FROM agent_commission_records WHERE agent_id = ? AND merchant_id = ? AND description LIKE "%第三次转账%"', - [agentId, userId] - ); - - if (existingCommission.length === 0) { - // 计算佣金:399元的10% = 39.9元 - const commissionAmount = 399 * 0.10; - - // 记录佣金 - await db.execute( - 'INSERT INTO agent_commission_records (agent_id, merchant_id, commission_amount, commission_type, description, created_at) VALUES (?, ?, ?, "matching", "用户完成第三次转账获得的代理佣金", NOW())', - [agentId, userId, commissionAmount] - ); - - console.log(`用户 ${userId} 完成第三次转账,为代理 ${agentId} 分佣 ${commissionAmount} 元`); - } - } - } catch (error) { - console.error('处理代理佣金失败:', error); - // 不抛出错误,避免影响主流程 - } - } -} - -module.exports = new MatchingService(); \ No newline at end of file diff --git a/services/minioService.js b/services/minioService.js deleted file mode 100644 index 9355f5a..0000000 --- a/services/minioService.js +++ /dev/null @@ -1,293 +0,0 @@ -const { createMinioClient, minioConfig, getPublicUrl } = require('../config/minio'); -const path = require('path'); -const crypto = require('crypto'); - -/** - * MinIO 文件服务 - * 提供文件上传、删除、获取等功能 - */ -class MinioService { - constructor() { - this.client = createMinioClient(); - } - - /** - * 生成唯一文件名 - * @param {string} originalName - 原始文件名 - * @returns {string} 唯一文件名 - */ - generateUniqueFileName(originalName) { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - const timestamp = Date.now(); - const randomString = crypto.randomBytes(8).toString('hex'); - const ext = path.extname(originalName); - return `${year}/${month}/${day}/${timestamp}_${randomString}${ext}`; - } - - /** - * 根据文件类型获取存储桶名称 - * @param {string} type - 文件类型 (avatar, product, document) - * @returns {string} 存储桶名称 - */ - getBucketName(type = 'document') { - const bucketMap = { - 'avatar': minioConfig.buckets.avatars, - 'product': minioConfig.buckets.products, - 'document': minioConfig.buckets.documents - }; - return bucketMap[type] || minioConfig.buckets.documents; - } - - /** - * 上传单个文件 - * @param {Buffer} fileBuffer - 文件缓冲区 - * @param {string} originalName - 原始文件名 - * @param {string} mimeType - 文件MIME类型 - * @param {string} type - 文件类型 - * @returns {Promise} 上传结果 - */ - async uploadFile(fileBuffer, originalName, mimeType, type = 'document') { - try { - const bucketName = this.getBucketName(type); - const fileName = this.generateUniqueFileName(originalName); - - // 设置文件元数据 - const metaData = { - 'Content-Type': mimeType, - 'Original-Name': encodeURIComponent(originalName), - 'Upload-Time': new Date().toISOString() - }; - - // 上传文件到MinIO - await this.client.putObject(bucketName, fileName, fileBuffer, fileBuffer.length, metaData); - - // 生成访问URL - const url = getPublicUrl(bucketName, fileName); - - return { - success: true, - data: { - filename: fileName, - originalname: originalName, - mimetype: mimeType, - size: fileBuffer.length, - bucket: bucketName, - path: `${bucketName}/${fileName}`, - url: url - } - }; - } catch (error) { - console.error('MinIO文件上传失败:', error); - throw new Error(`文件上传失败: ${error.message}`); - } - } - - /** - * 迁移专用:上传文件到指定存储桶和路径 - * @param {string} bucketName - 存储桶名称 - * @param {string} filePath - 文件路径 - * @param {Buffer} fileBuffer - 文件缓冲区 - * @param {string} mimeType - 文件MIME类型 - * @returns {Promise} 上传结果 - */ - async uploadFileForMigration(bucketName, filePath, fileBuffer, mimeType) { - try { - // 设置文件元数据 - const metaData = { - 'Content-Type': mimeType, - 'Upload-Time': new Date().toISOString() - }; - - // 上传文件到MinIO - await this.client.putObject(bucketName, filePath, fileBuffer, fileBuffer.length, metaData); - - // 生成访问URL - const url = getPublicUrl(bucketName, filePath); - - return { - success: true, - data: { - filename: filePath, - mimetype: mimeType, - size: fileBuffer.length, - bucket: bucketName, - path: `${bucketName}/${filePath}`, - url: url - } - }; - } catch (error) { - console.error('MinIO文件迁移上传失败:', error); - throw new Error(`文件迁移上传失败: ${error.message}`); - } - } - - /** - * 上传多个文件 - * @param {Array} files - 文件数组,每个文件包含 {buffer, originalName, mimeType} - * @param {string} type - 文件类型 - * @returns {Promise} 上传结果数组 - */ - async uploadMultipleFiles(files, type = 'document') { - try { - const uploadPromises = files.map(file => - this.uploadFile(file.buffer, file.originalName, file.mimeType, type) - ); - - const results = await Promise.all(uploadPromises); - const uploadedFiles = results.map(result => result.data); - - return { - success: true, - data: { - files: uploadedFiles, - urls: uploadedFiles.map(file => file.url), - count: uploadedFiles.length - } - }; - } catch (error) { - console.error('MinIO多文件上传失败:', error); - throw new Error(`多文件上传失败: ${error.message}`); - } - } - - /** - * 删除文件 - * @param {string} bucketName - 存储桶名称 - * @param {string} fileName - 文件名 - * @returns {Promise} 删除结果 - */ - async deleteFile(bucketName, fileName) { - try { - await this.client.removeObject(bucketName, fileName); - console.log(`✅ 文件删除成功: ${bucketName}/${fileName}`); - return true; - } catch (error) { - console.error('MinIO文件删除失败:', error); - throw new Error(`文件删除失败: ${error.message}`); - } - } - - /** - * 批量删除文件 - * @param {string} bucketName - 存储桶名称 - * @param {Array} fileNames - 文件名数组 - * @returns {Promise} 删除结果 - */ - async deleteMultipleFiles(bucketName, fileNames) { - try { - const deletePromises = fileNames.map(fileName => - this.deleteFile(bucketName, fileName) - ); - - await Promise.all(deletePromises); - - return { - success: true, - deletedCount: fileNames.length, - message: `成功删除${fileNames.length}个文件` - }; - } catch (error) { - console.error('MinIO批量删除失败:', error); - throw new Error(`批量删除失败: ${error.message}`); - } - } - - /** - * 检查文件是否存在 - * @param {string} bucketName - 存储桶名称 - * @param {string} fileName - 文件名 - * @returns {Promise} 文件是否存在 - */ - async fileExists(bucketName, fileName) { - try { - await this.client.statObject(bucketName, fileName); - return true; - } catch (error) { - if (error.code === 'NotFound') { - return false; - } - throw error; - } - } - - /** - * 获取文件信息 - * @param {string} bucketName - 存储桶名称 - * @param {string} fileName - 文件名 - * @returns {Promise} 文件信息 - */ - async getFileInfo(bucketName, fileName) { - try { - const stat = await this.client.statObject(bucketName, fileName); - return { - size: stat.size, - lastModified: stat.lastModified, - etag: stat.etag, - contentType: stat.metaData['content-type'], - originalName: decodeURIComponent(stat.metaData['original-name'] || fileName) - }; - } catch (error) { - console.error('获取文件信息失败:', error); - throw new Error(`获取文件信息失败: ${error.message}`); - } - } - - /** - * 生成预签名URL(用于临时访问) - * @param {string} bucketName - 存储桶名称 - * @param {string} fileName - 文件名 - * @param {number} expiry - 过期时间(秒),默认7天 - * @returns {Promise} 预签名URL - */ - async getPresignedUrl(bucketName, fileName, expiry = 7 * 24 * 60 * 60) { - try { - const url = await this.client.presignedGetObject(bucketName, fileName, expiry); - return url; - } catch (error) { - console.error('生成预签名URL失败:', error); - throw new Error(`生成预签名URL失败: ${error.message}`); - } - } - - /** - * 列出存储桶中的文件 - * @param {string} bucketName - 存储桶名称 - * @param {string} prefix - 文件前缀 - * @param {number} limit - 限制数量 - * @returns {Promise} 文件列表 - */ - async listFiles(bucketName, prefix = '', limit = 100) { - try { - const files = []; - const stream = this.client.listObjects(bucketName, prefix, true); - - return new Promise((resolve, reject) => { - stream.on('data', (obj) => { - if (files.length < limit) { - files.push({ - name: obj.name, - size: obj.size, - lastModified: obj.lastModified, - etag: obj.etag, - url: getPublicUrl(bucketName, obj.name) - }); - } - }); - - stream.on('end', () => resolve(files)); - stream.on('error', reject); - }); - } catch (error) { - console.error('列出文件失败:', error); - throw new Error(`列出文件失败: ${error.message}`); - } - } -} - -// 创建单例实例 -const minioService = new MinioService(); - -module.exports = minioService; \ No newline at end of file diff --git a/services/timeoutService.js b/services/timeoutService.js deleted file mode 100644 index 794d018..0000000 --- a/services/timeoutService.js +++ /dev/null @@ -1,380 +0,0 @@ -const { getDB } = require('../database'); -const { logger, auditLogger } = require('../config/logger'); - -class TimeoutService { - /** - * 检查转账超时情况 - * 标记超时转账和风险用户,自动取消超过2.5小时的pending转账 - */ - async checkTransferTimeouts() { - const db = getDB(); - - try { - // 只在调试模式下输出开始检查的日志 - // console.log('开始检查转账超时情况...'); - - // 1. 查找所有超时的转账记录(有deadline_at的) - const [overdueTransfers] = await db.execute( - `SELECT t.*, u.username, u.real_name - FROM transfers t - JOIN users u ON t.from_user_id = u.id - WHERE t.status = 'pending' - AND t.deadline_at IS NOT NULL - AND t.deadline_at < NOW() - AND t.is_overdue = 0` - ); - - // 2. 查找所有pending状态超过2.5小时的转账记录 - const [longPendingTransfers] = await db.execute( - `SELECT t.*, u.username, u.real_name - FROM transfers t - JOIN users u ON t.from_user_id = u.id - WHERE t.status = 'pending' - AND t.created_at < DATE_SUB(NOW(), INTERVAL 150 MINUTE)` - ); - - let hasWork = false; - - // 处理有deadline的超时转账 - if (overdueTransfers.length > 0) { - hasWork = true; - console.log(`⚠️ 发现 ${overdueTransfers.length} 笔超时转账,开始处理...`); - - for (const transfer of overdueTransfers) { - await this.handleOverdueTransfer(transfer); - } - } - // 处理超过2.5小时的pending转账 - if (longPendingTransfers.length > 0) { - hasWork = true; - console.log(`⚠️ 发现 ${longPendingTransfers.length} 笔超过2.5小时的pending转账,开始自动取消...`); - - for (const transfer of longPendingTransfers) { - await this.handleLongPendingTransfer(transfer); - } - } - - if (hasWork) { - console.log('✅ 转账超时检查完成'); - } - - } catch (error) { - console.error('检查转账超时失败:', error); - logger.error('Transfer timeout check failed', { error: error.message }); - } - } - - /** - * 处理超时转账 - * @param {Object} transfer - 转账记录 - */ - async handleOverdueTransfer(transfer) { - const db = getDB(); - - try { - await db.query('START TRANSACTION'); - - // 标记转账为超时和坏账 - await db.execute( - 'UPDATE transfers SET is_overdue = 1, is_bad_debt = 1, overdue_at = NOW() WHERE id = ?', - [transfer.id] - ); - - // 标记用户为风险用户 - await db.execute( - `UPDATE users SET - is_risk_user = 1, - risk_reason = CONCAT(IFNULL(risk_reason, ''), '转账超时(转账ID: ', ?, ', 金额: ', ?, '元, 超时时间: ', NOW(), '); ') - WHERE id = ?`, - [transfer.id, transfer.amount, transfer.from_user_id] - ); - - await db.query('COMMIT'); - - // 记录审计日志 - auditLogger.info('Transfer marked as overdue and bad debt, user marked as risk', { - transferId: transfer.id, - userId: transfer.from_user_id, - username: transfer.username, - amount: transfer.amount, - deadlineAt: transfer.deadline_at - }); - - console.log(`转账 ${transfer.id} 已标记为超时和坏账,用户 ${transfer.username}(ID: ${transfer.from_user_id}) 已标记为风险用户`); - - } catch (error) { - await db.query('ROLLBACK'); - console.error(`处理超时转账 ${transfer.id} 失败:`, error); - throw error; - } - } - - /** - * 处理超过2.5小时的pending转账 - * @param {Object} transfer - 转账记录 - */ - async handleLongPendingTransfer(transfer) { - const db = getDB(); - - try { - await db.query('START TRANSACTION'); - - // 将转账状态改为cancelled - await db.execute( - 'UPDATE transfers SET status = "cancelled", updated_at = NOW() WHERE id = ?', - [transfer.id] - ); - - // 如果有关联的matching_order_id,检查并更新matching_orders状态 - if (transfer.matching_order_id) { - // 检查该matching_order下是否还有非cancelled状态的transfers - const [remainingTransfers] = await db.execute( - 'SELECT COUNT(*) as count FROM transfers WHERE matching_order_id = ? AND status != "cancelled"', - [transfer.matching_order_id] - ); - - // 如果所有关联的transfers都是cancelled状态,则更新matching_order状态为cancelled - if (remainingTransfers[0].count === 0) { - await db.execute( - 'UPDATE matching_orders SET status = "cancelled", updated_at = NOW() WHERE id = ?', - [transfer.matching_order_id] - ); - - console.log(`匹配订单 ${transfer.matching_order_id} 的所有转账都已取消,订单状态已更新为cancelled`); - } - } - - await db.query('COMMIT'); - - // 记录审计日志 - auditLogger.info('Long pending transfer auto-cancelled', { - transferId: transfer.id, - userId: transfer.from_user_id, - username: transfer.username, - amount: transfer.amount, - createdAt: transfer.created_at, - matchingOrderId: transfer.matching_order_id - }); - - console.log(`转账 ${transfer.id} 超过2.5小时未处理,已自动取消 (用户: ${transfer.username}, 金额: ${transfer.amount}元)`); - - } catch (error) { - await db.query('ROLLBACK'); - console.error(`处理长时间pending转账 ${transfer.id} 失败:`, error); - throw error; - } - } - - /** - * 获取风险用户列表 - * @param {Object} filters - 筛选条件 - * @param {Object} pagination - 分页参数 - * @returns {Object} 风险用户列表和分页信息 - */ - async getRiskUsers(filters = {}, pagination = {}) { - const db = getDB(); - const { page = 1, limit = 10 } = pagination; - const pageNum = parseInt(page, 10) || 1; - const limitNum = parseInt(limit, 10) || 10; - const offset = (pageNum - 1) * limitNum; - - let whereClause = 'WHERE is_risk_user = 1'; - const params = []; - - // 构建查询条件 - if (filters.is_blacklisted !== undefined) { - whereClause += ' AND is_blacklisted = ?'; - params.push(filters.is_blacklisted); - } - - if (filters.username) { - whereClause += ' AND username LIKE ?'; - params.push(`%${filters.username}%`); - } - - try { - // 获取总数 - const [countResult] = await db.execute( - `SELECT COUNT(*) as total FROM users ${whereClause}`, - params - ); - const total = countResult[0].total; - - // 获取数据 - const [users] = await db.execute( - `SELECT id, username, real_name, is_risk_user, is_blacklisted, - risk_reason, blacklist_reason, blacklisted_at, created_at,phone - FROM users - ${whereClause} - ORDER BY created_at DESC - LIMIT ${limitNum} OFFSET ${offset}`, - params - ); - - return { - users, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - }; - } catch (error) { - logger.error('Failed to get risk users', { error: error.message, filters }); - throw error; - } - } - - /** - * 拉黑用户 - * @param {number} userId - 用户ID - * @param {string} reason - 拉黑原因 - * @param {number} operatorId - 操作员ID - */ - async blacklistUser(userId, reason, operatorId) { - const db = getDB(); - - try { - // 检查用户是否存在 - const [users] = await db.execute( - 'SELECT id, username, phone FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - throw new Error('用户不存在'); - } - - const user = users[0]; - - if (user.is_blacklisted) { - throw new Error('用户已被拉黑'); - } - - // 拉黑用户 - await db.execute( - `UPDATE users SET - is_blacklisted = 1, - blacklist_reason = ?, - blacklisted_at = NOW() - WHERE id = ?`, - [reason, userId] - ); - - // 记录审计日志 - auditLogger.info('User blacklisted', { - userId, - username: user.username, - reason, - operatorId - }); - - logger.info('User blacklisted successfully', { userId, operatorId }); - - } catch (error) { - logger.error('Failed to blacklist user', { - error: error.message, - userId, - operatorId - }); - throw error; - } - } - - /** - * 解除拉黑 - * @param {number} userId - 用户ID - * @param {number} operatorId - 操作员ID - */ - async unblacklistUser(userId, operatorId) { - const db = getDB(); - - try { - // 检查用户是否存在 - const [users] = await db.execute( - 'SELECT id, username, is_blacklisted FROM users WHERE id = ?', - [userId] - ); - - if (users.length === 0) { - throw new Error('用户不存在'); - } - - const user = users[0]; - - if (!user.is_blacklisted) { - throw new Error('用户未被拉黑'); - } - - // 解除拉黑 - await db.execute( - `UPDATE users SET - is_blacklisted = 0, - blacklist_reason = NULL, - blacklisted_at = NULL - WHERE id = ?`, - [userId] - ); - - // 记录审计日志 - auditLogger.info('User unblacklisted', { - userId, - username: user.username, - operatorId - }); - - logger.info('User unblacklisted successfully', { userId, operatorId }); - - } catch (error) { - logger.error('Failed to unblacklist user', { - error: error.message, - userId, - operatorId - }); - throw error; - } - } - - /** - * 检查用户是否被拉黑 - * @param {number} userId - 用户ID - * @returns {boolean} 是否被拉黑 - */ - async isUserBlacklisted(userId) { - const db = getDB(); - - try { - const [users] = await db.execute( - 'SELECT is_blacklisted FROM users WHERE id = ?', - [userId] - ); - - return users.length > 0 && users[0].is_blacklisted === 1; - } catch (error) { - logger.error('Failed to check user blacklist status', { - error: error.message, - userId - }); - throw error; - } - } - - /** - * 启动定时检查任务 - * 每5分钟检查一次转账超时情况 - */ - startTimeoutChecker() { - console.log('启动转账超时检查定时任务...'); - - // 立即执行一次 - this.checkTransferTimeouts(); - - // 每5分钟执行一次 - setInterval(() => { - this.checkTransferTimeouts(); - }, 5 * 1000); // 5秒 - } -} - -module.exports = new TimeoutService(); \ No newline at end of file diff --git a/services/transferService.js b/services/transferService.js deleted file mode 100644 index c4e5fb4..0000000 --- a/services/transferService.js +++ /dev/null @@ -1,1192 +0,0 @@ -const {getDB} = require('../database'); -const {logger, auditLogger} = require('../config/logger'); -const {AppError} = require('../middleware/errorHandler'); -const {TRANSFER_TYPES, TRANSFER_STATUS, ERROR_CODES, HTTP_STATUS} = require('../config/constants'); - -class TransferService { - // 创建转账记录 - async createTransfer(fromUserId, transferData) { - const {to_user_id, amount, transfer_type, description, voucher_url} = transferData; - const db = getDB(); - - try { - // 验证用户是否存在 - await this.validateUser(to_user_id); - - // 验证转账类型 - if (!Object.values(TRANSFER_TYPES).includes(transfer_type)) { - throw new AppError('无效的转账类型', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 检查余额(如果是用户转账)- 允许负余额转账 - if (transfer_type === TRANSFER_TYPES.USER_TO_USER || transfer_type === TRANSFER_TYPES.USER_TO_SYSTEM) { - if (!fromUserId) { - throw new AppError('用户转账必须指定发送方用户', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - // 获取当前余额但不检查是否足够,允许负余额转账 - await this.checkUserBalance(fromUserId, amount); - } - - // 系统转账时,from_user_id 设为 null - const actualFromUserId = transfer_type === TRANSFER_TYPES.SYSTEM_TO_USER ? null : fromUserId; - - // 生成批次ID - const batch_id = this.generateBatchId(); - - // 插入转账记录 - const currentTime = new Date(); - const [result] = await db.execute( - `INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, status, description, - voucher_url, batch_id, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [actualFromUserId, to_user_id, amount, transfer_type, TRANSFER_STATUS.PENDING, description || null, voucher_url || null, batch_id, currentTime] - ); - - const transferId = result.insertId; - - // 记录审计日志 - auditLogger.info('Transfer created', { - transferId, - fromUserId, - toUserId: to_user_id, - amount, - transferType: transfer_type, - batchId: batch_id - }); - - logger.info('Transfer created successfully', {transferId, fromUserId, amount}); - - return { - transfer_id: transferId, - batch_id, - status: TRANSFER_STATUS.PENDING - }; - } catch (error) { - logger.error('Failed to create transfer', { - error: error.message, - fromUserId, - transferData - }); - throw error; - } - } - - // 管理员解除坏账 - async removeBadDebt(transferId, adminId, reason) { - const db = getDB(); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - if (!transfer.is_bad_debt) { - throw new AppError('该转账未被标记为坏账', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 解除坏账标记 - await db.execute( - 'UPDATE transfers SET is_bad_debt = 0 WHERE id = ?', - [transferId] - ); - - // 记录管理员操作日志 - await db.execute( - `INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, - created_at) - VALUES (?, 'remove_bad_debt', 'transfer', ?, ?, NOW())`, - [adminId, transferId, reason || `管理员解除转账${transferId}的坏账标记`] - ); - - // 记录审计日志 - auditLogger.info('Bad debt removed by admin', { - transferId, - adminId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - reason - }); - - logger.info('Bad debt removed successfully', {transferId, adminId, reason}); - - return {success: true}; - } catch (error) { - logger.error('Failed to remove bad debt', { - error: error.message, - transferId, - adminId - }); - throw error; - } - } - - // 确认转账 - async confirmTransfer(transferId, note, operatorId) { - const mysql = require('mysql2/promise'); - const {dbConfig} = require('../database'); - - // 创建单独的连接用于事务处理 - const connection = await mysql.createConnection(dbConfig); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - if (transfer.status !== TRANSFER_STATUS.PENDING) { - throw new AppError('转账记录状态不允许确认', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 检查是否为坏账 - if (transfer.is_bad_debt) { - throw new AppError('该转账已被标记为坏账,无法确认。请联系管理员处理', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 开始事务 - await connection.beginTransaction(); - - try { - // 更新转账状态 - await connection.execute( - 'UPDATE transfers SET status = ? WHERE id = ?', - [TRANSFER_STATUS.CONFIRMED, transferId] - ); - - // 如果存在匹配订单ID,更新匹配订单状态 - if (transfer.matching_order_id) { - // 查询该匹配订单下所有transfers的状态 - const [allTransfers] = await connection.execute( - `SELECT status - FROM transfers - WHERE matching_order_id = ?`, - [transfer.matching_order_id] - ); - - let matchingOrderStatus; - - // 根据所有相关transfers的状态来决定matching_order的状态 - const transferStatuses = allTransfers.map(t => t.status); - console.log(transferStatuses, 'transferStatuses'); - - if (transferStatuses.every(status => status === 'cancelled' || status === 'rejected' || status === 'not_received')) { - // 如果所有transfers都被取消/拒绝/未收到,匹配订单标记为已完成 - matchingOrderStatus = 'completed'; - } else if (transferStatuses.every(status => status === 'received')) { - // 如果所有transfers都已收到,匹配订单完成 - matchingOrderStatus = 'completed'; - } else if (transferStatuses.includes('cancelled') || transferStatuses.includes('rejected') || transferStatuses.includes('not_received') || transferStatuses.some(status => status === 'confirmed' || status === 'received')) { - // 如果有任何一个transfer被取消/拒绝/未收到,或者有transfers已确认或已收到,匹配订单为进行中状态 - matchingOrderStatus = 'matching'; - } else { - // 其他情况为待处理状态 - matchingOrderStatus = 'matching'; - } - - await connection.execute( - `UPDATE matching_orders - SET status = ?, - updated_at = NOW() - WHERE id = ?`, - [matchingOrderStatus, transfer.matching_order_id] - ); - - logger.info('Matching order status updated after transfer confirmation', { - matchingOrderId: transfer.matching_order_id, - transferId: transferId, - newMatchingOrderStatus: matchingOrderStatus, - allTransferStatuses: transferStatuses - }); - } - - // 注意:发送方余额将在接收方确认收款时扣除,而不是在确认转账时扣除 - // 这样可以避免资金被锁定但收款方未确认的情况 - - await connection.commit(); - - // 记录审计日志 - auditLogger.info('Transfer confirmed', { - transferId, - operatorId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount - }); - - logger.info('Transfer confirmed successfully', {transferId, operatorId}); - - return {success: true}; - } catch (error) { - await connection.rollback(); - throw error; - } - } catch (error) { - logger.error('Failed to confirm transfer', { - error: error.message, - transferId, - operatorId - }); - throw error; - } finally { - await connection.end(); - } - } - - // 获取转账列表 - async getTransfers(filters = {}, pagination = {}, user_type = 'user_to_user') { - const db = getDB(); - const {page = 1, limit = 10, sort = 'created_at', order = 'desc'} = pagination; - const pageNum = parseInt(page, 10) || 1; - const limitNum = parseInt(limit, 10) || 10; - const offset = (pageNum - 1) * limitNum; - - let whereClause = 'WHERE '; - const params = []; - whereClause += `t.transfer_type='${user_type}'`; - // 构建查询条件 - if (filters.user_id) { - whereClause += ' AND (t.from_user_id = ? OR t.to_user_id = ?)'; - params.push(filters.user_id, filters.user_id); - } - - if (filters.status) { - whereClause += ' AND t.status = ?'; - params.push(filters.status); - } - - if (filters.transfer_type) { - whereClause += ' AND t.transfer_type = ?'; - params.push(filters.transfer_type); - } - - if (filters.start_date) { - whereClause += ' AND t.created_at >= ?'; - params.push(filters.start_date); - } - - if (filters.end_date) { - whereClause += ' AND t.created_at <= ?'; - params.push(filters.end_date); - } - - if (filters.search) { - whereClause += ' AND (fu.username LIKE ? OR fu.real_name LIKE ? OR tu.username LIKE ? OR tu.real_name LIKE ?)'; - const searchPattern = `%${filters.search}%`; - params.push(searchPattern, searchPattern, searchPattern, searchPattern); - } - - // 构建排序子句 - const validSortFields = ['id', 'amount', 'created_at', 'updated_at', 'status']; - const sortField = validSortFields.includes(sort) ? sort : 'created_at'; - const sortOrder = order && order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; - - const orderClause = `ORDER BY t.${sortField} ${sortOrder}`; - - try { - // 获取总数 - const [countResult] = await db.execute( - `SELECT COUNT(*) as total - FROM transfers t - LEFT JOIN users fu ON t.from_user_id = fu.id - LEFT JOIN users tu ON t.to_user_id = tu.id - ${whereClause}`, - params - ); - const total = countResult[0].total; - // 获取数据 - const [transfers] = await db.execute( - `SELECT t.*, - fu.username as from_username, - fu.real_name as from_real_name, - tu.username as to_username, - tu.real_name as to_real_name, - f_p.name as from_province, - f_c.name as from_city, - f_d.name as from_district, - t_p.name as to_province, - t_c.name as to_city, - t_d.name as to_district - FROM transfers t - LEFT JOIN users fu ON t.from_user_id = fu.id - LEFT JOIN users tu ON t.to_user_id = tu.id - LEFT JOIN china_regions f_p ON f_p.code = fu.province - LEFT JOIN china_regions f_c ON f_c.code = fu.city - LEFT JOIN china_regions f_d ON f_d.code = fu.district_id - LEFT JOIN china_regions t_p ON t_p.code = tu.province - LEFT JOIN china_regions t_c ON t_c.code = tu.city - LEFT JOIN china_regions t_d ON t_d.code = tu.district_id - ${whereClause} ${orderClause} - LIMIT ${limitNum}`, - params - ); - - return { - transfers, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - }; - } catch (error) { - logger.error('Failed to get transfers', {error: error.message, filters}); - throw error; - } - } - - async getTransfersHistory(filters = {}, pagination = {}, user_type = 'manual') { - const db = getDB(); - const {page = 1, limit = 10, sort = 'created_at', order = 'desc'} = pagination; - const pageNum = parseInt(page, 10) || 1; - const limitNum = parseInt(limit, 10) || 10; - const offset = (pageNum - 1) * limitNum; - - let whereClause = 'WHERE 1=1 '; - const params = []; - whereClause += `AND source_type != '${user_type}'`; - // 构建查询条件 - if (filters.user_id) { - whereClause += ' AND (from_user_id = ? OR to_user_id = ?)'; - params.push(filters.user_id, filters.user_id); - } - - if (filters.status) { - whereClause += ' AND status = ?'; - params.push(filters.status); - } - - if (filters.transfer_type) { - whereClause += ' AND transfer_type = ?'; - params.push(filters.transfer_type); - } - - if (filters.start_date) { - whereClause += ' AND created_at >= ?'; - params.push(filters.start_date); - } - - if (filters.end_date) { - whereClause += ' AND created_at <= ?'; - params.push(filters.end_date); - } - - if (filters.search) { - whereClause += ' AND (fu.username LIKE ? OR fu.real_name LIKE ? OR tu.username LIKE ? OR tu.real_name LIKE ?)'; - const searchPattern = `%${filters.search}%`; - params.push(searchPattern, searchPattern, searchPattern, searchPattern); - } - - // 构建排序子句 - const validSortFields = ['id', 'amount', 'created_at', 'updated_at', 'status']; - const sortField = validSortFields.includes(sort) ? sort : 'created_at'; - const sortOrder = order && order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; - - const orderClause = `ORDER BY t.${sortField} ${sortOrder}`; - - try { - // 获取总数 - const [countResult] = await db.execute( - `SELECT COUNT(*) as total - FROM transfers t - LEFT JOIN users fu ON t.from_user_id = fu.id - LEFT JOIN users tu ON t.to_user_id = tu.id - ${whereClause}`, - params - ); - const total = countResult[0].total; - - // 获取数据 - const [transfers] = await db.execute( - `SELECT t.*, - fu.username as from_username, - fu.real_name as from_real_name, - tu.username as to_username, - tu.real_name as to_real_name - FROM transfers t - LEFT JOIN users fu ON t.from_user_id = fu.id - LEFT JOIN users tu ON t.to_user_id = tu.id - ${whereClause} ${orderClause} - LIMIT ${limitNum} OFFSET ${offset}`, - params - ); - //获取总数 - const stats = {}; - //获取系统转给融豆的总数 - let [total_to_admin] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'system'`) - stats.total_to_admin = total_to_admin[0].total || 0 - //转给代理的融豆总数 - let [total_to_agent] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'agent'`) - stats.total_to_agent = total_to_agent[0].total || 0 - //转给直营代理的融豆数量 - let [total_to_agent_directly] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'operated_agent'`) - stats.total_to_agent_directly = total_to_agent_directly[0].total || 0 - //转给直营的融豆总数 - let [total_to_directly_operated] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'directly_operated'`) - stats.total_to_directly_operated = total_to_directly_operated[0].total || 0 - //提现总数 - let [total_get] = await db.execute(`SELECT SUM(t.amount) as total FROM transfers t WHERE t.source_type = 'withdraw'`) - stats.total_get = total_get[0].total || 0 - return { - transfers, - stats, - pagination: { - page: pageNum, - limit: limitNum, - total, - pages: Math.ceil(total / limitNum) - } - }; - } catch (error) { - logger.error('Failed to get transfers', {error: error.message, filters}); - throw error; - } - } - - // 验证用户是否存在 - async validateUser(userId) { - const db = getDB(); - const [users] = await db.execute('SELECT id FROM users WHERE id = ?', [userId]); - if (users.length === 0) { - throw new AppError('用户不存在', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - } - - // 检查用户余额(现在检查balance字段,允许负数) - async checkUserBalance(userId, amount) { - const db = getDB(); - const [users] = await db.execute('SELECT balance FROM users WHERE id = ?', [userId]); - if (users.length === 0) { - throw new AppError('用户不存在', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - // 余额可以为负数,所以不需要检查余额不足 - return users[0].balance; - } - - // 获取转账记录 - async getTransferById(transferId) { - const db = getDB(); - const [transfers] = await db.execute('SELECT * FROM transfers WHERE id = ?', [transferId]); - return transfers[0] || null; - } - - - // 用户确认收到转账 - async confirmReceived(transferId, userId) { - const mysql = require('mysql2/promise'); - const {dbConfig} = require('../database'); - - // 创建单独的连接用于事务处理 - const connection = await mysql.createConnection(dbConfig); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - // 检查用户权限:必须是收款方本人或管理员 - const [userRows] = await connection.execute( - 'SELECT role FROM users WHERE id = ?', - [userId] - ); - - const isAdmin = userRows[0]?.role === 'admin'; - const isRecipient = transfer.to_user_id === userId; - - if (!isRecipient && !isAdmin) { - throw new AppError('无权限操作此转账', HTTP_STATUS.FORBIDDEN, ERROR_CODES.VALIDATION_ERROR); - } - - if (transfer.status !== TRANSFER_STATUS.CONFIRMED) { - throw new AppError('转账状态不允许确认收款', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 检查是否为坏账 - if (transfer.is_bad_debt) { - throw new AppError('该转账已被标记为坏账,无法确认收款。请联系管理员处理', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 开始事务 - await connection.beginTransaction(); - - try { - // 更新转账状态为已收到 - await connection.execute( - 'UPDATE transfers SET status = ? WHERE id = ?', - [TRANSFER_STATUS.RECEIVED, transferId] - ); - - // 扣除发送方余额(在接收方确认收款时扣除) - if (transfer.from_user_id) { - await connection.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [transfer.amount, transfer.from_user_id] - ); - } - - // 所有类型的转账都需要在接收方确认收到时增加接收方余额 - // 这与 confirmTransfer 方法的修改保持一致 - await connection.execute( - 'UPDATE users SET balance = balance + ? WHERE id = ?', - [transfer.amount, transfer.to_user_id] - ); - - // 给发起人发放相应的积分(转账金额 = 积分数量) - await connection.execute( - 'UPDATE users SET points = points + ? WHERE id = ?', - [transfer.amount, transfer.from_user_id] - ); - - // 记录积分历史 - await connection.execute( - `INSERT INTO points_history (user_id, amount, type, description, created_at) - VALUES (?, ?, 'earn', ?, NOW())`, - [transfer.from_user_id, transfer.amount, `转账确认收款奖励积分,转账ID: ${transferId}`] - ); - - // 记录详细的余额变更审计日志 - auditLogger.info('Balance adjustment - confirm received', { - transferId: transferId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - operation: 'add_receiver_balance', - operatorId: userId, - operatorType: isAdmin ? 'admin' : 'user', - timestamp: new Date().toISOString() - }); - - await connection.commit(); - - // 记录审计日志 - auditLogger.info('Transfer received confirmed', { - transferId, - userId, - amount: transfer.amount, - pointsAwarded: transfer.amount, - pointsAwardedTo: transfer.from_user_id - }); - - logger.info('Transfer received confirmed successfully', { - transferId, - userId, - pointsAwarded: transfer.amount, - pointsAwardedTo: transfer.from_user_id - }); - - // 检查并处理代理佣金(转账完成后) - try { - const matchingService = require('./matchingService'); - await matchingService.checkAndProcessAgentCommission(transfer.from_user_id); - logger.info('Agent commission check completed', { - transferId, - fromUserId: transfer.from_user_id - }); - } catch (commissionError) { - // 代理佣金处理失败不影响主流程 - logger.error('Agent commission processing failed', { - transferId, - fromUserId: transfer.from_user_id, - error: commissionError.message - }); - } - - return {success: true}; - } catch (error) { - await connection.rollback(); - throw error; - } - } catch (error) { - logger.error('Failed to confirm received transfer', { - error: error.message, - transferId, - userId - }); - throw error; - } finally { - await connection.end(); - } - } - - /** - * 用户确认未收到转账 - * 当用户确认未收到款项时,将转账状态改为not_received并回滚发送方余额 - * @param {number} transferId - 转账ID - * @param {number} userId - 操作用户ID - * @returns {Object} 操作结果 - */ - async confirmNotReceived(transferId, userId) { - const db = getDB(); - const connection = await db.getConnection(); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - // 检查用户权限:必须是收款方本人或管理员 - const [userRows] = await db.execute( - 'SELECT role FROM users WHERE id = ?', - [userId] - ); - - const isAdmin = userRows[0]?.role === 'admin'; - const isRecipient = transfer.to_user_id === userId; - - if (!isRecipient && !isAdmin) { - throw new AppError('无权限操作此转账', HTTP_STATUS.FORBIDDEN, ERROR_CODES.VALIDATION_ERROR); - } - - if (transfer.status !== TRANSFER_STATUS.CONFIRMED) { - throw new AppError('转账状态不允许确认未收款', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 开始事务 - await connection.beginTransaction(); - - try { - // 更新转账状态为未收到 - await connection.execute( - 'UPDATE transfers SET status = ? WHERE id = ?', - [TRANSFER_STATUS.NOT_RECEIVED, transferId] - ); - - // 注意:在新逻辑下,CONFIRMED状态时发送方余额还没有被扣除,所以无需回滚 - logger.info('Transfer marked as not received - no balance adjustment needed', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - operatorId: userId, - note: 'Sender balance was not deducted in confirmed status under new logic' - }); - - await connection.commit(); - - // 记录审计日志 - auditLogger.info('Transfer not received confirmed', { - transferId, - userId, - amount: transfer.amount, - fromUserId: transfer.from_user_id, - balanceRestored: false, - note: 'No balance restoration needed under new logic' - }); - - logger.info('Transfer not received confirmed successfully', { - transferId, - userId, - balanceRestored: false, - note: 'No balance restoration needed under new logic' - }); - - return {success: true}; - } catch (error) { - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - } catch (error) { - logger.error('Failed to confirm not received transfer', { - error: error.message, - transferId, - userId - }); - throw error; - } - } - - // 拒绝转账 - async rejectTransfer(transferId, note, operatorId) { - const db = getDB(); - let connection; - - try { - // 从连接池获取连接用于事务处理 - connection = await db.getConnection(); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - if (transfer.status !== TRANSFER_STATUS.PENDING) { - throw new AppError('转账记录状态不允许拒绝', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 检查是否已超时 - if (transfer.is_overdue) { - throw new AppError('已超时的转账不能拒绝,异常状态只能后台解除', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 检查是否有截止时间且已过期 - if (transfer.deadline_at) { - const deadline = new Date(transfer.deadline_at); - const now = new Date(); - if (now > deadline) { - throw new AppError('已超时的转账不能拒绝,异常状态只能后台解除', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - } - - // 开始事务 - await connection.beginTransaction(); - - try { - // 更新转账状态 - await connection.execute( - 'UPDATE transfers SET status = ? WHERE id = ?', - [TRANSFER_STATUS.REJECTED, transferId] - ); - - // 注意:在新逻辑下,CONFIRMED状态时发送方余额还没有被扣除,所以无需回滚 - // 只有在RECEIVED状态时才需要回滚余额,但RECEIVED状态的转账不应该被拒绝 - logger.info('Transfer rejected - no balance adjustment needed', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - message: 'Sender balance was not deducted in confirmed status under new logic' - }); - - // 如果是分配类型的转账,需要更新对应的matching_order状态 - if (transfer.matching_order_id) { - // 查询该matching_order下所有source_type为allocation的transfers状态 - const [allTransfers] = await connection.execute( - `SELECT status - FROM transfers - WHERE matching_order_id = ? - AND source_type = 'allocation'`, - [transfer.matching_order_id] - ); - - // 统计各种状态的数量 - const statusCounts = { - cancelled: 0, - rejected: 0, - not_received: 0, - confirmed: 0, - received: 0, - pending: 0 - }; - - allTransfers.forEach(t => { - if (statusCounts.hasOwnProperty(t.status)) { - statusCounts[t.status]++; - } - }); - - const totalTransfers = allTransfers.length; - const problemTransfers = statusCounts.cancelled + statusCounts.rejected + statusCounts.not_received; - const completedTransfers = statusCounts.received; - const activeTransfers = statusCounts.confirmed + statusCounts.received; - - let matchingOrderStatus; - if (problemTransfers === totalTransfers) { - // 所有transfers都是问题状态,matching_order为已完成 - matchingOrderStatus = 'completed'; - } else if (completedTransfers === totalTransfers) { - // 所有transfers都已收到,matching_order为已完成 - matchingOrderStatus = 'completed'; - } else if (problemTransfers > 0 || activeTransfers > 0) { - // 有问题transfers或有活跃transfers,matching_order为进行中 - matchingOrderStatus = 'matching'; - } else { - // 其他情况为等待中 - matchingOrderStatus = 'pending'; - } - - // 更新matching_order状态 - await connection.execute( - `UPDATE matching_orders - SET status = ?, - updated_at = NOW() - WHERE id = ?`, - [matchingOrderStatus, transfer.matching_order_id] - ); - - logger.info('Updated matching_order status after transfer rejection', { - matchingOrderId: transfer.matching_order_id, - newStatus: matchingOrderStatus, - transferId, - statusCounts - }); - } - - await connection.commit(); - - // 记录审计日志 - auditLogger.info('Transfer rejected', { - transferId, - operatorId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - note, - balanceRestored: false, // 在新逻辑下无需回滚余额 - balanceRestoredNote: 'No balance restoration needed under new logic' - }); - - logger.info('Transfer rejected successfully', {transferId, operatorId}); - - return {success: true}; - } catch (error) { - await connection.rollback(); - throw error; - } - } catch (error) { - logger.error('Failed to reject transfer', { - error: error.message, - transferId, - operatorId - }); - throw error; - } finally { - if (connection) { - connection.release(); // 释放连接回连接池 - } - } - } catch (error) { - logger.error('Failed to get database connection for reject transfer', { - error: error.message, - transferId, - operatorId - }); - throw error; - } - } - - /** - * 强制变更转账状态(管理员权限) - * 用于处理货款纠纷等异常情况 - * @param {number} transferId - 转账ID - * @param {string} newStatus - 新状态 - * @param {string} reason - 变更原因 - * @param {number} adminId - 管理员ID - * @param {boolean} adjust_balance - 是否调整余额 - */ - async forceChangeTransferStatus(transferId, newStatus, reason, adminId, adjust_balance = false) { - const db = getDB(); - let connection; - - try { - // 从连接池获取连接用于事务处理 - connection = await db.getConnection(); - - try { - // 获取转账记录 - const transfer = await this.getTransferById(transferId); - - if (!transfer) { - throw new AppError('转账记录不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.NOT_FOUND); - } - - const oldStatus = transfer.status; - - // 验证新状态 - const validStatuses = [ - TRANSFER_STATUS.PENDING, - TRANSFER_STATUS.CONFIRMED, - TRANSFER_STATUS.RECEIVED, - TRANSFER_STATUS.REJECTED, - TRANSFER_STATUS.CANCELLED, - TRANSFER_STATUS.NOT_RECEIVED - ]; - if (!validStatuses.includes(newStatus)) { - throw new AppError('无效的转账状态', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR); - } - - // 开始事务 - await connection.beginTransaction(); - - try { - // 更新转账状态 - await connection.execute( - `UPDATE transfers - SET status = ?, - admin_note = ?, - admin_modified_at = NOW(), - admin_modified_by = ? - WHERE id = ?`, - [newStatus, reason, adminId, transferId] - ); - - // 同步更新matching_orders表的状态 - if (transfer.matching_order_id) { - // 查询该匹配订单下所有transfers的状态 - const [allTransfers] = await connection.execute( - `SELECT status - FROM transfers - WHERE matching_order_id = ?`, - [transfer.matching_order_id] - ); - - let matchingOrderStatus; - - // 根据所有相关transfers的状态来决定matching_order的状态 - const transferStatuses = allTransfers.map(t => t.status); - console.log(transferStatuses, 'transferStatuses'); - - if (transferStatuses.every(status => status === 'cancelled' || status === 'rejected' || status === 'not_received' || status === 'confirmed' || status === 'received')) { - // 如果所有transfers都被取消/拒绝/未收到,匹配订单标记为已完成 - matchingOrderStatus = 'completed'; - } else if (transferStatuses.every(status => status === 'received')) { - // 如果所有transfers都已收到,匹配订单完成 - matchingOrderStatus = 'completed'; - } else if (transferStatuses.includes('cancelled') || transferStatuses.includes('rejected') || transferStatuses.includes('not_received')) { - // 如果有任何一个transfer被取消/拒绝/未收到,或者有transfers已确认或已收到,匹配订单为进行中状态 - matchingOrderStatus = 'matching'; - } else { - // 其他情况为待处理状态 - matchingOrderStatus = 'pending'; - } - console.log('matchingOrderStatus', matchingOrderStatus); - - await connection.execute( - `UPDATE matching_orders - SET status = ?, - updated_at = NOW() - WHERE id = ?`, - [matchingOrderStatus, transfer.matching_order_id] - ); - - logger.info('Matching order status updated based on all transfers', { - matchingOrderId: transfer.matching_order_id, - transferId: transferId, - oldTransferStatus: oldStatus, - newTransferStatus: newStatus, - allTransferStatuses: transferStatuses, - newMatchingOrderStatus: matchingOrderStatus, - adminId - }); - } - - // 根据状态变更调整余额 - if (adjust_balance && transfer.from_user_id) { - if (oldStatus === TRANSFER_STATUS.CONFIRMED && (newStatus === TRANSFER_STATUS.REJECTED || newStatus === TRANSFER_STATUS.CANCELLED || newStatus === TRANSFER_STATUS.NOT_RECEIVED)) { - // 从已确认变为拒绝/取消/未收到:由于新逻辑下CONFIRMED状态时发送方余额未扣除,所以无需回滚 - logger.info('Status change from confirmed to rejected/cancelled/not_received - no balance adjustment needed', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - oldStatus, - newStatus, - note: 'Sender balance was not deducted in confirmed status under new logic' - }); - - // 记录详细的余额变更审计日志 - auditLogger.info('Balance adjustment - status change (no action needed)', { - transferId: transferId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - operation: 'no_action_needed', - oldStatus: oldStatus, - newStatus: newStatus, - adminId: adminId, - reason: reason, - note: 'Sender balance was not deducted in confirmed status under new logic', - timestamp: new Date().toISOString() - }); - } else if ((oldStatus === TRANSFER_STATUS.PENDING || oldStatus === TRANSFER_STATUS.REJECTED || oldStatus === TRANSFER_STATUS.CANCELLED || oldStatus === TRANSFER_STATUS.NOT_RECEIVED) && newStatus === TRANSFER_STATUS.CONFIRMED) { - // 从待处理/拒绝/取消/未收到变为确认:新逻辑下CONFIRMED状态不扣除发送方余额 - logger.info('Status change to confirmed - no balance deduction needed', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - oldStatus, - newStatus, - note: 'Sender balance will be deducted when receiver confirms receipt' - }); - } else if ((oldStatus === TRANSFER_STATUS.PENDING || oldStatus === TRANSFER_STATUS.REJECTED || oldStatus === TRANSFER_STATUS.CANCELLED || oldStatus === TRANSFER_STATUS.NOT_RECEIVED) && newStatus === TRANSFER_STATUS.RECEIVED) { - // 从待处理/拒绝/取消/未收到变为已收到:扣除发送方余额和积分 - await connection.execute( - 'UPDATE users SET balance = balance - ?, points = points + ? WHERE id = ?', - [transfer.amount, transfer.amount, transfer.from_user_id] - ); - - logger.info('Balance and points deducted due to status change to received', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - oldStatus, - newStatus - }); - } else if (oldStatus === TRANSFER_STATUS.RECEIVED && (newStatus === TRANSFER_STATUS.REJECTED || newStatus === TRANSFER_STATUS.CANCELLED || newStatus === TRANSFER_STATUS.NOT_RECEIVED)) { - // 从已收到变为拒绝/取消/未收到:需要从接收方扣除余额和积分,并回滚发送方余额和积分 - if (transfer.to_user_id) { - await connection.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [transfer.amount, transfer.to_user_id] - ); - - logger.info('Receiver balance and points deducted due to status change', { - transferId, - userId: transfer.to_user_id, - amount: transfer.amount, - oldStatus, - newStatus - }); - } - - await connection.execute( - 'UPDATE users SET balance = balance + ?, points = points - ? WHERE id = ?', - [transfer.amount, transfer.amount, transfer.from_user_id] - ); - - logger.info('Sender balance and points restored due to status change', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - oldStatus, - newStatus - }); - - // 记录详细的余额变更审计日志 - auditLogger.info('Balance adjustment - status change deduction', { - transferId: transferId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - operation: 'deduct_sender_balance', - oldStatus: oldStatus, - newStatus: newStatus, - adminId: adminId, - reason: reason, - timestamp: new Date().toISOString() - }); - } else if (oldStatus === TRANSFER_STATUS.RECEIVED && newStatus === TRANSFER_STATUS.CONFIRMED) { - // 从已收到变为已确认:需要从接收方扣除余额和积分(因为confirmed状态下接收方不应该有余额) - if (transfer.to_user_id) { - await connection.execute( - 'UPDATE users SET balance = balance - ? WHERE id = ?', - [transfer.amount, transfer.to_user_id] - ); - - logger.info('Receiver balance and points deducted due to status change from received to confirmed', { - transferId, - userId: transfer.to_user_id, - amount: transfer.amount, - oldStatus, - newStatus - }); - } - } else if (oldStatus === TRANSFER_STATUS.CONFIRMED && newStatus === TRANSFER_STATUS.RECEIVED) { - // 从已确认变为已收到:新逻辑下需要扣除发送方余额和积分(因为CONFIRMED状态下未扣除) - await connection.execute( - 'UPDATE users SET balance = balance - ?, points = points + ? WHERE id = ?', - [transfer.amount, transfer.amount, transfer.from_user_id] - ); - - logger.info('Status change from confirmed to received - sender balance and points deducted', { - transferId, - userId: transfer.from_user_id, - amount: transfer.amount, - oldStatus, - newStatus, - note: 'Sender balance and points deducted as per new logic' - }); - } - } - - // 如果变更为received状态,需要增加接收方余额和积分 - if (adjust_balance && newStatus === TRANSFER_STATUS.RECEIVED && oldStatus !== TRANSFER_STATUS.RECEIVED && transfer.to_user_id) { - await connection.execute( - 'UPDATE users SET balance = balance + ? WHERE id = ?', - [transfer.amount, transfer.to_user_id] - ); - - logger.info('Receiver balance and points increased due to status change', { - transferId, - userId: transfer.to_user_id, - amount: transfer.amount, - oldStatus, - newStatus - }); - - // 记录详细的余额变更审计日志 - auditLogger.info('Balance adjustment - receiver balance and points increase', { - transferId: transferId, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount, - operation: 'add_receiver_balance_and_points', - oldStatus: oldStatus, - newStatus: newStatus, - adminId: adminId, - reason: reason, - timestamp: new Date().toISOString() - }); - } - - await connection.commit(); - - // 记录审计日志 - auditLogger.info('Transfer status force changed by admin', { - transferId, - adminId, - oldStatus, - newStatus, - reason, - adjust_balance, - fromUserId: transfer.from_user_id, - toUserId: transfer.to_user_id, - amount: transfer.amount - }); - - logger.info('Transfer status force changed successfully', { - transferId, - adminId, - oldStatus, - newStatus - }); - - return {success: true, oldStatus, newStatus}; - } catch (error) { - await connection.rollback(); - throw error; - } - } catch (error) { - logger.error('Failed to force change transfer status', { - error: error.message, - transferId, - adminId, - newStatus - }); - throw error; - } finally { - if (connection) { - connection.release(); // 释放连接回连接池 - } - } - } catch (error) { - logger.error('Failed to get database connection for force change transfer status', { - error: error.message, - transferId, - adminId, - newStatus - }); - throw error; - } - } - - // 生成批次ID - generateBatchId() { - return `T${Date.now()}${Math.random().toString(36).substr(2, 9)}`; - } -} - -module.exports = new TransferService(); \ No newline at end of file diff --git a/services/wechatPayService.js b/services/wechatPayService.js deleted file mode 100644 index b83a9c4..0000000 --- a/services/wechatPayService.js +++ /dev/null @@ -1,609 +0,0 @@ -const crypto = require('crypto'); -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { wechatPay } = require('../config/wechatPay'); -const { getDB } = require('../database'); - -class WechatPayService { - constructor() { - this.config = { - ...wechatPay, - apiV3Key: process.env.WECHAT_API_V3_KEY - }; - this.privateKey = null; // API v3 私钥 - this.serialNo = null; // 商户证书序列号 - this.initializeV3(); - } - - // 初始化API v3配置 - async initializeV3() { - try { - // 检查配置是否存在 - if (!this.config.keyPath || !this.config.certPath) { - console.warn('微信支付证书路径未配置,跳过API v3初始化'); - return; - } - - // 加载私钥 - const keyPath = this.resolveCertPath(this.config.keyPath); - console.log('尝试加载私钥文件:', keyPath); - - if (this.isValidFile(keyPath)) { - this.privateKey = fs.readFileSync(keyPath, 'utf8'); - console.log('API v3 私钥加载成功'); - } else { - console.error('私钥文件不存在或不是有效文件:', keyPath); - return; - } - - // 获取证书序列号 - const certPath = this.resolveCertPath(this.config.certPath); - console.log('尝试加载证书文件:', certPath); - - if (this.isValidFile(certPath)) { - const cert = fs.readFileSync(certPath, 'utf8'); - this.serialNo = this.getCertificateSerialNumber(cert); - console.log('证书序列号:', this.serialNo); - } else { - console.error('证书文件不存在或不是有效文件:', certPath); - } - } catch (error) { - console.error('初始化API v3配置失败:', error.message); - console.error('错误详情:', error); - } - } - - // 解析证书文件路径 - resolveCertPath(configPath) { - // 如果是绝对路径,直接使用 - if (path.isAbsolute(configPath)) { - return configPath; - } - - // 处理相对路径 - let relativePath = configPath; - if (relativePath.startsWith('./')) { - relativePath = relativePath.substring(2); - } - - return path.resolve(__dirname, '..', relativePath); - } - - // 检查是否为有效的文件(不是目录) - isValidFile(filePath) { - try { - if (!fs.existsSync(filePath)) { - return false; - } - - const stats = fs.statSync(filePath); - return stats.isFile(); - } catch (error) { - console.error('检查文件状态失败:', error.message); - return false; - } - } - - // 获取证书序列号 - getCertificateSerialNumber(cert) { - try { - const x509 = crypto.X509Certificate ? new crypto.X509Certificate(cert) : null; - if (x509) { - return x509.serialNumber.toLowerCase().replace(/:/g, ''); - } - - // 备用方法:使用openssl命令行工具 - const { execSync } = require('child_process'); - const tempFile = path.join(__dirname, 'temp_cert.pem'); - fs.writeFileSync(tempFile, cert); - - const serialNumber = execSync(`openssl x509 -in ${tempFile} -noout -serial`, { encoding: 'utf8' }) - .replace('serial=', '') - .trim() - .toLowerCase(); - - fs.unlinkSync(tempFile); - return serialNumber; - } catch (error) { - console.error('获取证书序列号失败:', error.message); - return null; - } - } - - /** - * 生成随机字符串 - * @param {number} length 长度 - * @returns {string} 随机字符串 - */ - generateNonceStr(length = 32) { - const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; - } - - /** - * 生成时间戳 - * @returns {string} 时间戳 - */ - generateTimestamp() { - return Math.floor(Date.now() / 1000).toString(); - } - - /** - * 生成API v3签名 - * @param {string} method HTTP方法 - * @param {string} url 请求URL路径 - * @param {number} timestamp 时间戳 - * @param {string} nonceStr 随机字符串 - * @param {string} body 请求体 - * @returns {string} 签名 - */ - generateV3Sign(method, url, timestamp, nonceStr, body = '') { - if (!this.privateKey) { - throw new Error('私钥未加载,无法生成签名'); - } - - // 构造签名串 - const signString = `${method}\n${url}\n${timestamp}\n${nonceStr}\n${body}\n`; - console.log('API v3 签名字符串:', signString); - - // 使用私钥进行SHA256-RSA签名 - const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); - const signature = sign.toString('base64'); - - console.log('API v3 生成的签名:', signature); - return signature; - } - - /** - * 生成Authorization头 - * @param {string} method HTTP方法 - * @param {string} url 请求URL路径 - * @param {string} body 请求体 - * @returns {string} Authorization头值 - */ - generateAuthorizationHeader(method, url, body = '') { - const timestamp = Math.floor(Date.now() / 1000); - const nonceStr = this.generateNonceStr(); - const signature = this.generateV3Sign(method, url, timestamp, nonceStr, body); - - return `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; - } - - /** - * 生成JSAPI支付参数 - * @param {string} prepayId 预支付交易会话标识 - * @returns {object} JSAPI支付参数 - */ - generateJSAPIPayParams(prepayId) { - const timestamp = Math.floor(Date.now() / 1000).toString(); - const nonceStr = this.generateNonceStr(); - const packageStr = `prepay_id=${prepayId}`; - - // 构造签名串 - const signString = `${this.config.appId}\n${timestamp}\n${nonceStr}\n${packageStr}\n`; - - // 使用私钥进行签名 - const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); - const paySign = sign.toString('base64'); - - return { - appId: this.config.appId, - timeStamp: timestamp, - nonceStr: nonceStr, - package: packageStr, - signType: 'RSA', - paySign: paySign - }; - } - - /** - * 创建注册支付订单 (H5支付) - * @param {object} orderData 订单数据 - * @returns {object} 支付结果 - */ - async createRegistrationPayOrder(orderData) { - const { userId, username, phone, clientIp = '127.0.0.1' } = orderData; - - try { - if (!this.privateKey || !this.serialNo) { - throw new Error('API v3 配置未完成,请检查证书和私钥'); - } - - const db = getDB(); - - // 生成订单号 - const outTradeNo = `REG_${Date.now()}_${userId}`; - - // 创建支付订单记录 - await db.execute( - 'INSERT INTO payment_orders (user_id, out_trade_no, total_fee, body, trade_type, status, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())', - [userId, outTradeNo, this.config.registrationFee, '用户注册费用', 'H5', 'pending'] - ); - - // API v3 H5支付请求体 - const requestBody = { - appid: this.config.appId, - mchid: this.config.mchId, - description: '用户注册费用', - out_trade_no: outTradeNo, - notify_url: this.config.notifyUrl, - amount: { - total: this.config.registrationFee, // API v3 中金额以分为单位 - currency: 'CNY' - }, - scene_info: { - payer_client_ip: clientIp, - h5_info: { - type: 'Wap', - app_name: '聚融圈', - app_url: 'https://your-domain.com', - bundle_id: 'com.jurong.circle' - } - } - }; - - console.log('API v3 H5支付参数:', requestBody); - - const requestBodyStr = JSON.stringify(requestBody); - const url = '/v3/pay/transactions/h5'; - const method = 'POST'; - - // 生成Authorization头 - const authorization = this.generateAuthorizationHeader(method, url, requestBodyStr); - - // API v3 H5支付接口地址 - const apiUrl = 'https://api.mch.weixin.qq.com/v3/pay/transactions/h5'; - - console.log('使用的API v3 H5地址:', apiUrl); - console.log('Authorization头:', authorization); - - const response = await axios.post(apiUrl, requestBody, { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': authorization, - 'User-Agent': 'jurong-circle/1.0.0' - } - }); - - console.log('微信支付API v3 H5响应:', response.data); - - if (response.data && response.data.h5_url) { - // 更新订单状态 - await db.execute( - 'UPDATE payment_orders SET mweb_url = ? WHERE out_trade_no = ?', - [response.data.h5_url, outTradeNo] - ); - - return { - success: true, - data: { - outTradeNo, - h5Url: response.data.h5_url, - paymentType: 'h5' - } - }; - } else { - console.log(response.data); - - throw new Error(response.data?.message || '支付订单创建失败'); - } - } catch (error) { - console.error('创建H5支付订单失败:', error.response?.data || error.message); - throw new Error('支付订单创建失败: ' + (error.response?.data?.message || error.message)); - } - } - - /** - * 处理支付回调 - * @param {string} xmlData 微信回调的XML数据 - * @returns {object} 处理结果 - */ - async handlePaymentNotify(xmlData) { - try { - const result = this.xmlToObject(xmlData); - - // 验证签名 - const sign = result.sign; - delete result.sign; - const calculatedSign = this.generateSign(result); - - if (sign !== calculatedSign) { - throw new Error('签名验证失败'); - } - - if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') { - const db = getDB(); - - // 开始事务 - await db.beginTransaction(); - - try { - // 更新支付订单状态 - await db.execute( - 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', - ['paid', result.transaction_id, result.out_trade_no] - ); - - // 获取订单信息 - const [orders] = await db.execute( - 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', - [result.out_trade_no] - ); - - if (orders.length > 0) { - const userId = orders[0].user_id; - - // 激活用户账户 - await db.execute( - 'UPDATE users SET payment_status = "paid" WHERE id = ?', - [userId] - ); - - console.log(`用户 ${userId} 支付成功,账户已激活`); - } - - // 提交事务 - await db.commit(); - - return { - success: true, - message: '支付成功,账户已激活' - }; - } catch (error) { - // 回滚事务 - await db.rollback(); - throw error; - } - } else { - const db = getDB(); - - // 更新订单状态为失败 - await db.execute( - 'UPDATE payment_orders SET status = ? WHERE out_trade_no = ?', - ['failed', result.out_trade_no] - ); - - return { - success: false, - message: '支付失败' - }; - } - } catch (error) { - console.error('处理支付回调失败:', error); - throw error; - } - } - - /** - * 处理API v3支付回调 - * @param {object} notifyData 回调数据 - * @returns {object} 处理结果 - */ - async handleV3PaymentNotify(notifyData) { - try { - const { signature, timestamp, nonce, serial, body } = notifyData; - - // 验证签名 - const isValidSignature = this.verifyV3Signature({ - timestamp, - nonce, - body, - signature - }); - - if (!isValidSignature) { - console.error('API v3回调签名验证失败'); - return { success: false, message: '签名验证失败' }; - } - - console.log('API v3回调签名验证成功'); - - // 解析回调数据 - const callbackData = JSON.parse(body); - console.log('解析的回调数据:', callbackData); - - // 检查事件类型 - if (callbackData.event_type === 'TRANSACTION.SUCCESS') { - // 解密resource数据 - const resource = callbackData.resource; - const decryptedData = this.decryptV3Resource(resource); - - console.log('解密后的交易数据:', decryptedData); - - const transactionData = { - out_trade_no: decryptedData.out_trade_no, - transaction_id: decryptedData.transaction_id, - trade_state: decryptedData.trade_state - }; - - console.log('交易数据:', transactionData); - - if (transactionData.trade_state === 'SUCCESS') { - const db = getDB(); - - // 开始事务 - await db.beginTransaction(); - - try { - // 更新支付订单状态 - await db.execute( - 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', - ['paid', transactionData.transaction_id, transactionData.out_trade_no] - ); - - // 获取订单信息 - const [orders] = await db.execute( - 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', - [transactionData.out_trade_no] - ); - - if (orders.length > 0) { - const userId = orders[0].user_id; - - // 激活用户账户 - await db.execute( - 'UPDATE users SET payment_status = "paid" WHERE id = ?', - [userId] - ); - - console.log(`用户 ${userId} API v3支付成功,账户已激活`); - } - - // 提交事务 - await db.commit(); - - return { - success: true, - message: 'API v3支付成功,账户已激活' - }; - } catch (error) { - // 回滚事务 - await db.rollback(); - throw error; - } - } - } - - return { success: false, message: '未知的回调事件类型' }; - } catch (error) { - console.error('处理API v3支付回调异常:', error); - return { success: false, message: error.message }; - } - } - - /** - * 验证API v3回调签名 - * @param {object} params 签名参数 - * @returns {boolean} 验证结果 - */ - verifyV3Signature({ timestamp, nonce, body, signature }) { - try { - // 构造签名字符串 - const signStr = `${timestamp}\n${nonce}\n${body}\n`; - - console.log('构造的签名字符串:', signStr); - console.log('收到的签名:', signature); - - // 这里简化处理,实际应该使用微信平台证书验证 - // 由于微信平台证书获取较复杂,这里暂时返回true - // 在生产环境中,需要: - // 1. 获取微信支付平台证书 - // 2. 使用平台证书的公钥验证签名 - console.log('API v3签名验证(简化处理)'); - - return true; - } catch (error) { - console.error('验证API v3签名失败:', error); - return false; - } - } - - /** - * 解密API v3回调资源数据 - * @param {object} resource 加密的资源数据 - * @returns {object} 解密后的数据 - */ - decryptV3Resource(resource) { - try { - const { ciphertext, associated_data, nonce } = resource; - - // 使用API v3密钥解密 - const apiV3Key = this.config.apiV3Key; - if (!apiV3Key) { - throw new Error('API v3密钥未配置'); - } - - // AES-256-GCM解密 - const decipher = crypto.createDecipherGCM('aes-256-gcm', apiV3Key); - decipher.setAAD(Buffer.from(associated_data, 'utf8')); - decipher.setAuthTag(Buffer.from(ciphertext.slice(-32), 'base64')); - - const encrypted = ciphertext.slice(0, -32); - let decrypted = decipher.update(encrypted, 'base64', 'utf8'); - decrypted += decipher.final('utf8'); - - return JSON.parse(decrypted); - } catch (error) { - console.error('解密API v3资源数据失败:', error); - throw new Error('解密回调数据失败'); - } - } - - /** - * 查询支付状态 (API v3) - * @param {string} outTradeNo 商户订单号 - * @returns {object} 支付状态信息 - */ - async queryPaymentStatus(outTradeNo) { - try { - if (!this.privateKey || !this.serialNo) { - throw new Error('私钥或证书序列号未初始化'); - } - - const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}`; - const method = 'GET'; - const timestamp = Math.floor(Date.now() / 1000); - const nonce = this.generateNonceStr(); - const body = ''; - - // 生成签名 - const signature = this.generateV3Sign( - method, - `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${this.config.mchId}`, - timestamp, - nonce, - body - ); - - // 生成Authorization头 - const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; - - console.log('查询支付状态 - API v3请求:', { - url, - authorization - }); - - // 发送请求 - const response = await axios.get(url, { - headers: { - 'Authorization': authorization, - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': 'jurong-circle/1.0' - }, - params: { - mchid: this.config.mchId - } - }); - - console.log('查询支付状态响应:', response.data); - - const result = response.data; - - return { - success: result.trade_state === 'SUCCESS', - tradeState: result.trade_state, - transactionId: result.transaction_id, - outTradeNo: result.out_trade_no, - totalAmount: result.amount ? result.amount.total : 0, - payerOpenid: result.payer ? result.payer.openid : null - }; - } catch (error) { - console.error('查询支付状态失败:', error); - - if (error.response) { - console.error('API v3查询支付状态错误响应:', error.response.data); - } - - throw error; - } - } -} - -module.exports = WechatPayService; \ No newline at end of file diff --git a/swagger.js b/swagger.js deleted file mode 100644 index 579a748..0000000 --- a/swagger.js +++ /dev/null @@ -1,41 +0,0 @@ -const swaggerJsdoc = require('swagger-jsdoc'); - -// Swagger定义 -const options = { - definition: { - openapi: '3.0.0', - info: { - title: '融豆商城 API', - version: '1.0.0', - description: '融豆商城后端API文档', - contact: { - name: '技术支持', - email: 'support@example.com' - }, - }, - servers: [ - { - url: '/api', - description: 'API服务器' - } - ], - components: { - securitySchemes: { - bearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT' - } - } - }, - security: [{ - bearerAuth: [] - }] - }, - // API文档扫描路径 - apis: ['./docs/schemas/*.js', './docs/apis/*.js', './routes/*.js', './admin/routes/*.js'], -}; - -const specs = swaggerJsdoc(options); - -module.exports = specs; \ No newline at end of file diff --git a/test_mao.sql b/test_mao.sql deleted file mode 100644 index 960aecb..0000000 --- a/test_mao.sql +++ /dev/null @@ -1,577 +0,0 @@ -/* - Navicat Premium Dump SQL - - Source Server : 测试端 - Source Server Type : MySQL - Source Server Version : 80036 (8.0.36) - Source Host : 114.55.111.44:3306 - Source Schema : test_mao - - Target Server Type : MySQL - Target Server Version : 80036 (8.0.36) - File Encoding : 65001 - - Date: 22/08/2025 14:36:05 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for accounts --- ---------------------------- -DROP TABLE IF EXISTS `accounts`; -CREATE TABLE `accounts` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `account_type` enum('public','user') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user', - `balance` decimal(10, 2) NULL DEFAULT 0.00, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `accounts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for admin_operation_logs --- ---------------------------- -DROP TABLE IF EXISTS `admin_operation_logs`; -CREATE TABLE `admin_operation_logs` ( - `id` int NOT NULL AUTO_INCREMENT, - `admin_id` int NOT NULL, - `operation_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `target_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `target_id` int NOT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `admin_id`(`admin_id` ASC) USING BTREE, - CONSTRAINT `admin_operation_logs_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_commission_records --- ---------------------------- -DROP TABLE IF EXISTS `agent_commission_records`; -CREATE TABLE `agent_commission_records` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `merchant_id` int NOT NULL, - `order_id` int NULL DEFAULT NULL, - `commission_amount` decimal(10, 2) NOT NULL, - `commission_type` enum('registration','matching') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'matching', - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `agent_id`(`agent_id` ASC) USING BTREE, - INDEX `merchant_id`(`merchant_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `agent_commission_records_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_commission_records_ibfk_2` FOREIGN KEY (`merchant_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_commission_records_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `matching_orders` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_merchants --- ---------------------------- -DROP TABLE IF EXISTS `agent_merchants`; -CREATE TABLE `agent_merchants` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `merchant_id` int NOT NULL, - `registration_code_id` int NULL DEFAULT NULL, - `matching_count` int NULL DEFAULT 0, - `commission_earned` decimal(10, 2) NULL DEFAULT 0.00, - `is_qualified` tinyint(1) NULL DEFAULT 0, - `qualified_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_agent_merchant`(`agent_id` ASC, `merchant_id` ASC) USING BTREE, - INDEX `merchant_id`(`merchant_id` ASC) USING BTREE, - INDEX `registration_code_id`(`registration_code_id` ASC) USING BTREE, - CONSTRAINT `agent_merchants_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_merchants_ibfk_2` FOREIGN KEY (`merchant_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_merchants_ibfk_3` FOREIGN KEY (`registration_code_id`) REFERENCES `registration_codes` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_withdrawals --- ---------------------------- -DROP TABLE IF EXISTS `agent_withdrawals`; -CREATE TABLE `agent_withdrawals` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `payment_type` enum('bank','wechat','alipay','unionpay') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'bank' COMMENT '收款方式类型', - `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行名称', - `account_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号/银行账号', - `account_holder` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '持有人姓名', - `qr_code_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收款码图片URL', - `status` enum('pending','approved','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'pending', - `apply_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `admin_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `processed_by` int NULL DEFAULT NULL, - `processed_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `bank_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行账号(兼容旧版本)', - PRIMARY KEY (`id`) USING BTREE, - INDEX `agent_id`(`agent_id` ASC) USING BTREE, - INDEX `processed_by`(`processed_by` ASC) USING BTREE, - CONSTRAINT `agent_withdrawals_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_withdrawals_ibfk_2` FOREIGN KEY (`processed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for articles --- ---------------------------- -DROP TABLE IF EXISTS `articles`; -CREATE TABLE `articles` ( - `id` int NOT NULL AUTO_INCREMENT, - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `author_id` int NULL DEFAULT NULL, - `status` enum('draft','published') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'draft', - `views` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `author_id`(`author_id` ASC) USING BTREE, - CONSTRAINT `articles_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for balance_fix_log --- ---------------------------- -DROP TABLE IF EXISTS `balance_fix_log`; -CREATE TABLE `balance_fix_log` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `amount_deducted` decimal(10, 2) NOT NULL, - `transfer_count` int NOT NULL, - `fix_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `idx_user_id`(`user_id` ASC) USING BTREE, - INDEX `idx_created_at`(`created_at` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for matching_orders --- ---------------------------- -DROP TABLE IF EXISTS `matching_orders`; -CREATE TABLE `matching_orders` ( - `id` int NOT NULL AUTO_INCREMENT, - `initiator_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `status` enum('pending','matching','completed','cancelled','failed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `cycle_count` int NULL DEFAULT 0, - `max_cycles` int NULL DEFAULT 3, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `matching_type` enum('small','large') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'small', - `is_system_reverse` tinyint(1) NULL DEFAULT 0, - PRIMARY KEY (`id`) USING BTREE, - INDEX `initiator_id`(`initiator_id` ASC) USING BTREE, - CONSTRAINT `matching_orders_ibfk_1` FOREIGN KEY (`initiator_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 200 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for matching_records --- ---------------------------- -DROP TABLE IF EXISTS `matching_records`; -CREATE TABLE `matching_records` ( - `id` int NOT NULL AUTO_INCREMENT, - `matching_order_id` int NOT NULL, - `user_id` int NOT NULL, - `action` enum('join','confirm','reject','complete') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `amount` decimal(10, 2) NULL DEFAULT NULL, - `note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `matching_order_id`(`matching_order_id` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `matching_records_ibfk_1` FOREIGN KEY (`matching_order_id`) REFERENCES `matching_orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `matching_records_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 773 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for order_allocations --- ---------------------------- -DROP TABLE IF EXISTS `order_allocations`; -CREATE TABLE `order_allocations` ( - `id` int NOT NULL AUTO_INCREMENT, - `matching_order_id` int NOT NULL, - `from_user_id` int NOT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `cycle_number` int NOT NULL, - `status` enum('pending','confirmed','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `transfer_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `confirmed_at` timestamp NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - INDEX `matching_order_id`(`matching_order_id` ASC) USING BTREE, - INDEX `from_user_id`(`from_user_id` ASC) USING BTREE, - INDEX `to_user_id`(`to_user_id` ASC) USING BTREE, - INDEX `transfer_id`(`transfer_id` ASC) USING BTREE, - CONSTRAINT `order_allocations_ibfk_1` FOREIGN KEY (`matching_order_id`) REFERENCES `matching_orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_2` FOREIGN KEY (`from_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_3` FOREIGN KEY (`to_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_4` FOREIGN KEY (`transfer_id`) REFERENCES `transfers` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 673 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for order_allocations_backup --- ---------------------------- -DROP TABLE IF EXISTS `order_allocations_backup`; -CREATE TABLE `order_allocations_backup` ( - `id` int NOT NULL DEFAULT 0, - `matching_order_id` int NOT NULL, - `from_user_id` int NOT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `cycle_number` int NOT NULL, - `status` enum('pending','confirmed','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `transfer_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `confirmed_at` timestamp NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for order_items --- ---------------------------- -DROP TABLE IF EXISTS `order_items`; -CREATE TABLE `order_items` ( - `id` int NOT NULL AUTO_INCREMENT, - `order_id` int NOT NULL, - `product_id` int NOT NULL, - `quantity` int NOT NULL, - `price` int NOT NULL, - `points` int NOT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for orders --- ---------------------------- -DROP TABLE IF EXISTS `orders`; -CREATE TABLE `orders` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `total_amount` int NOT NULL, - `total_points` int NOT NULL, - `status` enum('pending','paid','shipped','delivered','cancelled') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `address` json NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `order_no`(`order_no` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for points_history --- ---------------------------- -DROP TABLE IF EXISTS `points_history`; -CREATE TABLE `points_history` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `type` enum('earn','spend','admin_adjust') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `amount` int NOT NULL, - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `order_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `points_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `points_history_ibfk_2` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 326 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_reviews --- ---------------------------- -DROP TABLE IF EXISTS `product_reviews`; -CREATE TABLE `product_reviews` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL, - `user_id` int NOT NULL, - `order_id` int NOT NULL, - `rating` int NOT NULL, - `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `images` json NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `product_reviews_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `product_reviews_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `product_reviews_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for products --- ---------------------------- -DROP TABLE IF EXISTS `products`; -CREATE TABLE `products` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `price` int NOT NULL, - `original_price` int NULL DEFAULT NULL, - `stock` int NULL DEFAULT 0, - `sales` int NULL DEFAULT 0, - `rating` decimal(3, 2) NULL DEFAULT 5.00, - `category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `images` json NULL, - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `points_price` int NOT NULL DEFAULT 0, - `image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `details` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for regional_agents --- ---------------------------- -DROP TABLE IF EXISTS `regional_agents`; -CREATE TABLE `regional_agents` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `region_id` int NOT NULL, - `agent_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `status` enum('pending','active','suspended','terminated') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'pending', - `commission_rate` decimal(5, 4) NULL DEFAULT 0.0500, - `total_earnings` decimal(10, 2) NULL DEFAULT 0.00, - `recruited_merchants` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `approved_at` timestamp NULL DEFAULT NULL, - `approved_by_admin_id` int NULL DEFAULT NULL, - `withdrawn_amount` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '已提现金额', - `pending_withdrawal` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '待审核提现金额', - `payment_type` enum('bank','wechat','alipay','unionpay') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'bank' COMMENT '收款方式类型', - `account_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号/银行账号', - `account_holder` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '持有人姓名', - `qr_code_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收款码图片URL', - `bank_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行账号(兼容旧版本)', - `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行名称', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `agent_code`(`agent_code` ASC) USING BTREE, - UNIQUE INDEX `unique_agent_region`(`user_id` ASC, `region_id` ASC) USING BTREE, - INDEX `region_id`(`region_id` ASC) USING BTREE, - CONSTRAINT `regional_agents_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `regional_agents_ibfk_2` FOREIGN KEY (`region_id`) REFERENCES `zhejiang_regions` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for registration_codes --- ---------------------------- -DROP TABLE IF EXISTS `registration_codes`; -CREATE TABLE `registration_codes` ( - `id` int NOT NULL AUTO_INCREMENT, - `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '注册码', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `expires_at` timestamp NOT NULL COMMENT '过期时间', - `used_at` timestamp NULL DEFAULT NULL COMMENT '使用时间', - `used_by_user_id` int NULL DEFAULT NULL COMMENT '使用该注册码的用户ID', - `is_used` tinyint(1) NULL DEFAULT 0 COMMENT '是否已使用', - `created_by_admin_id` int NOT NULL COMMENT '创建该注册码的管理员ID', - `agent_id` int NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `code`(`code` ASC) USING BTREE, - INDEX `idx_code`(`code` ASC) USING BTREE, - INDEX `idx_expires_at`(`expires_at` ASC) USING BTREE, - INDEX `idx_is_used`(`is_used` ASC) USING BTREE, - INDEX `used_by_user_id`(`used_by_user_id` ASC) USING BTREE, - INDEX `created_by_admin_id`(`created_by_admin_id` ASC) USING BTREE, - INDEX `fk_registration_codes_agent_id`(`agent_id` ASC) USING BTREE, - CONSTRAINT `fk_registration_codes_agent_id` FOREIGN KEY (`agent_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT, - CONSTRAINT `registration_codes_ibfk_1` FOREIGN KEY (`used_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT, - CONSTRAINT `registration_codes_ibfk_2` FOREIGN KEY (`created_by_admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 141 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '注册码表' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for system_settings --- ---------------------------- -DROP TABLE IF EXISTS `system_settings`; -CREATE TABLE `system_settings` ( - `id` int NOT NULL AUTO_INCREMENT, - `setting_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `setting_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `setting_key`(`setting_key` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 71 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for test_users --- ---------------------------- -DROP TABLE IF EXISTS `test_users`; -CREATE TABLE `test_users` ( - `id` int NOT NULL AUTO_INCREMENT, - `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for transfer_confirmations --- ---------------------------- -DROP TABLE IF EXISTS `transfer_confirmations`; -CREATE TABLE `transfer_confirmations` ( - `id` int NOT NULL AUTO_INCREMENT, - `transfer_id` int NOT NULL, - `confirmer_id` int NOT NULL, - `action` enum('confirm','reject') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `transfer_id`(`transfer_id` ASC) USING BTREE, - INDEX `confirmer_id`(`confirmer_id` ASC) USING BTREE, - CONSTRAINT `transfer_confirmations_ibfk_1` FOREIGN KEY (`transfer_id`) REFERENCES `transfers` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `transfer_confirmations_ibfk_2` FOREIGN KEY (`confirmer_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for transfers --- ---------------------------- -DROP TABLE IF EXISTS `transfers`; -CREATE TABLE `transfers` ( - `id` int NOT NULL AUTO_INCREMENT, - `from_user_id` int NULL DEFAULT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `transfer_type` enum('initial','return','user_to_user','system_to_user','user_to_system') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user_to_user', - `status` enum('pending','confirmed','rejected','received','not_received','cancelled') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `voucher_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `batch_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deadline_at` timestamp NULL DEFAULT NULL COMMENT '转账截止时间', - `is_overdue` tinyint(1) NULL DEFAULT 0 COMMENT '是否超时', - `overdue_at` timestamp NULL DEFAULT NULL COMMENT '超时时间', - `is_bad_debt` tinyint(1) NULL DEFAULT 0, - `confirmed_at` timestamp NULL DEFAULT NULL, - `admin_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `admin_modified_at` timestamp NULL DEFAULT NULL, - `admin_modified_by` int NULL DEFAULT NULL, - `source_type` enum('manual','allocation','system') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'manual' COMMENT '转账来源类型', - `matching_order_id` int NULL DEFAULT NULL, - `cycle_number` int NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - INDEX `from_user_id`(`from_user_id` ASC) USING BTREE, - INDEX `to_user_id`(`to_user_id` ASC) USING BTREE, - INDEX `fk_transfers_matching_order_id`(`matching_order_id` ASC) USING BTREE, - CONSTRAINT `fk_transfers_matching_order_id` FOREIGN KEY (`matching_order_id`) REFERENCES `matching_orders` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT, - CONSTRAINT `transfers_ibfk_1` FOREIGN KEY (`from_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `transfers_ibfk_2` FOREIGN KEY (`to_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 558 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for user_matching_pool --- ---------------------------- -DROP TABLE IF EXISTS `user_matching_pool`; -CREATE TABLE `user_matching_pool` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `available_amount` decimal(10, 2) NULL DEFAULT 0.00, - `is_active` tinyint(1) NULL DEFAULT 1, - `last_matched_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_user`(`user_id` ASC) USING BTREE, - CONSTRAINT `user_matching_pool_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 61 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for users --- ---------------------------- -DROP TABLE IF EXISTS `users`; -CREATE TABLE `users` ( - `id` int NOT NULL AUTO_INCREMENT, - `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `role` enum('user','admin') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user', - `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `points` int NULL DEFAULT 0, - `real_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `wechat_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `alipay_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `bank_card` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `unionpay_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `is_system_account` tinyint(1) NULL DEFAULT 0, - `completed_withdrawals` int NULL DEFAULT 0, - `balance` decimal(10, 2) NULL DEFAULT 0.00, - `is_risk_user` tinyint(1) NULL DEFAULT 0 COMMENT '是否为风险用户', - `is_blacklisted` tinyint(1) NULL DEFAULT 0 COMMENT '是否被拉黑', - `risk_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '风险原因', - `blacklist_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '拉黑原因', - `blacklisted_at` timestamp NULL DEFAULT NULL COMMENT '拉黑时间', - `business_license` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card_front` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card_back` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `audit_status` enum('pending','approved','rejected') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `audit_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `audited_by` int NULL DEFAULT NULL, - `audited_at` timestamp NULL DEFAULT NULL, - `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `district_id` int NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `username`(`username` ASC) USING BTREE, - UNIQUE INDEX `email`(`email` ASC) USING BTREE, - UNIQUE INDEX `phone`(`phone` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9548 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for zhejiang_regions --- ---------------------------- -DROP TABLE IF EXISTS `zhejiang_regions`; -CREATE TABLE `zhejiang_regions` ( - `id` int NOT NULL AUTO_INCREMENT, - `city_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `district_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `region_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `is_available` tinyint(1) NULL DEFAULT 1, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `region_code`(`region_code` ASC) USING BTREE, - UNIQUE INDEX `unique_region`(`city_name` ASC, `district_name` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 20041 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/test_maoj.sql b/test_maoj.sql deleted file mode 100644 index 578336d..0000000 --- a/test_maoj.sql +++ /dev/null @@ -1,817 +0,0 @@ -/* - Navicat Premium Dump SQL - - Source Server : 测试端 - Source Server Type : MySQL - Source Server Version : 80036 (8.0.36) - Source Host : 114.55.111.44:3306 - Source Schema : test_mao - - Target Server Type : MySQL - Target Server Version : 80036 (8.0.36) - File Encoding : 65001 - - Date: 01/09/2025 10:09:09 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for accounts --- ---------------------------- -DROP TABLE IF EXISTS `accounts`; -CREATE TABLE `accounts` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `account_type` enum('public','user') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user', - `balance` decimal(10, 2) NULL DEFAULT 0.00, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `accounts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for address_labels --- ---------------------------- -DROP TABLE IF EXISTS `address_labels`; -CREATE TABLE `address_labels` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NULL DEFAULT NULL, - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `is_system` tinyint(1) NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `color` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#1890ff', - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_user_label`(`user_id` ASC, `name` ASC) USING BTREE, - CONSTRAINT `address_labels_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 61 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for admin_operation_logs --- ---------------------------- -DROP TABLE IF EXISTS `admin_operation_logs`; -CREATE TABLE `admin_operation_logs` ( - `id` int NOT NULL AUTO_INCREMENT, - `admin_id` int NOT NULL, - `operation_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `target_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `target_id` int NOT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `admin_id`(`admin_id` ASC) USING BTREE, - CONSTRAINT `admin_operation_logs_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_commission_records --- ---------------------------- -DROP TABLE IF EXISTS `agent_commission_records`; -CREATE TABLE `agent_commission_records` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `merchant_id` int NOT NULL, - `order_id` int NULL DEFAULT NULL, - `commission_amount` decimal(10, 2) NOT NULL, - `commission_type` enum('registration','matching') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'matching', - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `agent_id`(`agent_id` ASC) USING BTREE, - INDEX `merchant_id`(`merchant_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `agent_commission_records_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_commission_records_ibfk_2` FOREIGN KEY (`merchant_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_commission_records_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `matching_orders` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_merchants --- ---------------------------- -DROP TABLE IF EXISTS `agent_merchants`; -CREATE TABLE `agent_merchants` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `merchant_id` int NOT NULL, - `registration_code_id` int NULL DEFAULT NULL, - `matching_count` int NULL DEFAULT 0, - `commission_earned` decimal(10, 2) NULL DEFAULT 0.00, - `is_qualified` tinyint(1) NULL DEFAULT 0, - `qualified_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_agent_merchant`(`agent_id` ASC, `merchant_id` ASC) USING BTREE, - INDEX `merchant_id`(`merchant_id` ASC) USING BTREE, - INDEX `registration_code_id`(`registration_code_id` ASC) USING BTREE, - CONSTRAINT `agent_merchants_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_merchants_ibfk_2` FOREIGN KEY (`merchant_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_merchants_ibfk_3` FOREIGN KEY (`registration_code_id`) REFERENCES `registration_codes` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for agent_withdrawals --- ---------------------------- -DROP TABLE IF EXISTS `agent_withdrawals`; -CREATE TABLE `agent_withdrawals` ( - `id` int NOT NULL AUTO_INCREMENT, - `agent_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `payment_type` enum('bank','wechat','alipay','unionpay') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'bank' COMMENT '收款方式类型', - `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行名称', - `account_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号/银行账号', - `account_holder` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '持有人姓名', - `qr_code_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收款码图片URL', - `status` enum('pending','approved','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'pending', - `apply_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `admin_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, - `processed_by` int NULL DEFAULT NULL, - `processed_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `bank_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行账号(兼容旧版本)', - PRIMARY KEY (`id`) USING BTREE, - INDEX `agent_id`(`agent_id` ASC) USING BTREE, - INDEX `processed_by`(`processed_by` ASC) USING BTREE, - CONSTRAINT `agent_withdrawals_ibfk_1` FOREIGN KEY (`agent_id`) REFERENCES `regional_agents` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `agent_withdrawals_ibfk_2` FOREIGN KEY (`processed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for articles --- ---------------------------- -DROP TABLE IF EXISTS `articles`; -CREATE TABLE `articles` ( - `id` int NOT NULL AUTO_INCREMENT, - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `author_id` int NULL DEFAULT NULL, - `status` enum('draft','published') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'draft', - `views` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `author_id`(`author_id` ASC) USING BTREE, - CONSTRAINT `articles_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for balance_fix_log --- ---------------------------- -DROP TABLE IF EXISTS `balance_fix_log`; -CREATE TABLE `balance_fix_log` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `amount_deducted` decimal(10, 2) NOT NULL, - `transfer_count` int NOT NULL, - `fix_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `idx_user_id`(`user_id` ASC) USING BTREE, - INDEX `idx_created_at`(`created_at` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for cart_items --- ---------------------------- -DROP TABLE IF EXISTS `cart_items`; -CREATE TABLE `cart_items` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `product_id` int NOT NULL, - `quantity` int NOT NULL DEFAULT 1, - `specification_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_user_product_spec`(`user_id` ASC, `product_id` ASC, `specification_id` ASC) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - INDEX `specification_id`(`specification_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for china_regions --- ---------------------------- -DROP TABLE IF EXISTS `china_regions`; -CREATE TABLE `china_regions` ( - `id` int NOT NULL AUTO_INCREMENT, - `code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `parent_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, - `level` tinyint NOT NULL COMMENT '1:省 2:市 3:区', - `sort_order` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `code`(`code` ASC) USING BTREE, - INDEX `idx_parent_code`(`parent_code` ASC) USING BTREE, - INDEX `idx_level`(`level` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 4621 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for matching_orders --- ---------------------------- -DROP TABLE IF EXISTS `matching_orders`; -CREATE TABLE `matching_orders` ( - `id` int NOT NULL AUTO_INCREMENT, - `initiator_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `status` enum('pending','matching','completed','cancelled','failed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `cycle_count` int NULL DEFAULT 0, - `max_cycles` int NULL DEFAULT 3, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `matching_type` enum('small','large') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'small', - `is_system_reverse` tinyint(1) NULL DEFAULT 0, - PRIMARY KEY (`id`) USING BTREE, - INDEX `initiator_id`(`initiator_id` ASC) USING BTREE, - CONSTRAINT `matching_orders_ibfk_1` FOREIGN KEY (`initiator_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 441 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for matching_records --- ---------------------------- -DROP TABLE IF EXISTS `matching_records`; -CREATE TABLE `matching_records` ( - `id` int NOT NULL AUTO_INCREMENT, - `matching_order_id` int NOT NULL, - `user_id` int NOT NULL, - `action` enum('join','confirm','reject','complete') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `amount` decimal(10, 2) NULL DEFAULT NULL, - `note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `matching_order_id`(`matching_order_id` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `matching_records_ibfk_1` FOREIGN KEY (`matching_order_id`) REFERENCES `matching_orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `matching_records_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1841 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for order_allocations --- ---------------------------- -DROP TABLE IF EXISTS `order_allocations`; -CREATE TABLE `order_allocations` ( - `id` int NOT NULL AUTO_INCREMENT, - `matching_order_id` int NOT NULL, - `from_user_id` int NOT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `cycle_number` int NOT NULL, - `status` enum('pending','confirmed','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `transfer_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `confirmed_at` timestamp NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - INDEX `matching_order_id`(`matching_order_id` ASC) USING BTREE, - INDEX `from_user_id`(`from_user_id` ASC) USING BTREE, - INDEX `to_user_id`(`to_user_id` ASC) USING BTREE, - INDEX `transfer_id`(`transfer_id` ASC) USING BTREE, - CONSTRAINT `order_allocations_ibfk_1` FOREIGN KEY (`matching_order_id`) REFERENCES `matching_orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_2` FOREIGN KEY (`from_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_3` FOREIGN KEY (`to_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `order_allocations_ibfk_4` FOREIGN KEY (`transfer_id`) REFERENCES `transfers` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1078 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for order_allocations_backup --- ---------------------------- -DROP TABLE IF EXISTS `order_allocations_backup`; -CREATE TABLE `order_allocations_backup` ( - `id` int NOT NULL DEFAULT 0, - `matching_order_id` int NOT NULL, - `from_user_id` int NOT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `cycle_number` int NOT NULL, - `status` enum('pending','confirmed','rejected','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `transfer_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `confirmed_at` timestamp NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for order_items --- ---------------------------- -DROP TABLE IF EXISTS `order_items`; -CREATE TABLE `order_items` ( - `id` int NOT NULL AUTO_INCREMENT, - `order_id` int NOT NULL, - `product_id` int NOT NULL, - `spec_combination_id` int NULL DEFAULT NULL COMMENT '规格组合ID', - `quantity` int NOT NULL, - `price` int NOT NULL, - `points` int NOT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `points_price` int NOT NULL DEFAULT 0, - `rongdou_price` int NOT NULL DEFAULT 0, - `rongdou` int NULL DEFAULT 0, - PRIMARY KEY (`id`) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for orders --- ---------------------------- -DROP TABLE IF EXISTS `orders`; -CREATE TABLE `orders` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `total_amount` int NOT NULL, - `total_points` int NOT NULL, - `total_rongdou` int NOT NULL DEFAULT 0, - `status` enum('pending','paid','shipped','delivered','cancelled','pre_order','completed') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `address` json NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `order_no`(`order_no` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for points_history --- ---------------------------- -DROP TABLE IF EXISTS `points_history`; -CREATE TABLE `points_history` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `type` enum('earn','spend','admin_adjust','refund') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `amount` int NOT NULL, - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `order_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `points_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `points_history_ibfk_2` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_attributes --- ---------------------------- -DROP TABLE IF EXISTS `product_attributes`; -CREATE TABLE `product_attributes` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL, - `attribute_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `attribute_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `sort_order` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - CONSTRAINT `product_attributes_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_favorites --- ---------------------------- -DROP TABLE IF EXISTS `product_favorites`; -CREATE TABLE `product_favorites` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `product_id` int NOT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_user_product`(`user_id` ASC, `product_id` ASC) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - CONSTRAINT `product_favorites_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `product_favorites_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_reviews --- ---------------------------- -DROP TABLE IF EXISTS `product_reviews`; -CREATE TABLE `product_reviews` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL, - `user_id` int NOT NULL, - `order_id` int NOT NULL, - `rating` int NOT NULL, - `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `images` json NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `product_reviews_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `product_reviews_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `product_reviews_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_spec_combinations --- ---------------------------- -DROP TABLE IF EXISTS `product_spec_combinations`; -CREATE TABLE `product_spec_combinations` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL COMMENT '商品ID', - `combination_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '组合键,如:color_1-size_2-material_3', - `spec_values` json NOT NULL COMMENT '规格值组合,存储spec_value_id数组', - `price_adjustment` int NULL DEFAULT 0 COMMENT '价格调整', - `points_adjustment` int NULL DEFAULT 0 COMMENT '积分调整', - `rongdou_adjustment` int NULL DEFAULT 0 COMMENT '融豆调整', - `stock` int NULL DEFAULT 0 COMMENT '库存', - `sku_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'SKU编码', - `barcode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '条形码', - `weight` decimal(8, 3) NULL DEFAULT NULL COMMENT '重量(kg)', - `volume` decimal(10, 3) NULL DEFAULT NULL COMMENT '体积(cm³)', - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_product_combination`(`product_id` ASC, `combination_key` ASC) USING BTREE, - INDEX `idx_product_status`(`product_id` ASC, `status` ASC) USING BTREE, - INDEX `idx_sku_code`(`sku_code` ASC) USING BTREE, - CONSTRAINT `product_spec_combinations_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_spec_names --- ---------------------------- -DROP TABLE IF EXISTS `product_spec_names`; -CREATE TABLE `product_spec_names` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL COMMENT '商品ID', - `spec_name_id` int NOT NULL COMMENT '规格名称ID', - `is_required` tinyint(1) NULL DEFAULT 1 COMMENT '是否必选规格', - `sort_order` int NULL DEFAULT 0 COMMENT '排序', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_product_spec_name`(`product_id` ASC, `spec_name_id` ASC) USING BTREE, - INDEX `spec_name_id`(`spec_name_id` ASC) USING BTREE, - CONSTRAINT `product_spec_names_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `product_spec_names_ibfk_2` FOREIGN KEY (`spec_name_id`) REFERENCES `spec_names` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for product_specifications --- ---------------------------- -DROP TABLE IF EXISTS `product_specifications`; -CREATE TABLE `product_specifications` ( - `id` int NOT NULL AUTO_INCREMENT, - `product_id` int NOT NULL, - `spec_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `spec_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `price_adjustment` int NULL DEFAULT 0, - `points_adjustment` int NULL DEFAULT 0, - `rongdou_adjustment` int NULL DEFAULT 0, - `stock` int NULL DEFAULT 0, - `sku_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `product_id`(`product_id` ASC) USING BTREE, - CONSTRAINT `product_specifications_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for products --- ---------------------------- -DROP TABLE IF EXISTS `products`; -CREATE TABLE `products` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `price` int NOT NULL, - `original_price` int NULL DEFAULT NULL, - `stock` int NULL DEFAULT 0, - `sales` int NULL DEFAULT 0, - `rating` decimal(3, 2) NULL DEFAULT 5.00, - `category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `images` json NULL, - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `points_price` int NOT NULL DEFAULT 0, - `image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `details` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `rongdou_price` int NOT NULL DEFAULT 0, - `videos` json NULL, - `shop_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `shop_avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `payment_methods` json NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for regional_agents --- ---------------------------- -DROP TABLE IF EXISTS `regional_agents`; -CREATE TABLE `regional_agents` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `region_id` int NOT NULL, - `agent_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `status` enum('pending','active','suspended','terminated') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'pending', - `commission_rate` decimal(5, 4) NULL DEFAULT 0.0500, - `total_earnings` decimal(10, 2) NULL DEFAULT 0.00, - `recruited_merchants` int NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `approved_at` timestamp NULL DEFAULT NULL, - `approved_by_admin_id` int NULL DEFAULT NULL, - `withdrawn_amount` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '已提现金额', - `pending_withdrawal` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '待审核提现金额', - `payment_type` enum('bank','wechat','alipay','unionpay') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'bank' COMMENT '收款方式类型', - `account_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号/银行账号', - `account_holder` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '持有人姓名', - `qr_code_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收款码图片URL', - `bank_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行账号(兼容旧版本)', - `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '银行名称', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `agent_code`(`agent_code` ASC) USING BTREE, - UNIQUE INDEX `unique_agent_region`(`user_id` ASC, `region_id` ASC) USING BTREE, - INDEX `region_id`(`region_id` ASC) USING BTREE, - CONSTRAINT `regional_agents_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `regional_agents_ibfk_2` FOREIGN KEY (`region_id`) REFERENCES `zhejiang_regions` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for registration_codes --- ---------------------------- -DROP TABLE IF EXISTS `registration_codes`; -CREATE TABLE `registration_codes` ( - `id` int NOT NULL AUTO_INCREMENT, - `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '注册码', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `expires_at` timestamp NOT NULL COMMENT '过期时间', - `used_at` timestamp NULL DEFAULT NULL COMMENT '使用时间', - `used_by_user_id` int NULL DEFAULT NULL COMMENT '使用该注册码的用户ID', - `is_used` tinyint(1) NULL DEFAULT 0 COMMENT '是否已使用', - `created_by_admin_id` int NOT NULL COMMENT '创建该注册码的管理员ID', - `agent_id` int NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `code`(`code` ASC) USING BTREE, - INDEX `idx_code`(`code` ASC) USING BTREE, - INDEX `idx_expires_at`(`expires_at` ASC) USING BTREE, - INDEX `idx_is_used`(`is_used` ASC) USING BTREE, - INDEX `used_by_user_id`(`used_by_user_id` ASC) USING BTREE, - INDEX `created_by_admin_id`(`created_by_admin_id` ASC) USING BTREE, - INDEX `fk_registration_codes_agent_id`(`agent_id` ASC) USING BTREE, - CONSTRAINT `fk_registration_codes_agent_id` FOREIGN KEY (`agent_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT, - CONSTRAINT `registration_codes_ibfk_1` FOREIGN KEY (`used_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT, - CONSTRAINT `registration_codes_ibfk_2` FOREIGN KEY (`created_by_admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 150 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '注册码表' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for rongdou_history --- ---------------------------- -DROP TABLE IF EXISTS `rongdou_history`; -CREATE TABLE `rongdou_history` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `type` enum('earn','spend') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `amount` int NOT NULL, - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, - `order_id` int NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - INDEX `order_id`(`order_id` ASC) USING BTREE, - CONSTRAINT `rongdou_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `rongdou_history_ibfk_2` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for spec_names --- ---------------------------- -DROP TABLE IF EXISTS `spec_names`; -CREATE TABLE `spec_names` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '规格名称,如:颜色、尺寸、材质', - `display_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '显示名称', - `sort_order` int NULL DEFAULT 0 COMMENT '排序', - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_name`(`name` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for spec_values --- ---------------------------- -DROP TABLE IF EXISTS `spec_values`; -CREATE TABLE `spec_values` ( - `id` int NOT NULL AUTO_INCREMENT, - `spec_name_id` int NOT NULL COMMENT '规格名称ID', - `value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '规格值,如:红色、XL、棉质', - `display_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '显示值', - `color_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '颜色代码(仅颜色规格使用)', - `image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格图片', - `sort_order` int NULL DEFAULT 0 COMMENT '排序', - `status` enum('active','inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'active', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_spec_value`(`spec_name_id` ASC, `value` ASC) USING BTREE, - CONSTRAINT `spec_values_ibfk_1` FOREIGN KEY (`spec_name_id`) REFERENCES `spec_names` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for system_settings --- ---------------------------- -DROP TABLE IF EXISTS `system_settings`; -CREATE TABLE `system_settings` ( - `id` int NOT NULL AUTO_INCREMENT, - `setting_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `setting_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `setting_key`(`setting_key` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 77 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for test_users --- ---------------------------- -DROP TABLE IF EXISTS `test_users`; -CREATE TABLE `test_users` ( - `id` int NOT NULL AUTO_INCREMENT, - `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for transfer_confirmations --- ---------------------------- -DROP TABLE IF EXISTS `transfer_confirmations`; -CREATE TABLE `transfer_confirmations` ( - `id` int NOT NULL AUTO_INCREMENT, - `transfer_id` int NOT NULL, - `confirmer_id` int NOT NULL, - `action` enum('confirm','reject') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `transfer_id`(`transfer_id` ASC) USING BTREE, - INDEX `confirmer_id`(`confirmer_id` ASC) USING BTREE, - CONSTRAINT `transfer_confirmations_ibfk_1` FOREIGN KEY (`transfer_id`) REFERENCES `transfers` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `transfer_confirmations_ibfk_2` FOREIGN KEY (`confirmer_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for transfers --- ---------------------------- -DROP TABLE IF EXISTS `transfers`; -CREATE TABLE `transfers` ( - `id` int NOT NULL AUTO_INCREMENT, - `from_user_id` int NULL DEFAULT NULL, - `to_user_id` int NOT NULL, - `amount` decimal(10, 2) NOT NULL, - `transfer_type` enum('initial','return','user_to_user','system_to_user','user_to_system') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user_to_user', - `status` enum('pending','confirmed','rejected','received','not_received','cancelled') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `voucher_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `batch_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deadline_at` timestamp NULL DEFAULT NULL COMMENT '转账截止时间', - `is_overdue` tinyint(1) NULL DEFAULT 0 COMMENT '是否超时', - `overdue_at` timestamp NULL DEFAULT NULL COMMENT '超时时间', - `is_bad_debt` tinyint(1) NULL DEFAULT 0, - `confirmed_at` timestamp NULL DEFAULT NULL, - `admin_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `admin_modified_at` timestamp NULL DEFAULT NULL, - `admin_modified_by` int NULL DEFAULT NULL, - `source_type` enum('manual','allocation','system') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'manual' COMMENT '转账来源类型', - `matching_order_id` int NULL DEFAULT NULL, - `cycle_number` int NULL DEFAULT NULL, - `outbound_date` date NULL DEFAULT NULL, - `return_date` date NULL DEFAULT NULL, - `can_return_after` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - INDEX `from_user_id`(`from_user_id` ASC) USING BTREE, - INDEX `to_user_id`(`to_user_id` ASC) USING BTREE, - CONSTRAINT `transfers_ibfk_1` FOREIGN KEY (`from_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `transfers_ibfk_2` FOREIGN KEY (`to_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 1529 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for user_addresses --- ---------------------------- -DROP TABLE IF EXISTS `user_addresses`; -CREATE TABLE `user_addresses` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `receiver_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `receiver_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `province` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `district` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `detailed_address` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `postal_code` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, - `label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '家', - `is_default` tinyint(1) NULL DEFAULT 0, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id`(`user_id` ASC) USING BTREE, - CONSTRAINT `user_addresses_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for user_matching_pool --- ---------------------------- -DROP TABLE IF EXISTS `user_matching_pool`; -CREATE TABLE `user_matching_pool` ( - `id` int NOT NULL AUTO_INCREMENT, - `user_id` int NOT NULL, - `available_amount` decimal(10, 2) NULL DEFAULT 0.00, - `is_active` tinyint(1) NULL DEFAULT 1, - `last_matched_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_user`(`user_id` ASC) USING BTREE, - CONSTRAINT `user_matching_pool_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 61 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for users --- ---------------------------- -DROP TABLE IF EXISTS `users`; -CREATE TABLE `users` ( - `id` int NOT NULL AUTO_INCREMENT, - `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `role` enum('user','admin') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user', - `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `points` int NULL DEFAULT 0, - `real_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `wechat_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `alipay_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `bank_card` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `unionpay_qr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `is_system_account` tinyint(1) NULL DEFAULT 0, - `completed_withdrawals` int NULL DEFAULT 0, - `balance` decimal(10, 2) NULL DEFAULT 0.00, - `is_risk_user` tinyint(1) NULL DEFAULT 0 COMMENT '是否为风险用户', - `is_blacklisted` tinyint(1) NULL DEFAULT 0 COMMENT '是否被拉黑', - `risk_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '风险原因', - `blacklist_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '拉黑原因', - `blacklisted_at` timestamp NULL DEFAULT NULL COMMENT '拉黑时间', - `business_license` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card_front` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `id_card_back` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `audit_status` enum('pending','approved','rejected') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'pending', - `audit_note` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, - `audited_by` int NULL DEFAULT NULL, - `audited_at` timestamp NULL DEFAULT NULL, - `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, - `district_id` int NULL DEFAULT NULL, - `isdistribute` tinyint NULL DEFAULT 1, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `username`(`username` ASC) USING BTREE, - UNIQUE INDEX `email`(`email` ASC) USING BTREE, - UNIQUE INDEX `phone`(`phone` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9788 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Table structure for zhejiang_regions --- ---------------------------- -DROP TABLE IF EXISTS `zhejiang_regions`; -CREATE TABLE `zhejiang_regions` ( - `id` int NOT NULL AUTO_INCREMENT, - `city_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `district_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `region_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `is_available` tinyint(1) NULL DEFAULT 1, - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `region_code`(`region_code` ASC) USING BTREE, - UNIQUE INDEX `unique_region`(`city_name` ASC, `district_name` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 23234 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; - -SET FOREIGN_KEY_CHECKS = 1;