diff --git a/.codebuddy/.rules/my-rule.mdc b/.codebuddy/.rules/my-rule.mdc new file mode 100644 index 0000000..17070ff --- /dev/null +++ b/.codebuddy/.rules/my-rule.mdc @@ -0,0 +1,35 @@ +# my-rule + +这是一个规则文件,用于帮助 AI 理解您的代码库和遵循项目约定。 +1. 请保持对话语言为中文 +2. 我的系统为 macos +3. 远程服务器为centos10 64位 +4. 项目文件夹结构为: + - docs 文档目录 + - admin-system 管理后台目录 + - mini-program 小程序app目录 + - backend 后端服务目录 + - website 官网目录 + - scripts 脚本目录 放置一些脚本,如: + - 数据库脚本 + - 部署脚本 + - 测试脚本 + - 运维脚本 +5. 整个项目入口文档为根目录下的readme.md,其他文档请放在docs目录下 +6. 请使用markdown格式编写文档,整个项目文档包括: + - 需求文档:整个项目需求文档.md 官网需求文档.md 后端管理需求文档.md 管理后台需求文档.md 小程序app需求文档.md + - 架构文档:整个项目的架构文档.md 后端架构文档.md 小程序架构文档.md 管理后台架构文档.md + - 详细设计文档: + - 数据库设计文档.md + - 管理后台接口设计文档.md + - 小程序app接口设计文档.md + - 开发文档: + - 后端开发文档.md 包含:细分到每个子任务的开发计划 + - 小程序app开发文档.md 包含:细分到每个子任务的开发计划 + - 管理后台开发文档.md 包含:细分到每个子任务的开发计划 + - 后端管理开发文档.md 包含:细分到每个子任务的开发计划 + - 测试文档.md + - 部署文档.md + - 运维文档.md + - 安全文档.md + - 用户手册文档.md \ No newline at end of file diff --git a/README.md b/README.md index 6424ecc..68afba2 100644 --- a/README.md +++ b/README.md @@ -49,245 +49,166 @@ ``` jiebanke/ -├── 📁 backend/ # 后端服务 (Node.js + Express) -├── 📁 admin-system/ # 后台管理系统 (Vue 3 + Element Plus) -├── 📁 website/ # 官方网站 (Vue 3) -├── 📁 mini-program/ # 微信小程序矩阵 (原生小程序) -├── 📁 docs/ # 项目文档 -├── 📁 scripts/ # 工具脚本 -├── 📁 test/ # 测试文件目录 -└── 📄 README.md # 项目说明 (当前文件) +├── README.md # 项目入口文档 +├── docs/ # 项目文档目录 +│ ├── 整个项目需求文档.md # 项目整体需求 +│ ├── 官网需求文档.md # 官网需求 +│ ├── 后端管理需求文档.md # 后端管理需求 +│ ├── 管理后台需求文档.md # 管理后台需求 +│ ├── 小程序app需求文档.md # 小程序需求 +│ ├── 整个项目的架构文档.md # 项目整体架构 +│ ├── 后端架构文档.md # 后端架构 +│ ├── 小程序架构文档.md # 小程序架构 +│ ├── 管理后台架构文档.md # 管理后台架构 +│ ├── 数据库设计文档.md # 数据库设计 +│ ├── 管理后台接口设计文档.md # 管理后台接口 +│ ├── 小程序app接口设计文档.md # 小程序接口 +│ ├── 后端开发文档.md # 后端开发指南 +│ ├── 小程序app开发文档.md # 小程序开发指南 +│ ├── 管理后台开发文档.md # 管理后台开发指南 +│ ├── 后端管理开发文档.md # 后端管理开发指南 +│ ├── 测试文档.md # 测试策略和规范 +│ ├── 部署文档.md # 部署指南 +│ ├── 运维文档.md # 运维手册 +│ ├── 安全文档.md # 安全规范 +│ └── 用户手册文档.md # 用户操作手册 +├── admin-system/ # 管理后台目录 +├── mini-program/ # 小程序app目录 +├── backend/ # 后端服务目录 +├── website/ # 官网目录 +└── scripts/ # 脚本目录 + ├── database/ # 数据库脚本 + ├── deploy/ # 部署脚本 + ├── test/ # 测试脚本 + └── ops/ # 运维脚本 ``` +## 📚 文档导航 + +### 需求文档 +- [整个项目需求文档](./docs/整个项目需求文档.md) - 项目整体需求和功能规划 +- [官网需求文档](./docs/官网需求文档.md) - 官网功能需求 +- [后端管理需求文档](./docs/后端管理需求文档.md) - 后端管理系统需求 +- [管理后台需求文档](./docs/管理后台需求文档.md) - 管理后台功能需求 +- [小程序app需求文档](./docs/小程序app需求文档.md) - 小程序功能需求 + +### 架构文档 +- [整个项目的架构文档](./docs/整个项目的架构文档.md) - 项目整体架构设计 +- [后端架构文档](./docs/后端架构文档.md) - 后端服务架构 +- [小程序架构文档](./docs/小程序架构文档.md) - 小程序架构设计 +- [管理后台架构文档](./docs/管理后台架构文档.md) - 管理后台架构 + +### 详细设计文档 +- [数据库设计文档](./docs/数据库设计文档.md) - 数据库表结构和关系设计 +- [管理后台接口设计文档](./docs/管理后台接口设计文档.md) - 管理后台API接口 +- [小程序app接口设计文档](./docs/小程序app接口设计文档.md) - 小程序API接口 + +### 开发文档 +- [后端开发文档](./docs/后端开发文档.md) - 后端开发指南和任务计划 +- [小程序app开发文档](./docs/小程序app开发文档.md) - 小程序开发指南和任务计划 +- [管理后台开发文档](./docs/管理后台开发文档.md) - 管理后台开发指南和任务计划 +- [后端管理开发文档](./docs/后端管理开发文档.md) - 后端管理开发指南和任务计划 + +### 运维文档 +- [测试文档](./docs/测试文档.md) - 测试策略、用例和自动化测试 +- [部署文档](./docs/部署文档.md) - 环境部署和配置指南 +- [运维文档](./docs/运维文档.md) - 系统监控、维护和故障处理 +- [安全文档](./docs/安全文档.md) - 安全策略和防护措施 +- [用户手册文档](./docs/用户手册文档.md) - 用户操作指南 + ## 🚀 快速开始 ### 环境要求 -- Node.js 16.x+ +- Node.js 18+ - MySQL 8.0+ -- npm 8.x+ +- Redis 6.0+ +- Docker & Docker Compose (可选) -### 安装依赖 +### 本地开发 + +1. **克隆项目** ```bash -# 安装后端依赖 -cd backend && npm install - -# 安装后台管理依赖 -cd admin-system && npm install - -# 安装官网依赖 -cd website && npm install - -# 安装小程序依赖 -cd mini-program && npm install +git clone https://github.com/your-org/jiebanke.git +cd jiebanke ``` -### 启动开发环境 +2. **安装依赖** ```bash -# 启动后端服务 -cd backend && npm run dev +# 后端服务 +cd backend +npm install -# 启动后台管理 (新终端) -cd admin-system && npm run dev +# 管理后台 +cd ../admin-system +npm install -# 启动官方网站 (新终端) -cd website && npm run dev +# 小程序 +cd ../mini-program +npm install ``` -## 📖 项目文档 - -所有详细文档位于 `docs/` 目录: - -### 📖 快速导航 - -| 文档类型 | 文档名称 | 描述 | 适用人员 | -|---------|---------|------|---------| -| 🚀 快速开始 | [系统集成和部署文档](docs/系统集成和部署文档.md) | 环境搭建、部署流程 | 开发者、运维 | -| 🔧 开发指南 | [前端开发文档](docs/前端开发文档.md) | 前端开发规范和指南 | 前端开发者 | -| 🔧 开发指南 | [后端开发文档](docs/后端开发文档.md) | 后端开发规范和指南 | 后端开发者 | -| 📋 API参考 | [API接口文档](docs/API接口文档.md) | 完整的API接口文档 | 全栈开发者 | -| 🗄️ 数据设计 | [数据库设计文档](docs/数据库设计文档.md) | 数据库结构设计 | 后端开发者、DBA | -| 👨‍💼 管理功能 | [管理员后台系统API文档](docs/管理员后台系统API文档.md) | 管理后台功能说明 | 管理员、开发者 | -| 📁 文件系统 | [文件上传系统文档](docs/文件上传系统文档.md) | 文件上传和管理 | 全栈开发者 | -| 🔍 监控运维 | [错误处理和日志系统文档](docs/错误处理和日志系统文档.md) | 错误处理和日志 | 开发者、运维 | -| 🧪 质量保证 | [测试文档](docs/测试文档.md) | 测试策略、用例设计和质量保证 | 测试工程师、开发者 | -| 🔒 安全管理 | [安全和权限管理文档](docs/安全和权限管理文档.md) | 安全策略、权限控制、安全防护措施 | 安全工程师、系统管理员 | -| ⚡ 性能优化 | [性能优化文档](docs/性能优化文档.md) | 系统性能优化策略、监控方案和优化实践 | 性能工程师、运维工程师 | -| 🚀 部署运维 | [部署和运维文档](docs/部署和运维文档.md) | 系统部署流程和运维管理方案 | 运维工程师、DevOps工程师 | -| 📊 项目管理 | [项目开发进度报告](docs/项目开发进度报告.md) | 项目进度和规划 | 项目经理、开发者 | -| 📝 开发规范 | [开发规范和最佳实践](docs/开发规范和最佳实践.md) | 代码规范和标准 | 全体开发者 | - -### 核心文档 -- 📄 [项目概述](docs/项目概述.md) - 项目背景、目标和整体介绍 -- 📄 [系统架构文档](docs/系统架构文档.md) - 系统架构设计和技术栈 -- 📄 [API接口文档](docs/API接口文档.md) - 完整的API接口说明 -- 📄 [数据库设计文档](docs/数据库设计文档.md) - 数据库表结构和关系设计 -- 📄 [开发指南](docs/开发指南.md) - 开发环境搭建和开发规范 -- 📄 [部署指南](docs/部署指南.md) - 开发、测试、生产环境部署指南 - -### 功能模块文档 - -| 文档名称 | 描述 | 链接 | -|---------|------|------| -| API接口文档 | 详细的API接口说明和使用示例 | [查看文档](./docs/API接口文档.md) | -| 管理员后台文档 | 管理员功能和操作指南 | [查看文档](./docs/管理员后台文档.md) | -| 用户认证系统文档 | 用户注册、登录、权限管理 | [查看文档](./docs/用户认证系统文档.md) | -| 动物管理系统文档 | 动物信息管理和认领流程 | [查看文档](./docs/动物管理系统文档.md) | -| 文件上传系统文档 | 文件上传、存储和管理 | [查看文档](./docs/文件上传系统文档.md) | -| 数据库设计文档 | 数据库架构、表结构和关系设计 | [查看文档](./docs/数据库设计文档.md) | -| 错误处理和日志系统文档 | 错误处理机制和日志记录 | [查看文档](./docs/错误处理和日志系统文档.md) | -| 系统集成和部署文档 | 系统部署和运维指南 | [查看文档](./docs/系统集成和部署文档.md) | -| 前端开发文档 | 前端技术架构、组件设计和开发规范 | [查看文档](./docs/前端开发文档.md) | - -#### 项目管理文档 -- **[项目开发进度报告](docs/项目开发进度报告.md)** - 项目进度跟踪和里程碑规划 -- **[开发规范和最佳实践](docs/开发规范和最佳实践.md)** - 团队开发规范和代码标准 -- **[测试文档](docs/测试文档.md)** - 测试策略、用例设计和质量保证 - -### 补充文档 -- 📄 [变更日志](CHANGELOG.md) - 项目版本变更记录 -- 📄 [贡献指南](docs/贡献指南.md) - 如何参与项目开发 -- 📄 [常见问题](docs/常见问题.md) - 开发和使用中的常见问题解答 -- 📄 [许可证](LICENSE.md) - 项目许可证信息 - -## 🛠️ 开发工具 - -### 脚本工具 -项目提供了一些有用的开发脚本: - +3. **配置环境** ```bash -# 数据库连接测试 -cd backend && npm run test-db - -# API接口测试 -cd backend && npm run test-api - -# 数据库初始化 -cd backend && npm run db:reset - -# 部署脚本 (Linux/Mac) -cd scripts && ./deploy.sh all - -# 部署脚本 (Windows PowerShell) -cd scripts && .\deploy.ps1 all -``` - -### 环境配置 -复制环境变量模板并配置: - -```bash -# 后端环境配置 +# 复制环境配置文件 cp backend/.env.example backend/.env - -# 后台管理环境配置 cp admin-system/.env.example admin-system/.env ``` -## ☁️ 部署 - -项目支持多种部署方式: - -### 自动部署脚本 -在 `scripts/` 目录中提供了自动部署脚本,支持 Linux/Mac 和 Windows 系统: - +4. **启动服务** ```bash -# Linux/Mac 部署所有模块 -cd scripts && chmod +x deploy.sh && ./deploy.sh all +# 启动后端服务 +cd backend +npm run dev -# Windows PowerShell 部署所有模块 -cd scripts && .\deploy.ps1 all +# 启动管理后台 +cd admin-system +npm run dev + +# 启动小程序开发工具 +cd mini-program +npm run dev ``` -支持的部署选项: -- `all` - 部署所有模块 -- `backend` - 部署后端服务 -- `admin` - 部署后台管理系统 -- `website` - 部署官方网站 -- `mini-program` - 构建微信小程序 - -### Docker 容器化部署 -每个模块都提供了 Docker 配置文件,可以使用 docker-compose 进行部署: +### Docker 部署 ```bash -# 启动所有服务 +# 构建并启动所有服务 docker-compose up -d -# 启动指定服务 -docker-compose up -d backend - # 查看服务状态 docker-compose ps + +# 查看日志 +docker-compose logs -f ``` -### 手动部署 -每个模块也可以手动部署到服务器,具体说明请参考各模块目录中的 DEPLOYMENT.md 文件。 +## 📋 开发规范 -## 🌐 访问地址 +- **代码规范**: 遵循 ESLint + Prettier 配置 +- **提交规范**: 使用 Conventional Commits 格式 +- **分支策略**: Git Flow 工作流 +- **测试要求**: 单元测试覆盖率 ≥ 80% +- **文档更新**: 代码变更需同步更新文档 -- **后端API**: https://webapi.jiebanke.com -- **后台管理**: https://admin.jiebanke.com -- **官方网站**: https://www.jiebanke.com -- **小程序**: 使用微信开发者工具打开 `mini-program/` 目录 +## 🤝 贡献指南 -## 📦 依赖管理 +1. Fork 项目 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 创建 Pull Request -### 主要技术栈 +## 📄 许可证 -**后端**: -- Node.js + Express.js -- Sequelize ORM -- JWT 认证 -- MySQL 数据库 +本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情 -**前端**: -- Vue 3 + TypeScript -- Element Plus UI -- Vite 构建工具 -- Pinia 状态管理 +## 📞 联系我们 -**小程序**: -- 微信原生小程序 -- Vant Weapp UI -- Uni-app 框架 - -## 🔧 开发规范 - -### 代码风格 -- 使用 ESLint + Prettier 统一代码风格 -- 遵循 Git Commit 消息规范 -- 实行代码审查流程 - -### 分支策略 -- 采用 Git Flow 工作流 -- 功能分支开发 -- 发布分支管理 - -## 🚀 部署说明 - -详细部署指南请参考 [DEPLOYMENT.md](docs/DEPLOYMENT.md),包含: - -- 开发环境部署 -- 测试环境部署 -- 生产环境部署 -- 容器化部署 (Docker) -- 安全配置指南 - -## 📞 支持与维护 - -### 开发团队 -- 后端开发: backend@jiebanke.com -- 前端开发: frontend@jiebanke.com -- 小程序开发: miniprogram@jiebanke.com - -### 运维支持 -- 运维团队: ops@jiebanke.com -- 紧急联系: +86-138-0013-8000 - -## 📊 版本信息 - -- **当前版本**: v1.0.0 -- **Node.js**: 16.20.2 -- **Vue**: 3.3.4 -- **MySQL**: 8.0.33 +- 项目维护者: [团队名称] +- 邮箱: contact@jiebanke.com +- 项目地址: https://github.com/your-org/jiebanke +- 文档地址: https://docs.jiebanke.com --- -*最后更新: 2024年* 📅 \ No newline at end of file + +**注意**: 详细的开发、部署和使用说明请参考 `docs/` 目录下的相关文档。 \ No newline at end of file diff --git a/backend/scripts/check-database-structure.js b/backend/scripts/check-database-structure.js new file mode 100644 index 0000000..2e563f6 --- /dev/null +++ b/backend/scripts/check-database-structure.js @@ -0,0 +1,226 @@ +#!/usr/bin/env node + +/** + * 数据库结构检查脚本 + * 检查数据库表结构和数据完整性 + */ + +const mysql = require('mysql2/promise'); +const config = require('../config/env'); + +async function checkDatabaseStructure() { + let connection; + + try { + console.log('🔍 开始检查数据库结构...'); + + // 创建数据库连接 + const dbConfig = { + host: config.mysql.host, + port: config.mysql.port, + user: config.mysql.user, + password: config.mysql.password, + database: config.mysql.database, + charset: config.mysql.charset, + timezone: config.mysql.timezone + }; + + connection = await mysql.createConnection(dbConfig); + console.log('✅ 数据库连接成功'); + console.log(`📊 数据库: ${dbConfig.database}`); + console.log('='.repeat(60)); + + // 测试基本查询 + const [testRows] = await connection.execute('SELECT 1 + 1 AS result'); + console.log(`✅ 基本查询测试: ${testRows[0].result}`); + + // 检查数据库版本和字符集 + const [versionRows] = await connection.execute('SELECT VERSION() as version'); + console.log(`📊 MySQL版本: ${versionRows[0].version}`); + + // 获取所有表 + console.log('\n📋 检查数据库表结构:'); + const [tables] = await connection.execute(` + SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, TABLE_COMMENT + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = ? + ORDER BY TABLE_NAME + `, [dbConfig.database]); + + if (tables.length === 0) { + console.log('⚠️ 数据库中没有找到任何表'); + console.log('💡 建议运行数据库结构创建脚本'); + return { success: false, message: '数据库为空' }; + } + + console.log(`📊 找到 ${tables.length} 个表:`); + let totalRows = 0; + + for (const table of tables) { + const rowCount = table.TABLE_ROWS || 0; + totalRows += rowCount; + const dataSize = (table.DATA_LENGTH / 1024).toFixed(2); + const indexSize = (table.INDEX_LENGTH / 1024).toFixed(2); + + console.log(` 📄 ${table.TABLE_NAME.padEnd(25)} | ${String(rowCount).padStart(6)} 行 | ${dataSize.padStart(8)} KB | ${table.TABLE_COMMENT || '无注释'}`); + } + + console.log(`\n📊 总记录数: ${totalRows}`); + + // 检查核心表的详细结构 + console.log('\n🔍 检查核心表结构:'); + const coreTables = ['admins', 'users', 'merchants', 'animals', 'orders', 'payments']; + + for (const tableName of coreTables) { + const tableExists = tables.find(t => t.TABLE_NAME === tableName); + + if (tableExists) { + console.log(`\n📋 表: ${tableName}`); + + // 获取表结构 + const [columns] = await connection.execute(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + ORDER BY ORDINAL_POSITION + `, [dbConfig.database, tableName]); + + console.log(' 字段结构:'); + for (const col of columns.slice(0, 10)) { // 只显示前10个字段 + const nullable = col.IS_NULLABLE === 'YES' ? '可空' : '非空'; + const defaultVal = col.COLUMN_DEFAULT ? `默认:${col.COLUMN_DEFAULT}` : ''; + console.log(` ${col.COLUMN_NAME.padEnd(20)} | ${col.DATA_TYPE.padEnd(15)} | ${nullable.padEnd(4)} | ${col.COLUMN_COMMENT || '无注释'}`); + } + + if (columns.length > 10) { + console.log(` ... 还有 ${columns.length - 10} 个字段`); + } + + // 检查索引 + const [indexes] = await connection.execute(` + SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + ORDER BY INDEX_NAME, SEQ_IN_INDEX + `, [dbConfig.database, tableName]); + + if (indexes.length > 0) { + console.log(' 索引:'); + const indexGroups = {}; + indexes.forEach(idx => { + if (!indexGroups[idx.INDEX_NAME]) { + indexGroups[idx.INDEX_NAME] = []; + } + indexGroups[idx.INDEX_NAME].push(idx.COLUMN_NAME); + }); + + Object.entries(indexGroups).forEach(([indexName, columns]) => { + const type = indexName === 'PRIMARY' ? '主键' : '索引'; + console.log(` ${type}: ${indexName} (${columns.join(', ')})`); + }); + } + + // 检查数据样例 + try { + const [sampleData] = await connection.execute(`SELECT * FROM ${tableName} LIMIT 3`); + if (sampleData.length > 0) { + console.log(` 📊 数据样例: ${sampleData.length} 条记录`); + } else { + console.log(' 📊 数据样例: 表为空'); + } + } catch (error) { + console.log(` ❌ 无法获取数据样例: ${error.message}`); + } + + } else { + console.log(`❌ 核心表不存在: ${tableName}`); + } + } + + // 检查外键约束 + console.log('\n🔗 检查外键约束:'); + const [foreignKeys] = await connection.execute(` + SELECT + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME, + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL + ORDER BY TABLE_NAME + `, [dbConfig.database]); + + if (foreignKeys.length > 0) { + console.log(`📊 找到 ${foreignKeys.length} 个外键约束:`); + foreignKeys.forEach(fk => { + console.log(` ${fk.TABLE_NAME}.${fk.COLUMN_NAME} -> ${fk.REFERENCED_TABLE_NAME}.${fk.REFERENCED_COLUMN_NAME}`); + }); + } else { + console.log('⚠️ 没有找到外键约束'); + } + + // 数据完整性检查 + console.log('\n🔍 数据完整性检查:'); + + // 检查管理员数据 + if (tables.find(t => t.TABLE_NAME === 'admins')) { + const [adminCount] = await connection.execute('SELECT COUNT(*) as count FROM admins'); + console.log(`👨‍💼 管理员数量: ${adminCount[0].count}`); + + if (adminCount[0].count > 0) { + const [admins] = await connection.execute('SELECT username, role, status FROM admins LIMIT 5'); + admins.forEach(admin => { + console.log(` - ${admin.username} (${admin.role}, 状态: ${admin.status})`); + }); + } + } + + // 检查用户数据 + if (tables.find(t => t.TABLE_NAME === 'users')) { + const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users'); + console.log(`👥 用户数量: ${userCount[0].count}`); + + if (userCount[0].count > 0) { + const [userStats] = await connection.execute(` + SELECT user_type, COUNT(*) as count + FROM users + GROUP BY user_type + `); + userStats.forEach(stat => { + console.log(` - ${stat.user_type}: ${stat.count} 人`); + }); + } + } + + console.log('\n🎉 数据库结构检查完成!'); + + return { + success: true, + tableCount: tables.length, + totalRows: totalRows, + tables: tables.map(t => t.TABLE_NAME) + }; + + } catch (error) { + console.error('❌ 数据库结构检查失败:', error.message); + console.error('🔍 错误代码:', error.code); + return { success: false, error: error.message }; + } finally { + if (connection) { + await connection.end(); + console.log('🔒 数据库连接已关闭'); + } + } +} + +// 如果是直接运行此文件,则执行检查 +if (require.main === module) { + checkDatabaseStructure() + .then((result) => { + process.exit(result.success ? 0 : 1); + }) + .catch(() => process.exit(1)); +} + +module.exports = { checkDatabaseStructure }; \ No newline at end of file diff --git a/backend/scripts/create-database-schema.sql b/backend/scripts/create-database-schema.sql new file mode 100644 index 0000000..b6c1abb --- /dev/null +++ b/backend/scripts/create-database-schema.sql @@ -0,0 +1,370 @@ +-- 解班客数据库完整结构创建脚本 +-- 创建时间: 2024年 +-- 数据库: jbkdata + +-- 设置字符集 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- 使用数据库 +USE jbkdata; + +-- ================================ +-- 1. 管理员表 +-- ================================ +CREATE TABLE IF NOT EXISTS `admins` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(50) NOT NULL COMMENT '用户名', + `password` varchar(255) NOT NULL COMMENT '密码', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像', + `role` enum('super_admin','admin','operator') DEFAULT 'admin' COMMENT '角色', + `status` tinyint(1) DEFAULT 1 COMMENT '状态 1:启用 0:禁用', + `last_login` timestamp NULL DEFAULT NULL COMMENT '最后登录时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + KEY `idx_email` (`email`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员表'; + +-- ================================ +-- 2. 用户表 +-- ================================ +CREATE TABLE IF NOT EXISTS `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(50) NOT NULL COMMENT '用户名', + `password_hash` varchar(255) NOT NULL COMMENT '密码哈希', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(20) DEFAULT NULL COMMENT '手机号', + `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', + `avatar_url` varchar(255) DEFAULT NULL COMMENT '头像URL', + `user_type` enum('farmer','tourist','merchant','admin') DEFAULT 'tourist' COMMENT '用户类型', + `status` enum('active','inactive','banned') DEFAULT 'active' COMMENT '状态', + `balance` decimal(15,2) DEFAULT 0.00 COMMENT '余额', + `points` int(11) DEFAULT 0 COMMENT '积分', + `level` tinyint(4) DEFAULT 1 COMMENT '用户等级', + `last_login_at` timestamp NULL DEFAULT NULL COMMENT '最后登录时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + UNIQUE KEY `email` (`email`), + UNIQUE KEY `phone` (`phone`), + KEY `idx_user_type` (`user_type`), + KEY `idx_status` (`status`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; + +-- ================================ +-- 3. 商家表 +-- ================================ +CREATE TABLE IF NOT EXISTS `merchants` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL COMMENT '关联用户ID', + `business_name` varchar(100) NOT NULL COMMENT '商家名称', + `business_type` enum('restaurant','hotel','farm','attraction','transport') NOT NULL COMMENT '商家类型', + `description` text COMMENT '商家描述', + `address` varchar(255) DEFAULT NULL COMMENT '地址', + `latitude` decimal(10,8) DEFAULT NULL COMMENT '纬度', + `longitude` decimal(11,8) DEFAULT NULL COMMENT '经度', + `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', + `business_hours` json DEFAULT NULL COMMENT '营业时间', + `images` json DEFAULT NULL COMMENT '商家图片', + `rating` decimal(3,2) DEFAULT 0.00 COMMENT '评分', + `review_count` int(11) DEFAULT 0 COMMENT '评价数量', + `status` enum('pending','approved','rejected','suspended') DEFAULT 'pending' COMMENT '状态', + `verified_at` timestamp NULL DEFAULT NULL COMMENT '认证时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + KEY `idx_business_type` (`business_type`), + KEY `idx_status` (`status`), + KEY `idx_location` (`latitude`,`longitude`), + CONSTRAINT `merchants_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商家表'; + +-- ================================ +-- 4. 动物表 +-- ================================ +CREATE TABLE IF NOT EXISTS `animals` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL COMMENT '动物名称', + `type` enum('chicken','duck','pig','cow','sheep','rabbit','fish') NOT NULL COMMENT '动物类型', + `breed` varchar(50) DEFAULT NULL COMMENT '品种', + `age` int(11) DEFAULT NULL COMMENT '年龄(月)', + `weight` decimal(8,2) DEFAULT NULL COMMENT '重量(kg)', + `gender` enum('male','female','unknown') DEFAULT 'unknown' COMMENT '性别', + `description` text COMMENT '描述', + `image` varchar(255) DEFAULT NULL COMMENT '图片URL', + `images` json DEFAULT NULL COMMENT '多张图片', + `price` decimal(10,2) NOT NULL COMMENT '认领价格', + `daily_cost` decimal(8,2) DEFAULT 0.00 COMMENT '每日费用', + `location` varchar(100) DEFAULT NULL COMMENT '所在位置', + `farmer_id` int(11) DEFAULT NULL COMMENT '农户ID', + `status` enum('available','claimed','sold','deceased') DEFAULT 'available' COMMENT '状态', + `health_status` enum('healthy','sick','recovering') DEFAULT 'healthy' COMMENT '健康状态', + `vaccination_records` json DEFAULT NULL COMMENT '疫苗记录', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_type` (`type`), + KEY `idx_status` (`status`), + KEY `idx_farmer_id` (`farmer_id`), + KEY `idx_price` (`price`), + CONSTRAINT `animals_ibfk_1` FOREIGN KEY (`farmer_id`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='动物表'; + +-- ================================ +-- 5. 动物认领表 +-- ================================ +CREATE TABLE IF NOT EXISTS `animal_claims` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `claim_no` varchar(50) NOT NULL COMMENT '认领订单号', + `animal_id` int(11) NOT NULL COMMENT '动物ID', + `user_id` int(11) NOT NULL COMMENT '认领用户ID', + `claim_reason` text COMMENT '认领原因', + `claim_duration` int(11) NOT NULL COMMENT '认领时长(天)', + `total_amount` decimal(10,2) NOT NULL COMMENT '总费用', + `contact_info` json DEFAULT NULL COMMENT '联系信息', + `status` enum('pending','approved','rejected','cancelled','completed') DEFAULT 'pending' COMMENT '状态', + `reviewed_by` int(11) DEFAULT NULL COMMENT '审核人ID', + `reviewed_at` timestamp NULL DEFAULT NULL COMMENT '审核时间', + `review_note` text COMMENT '审核备注', + `start_date` date DEFAULT NULL COMMENT '开始日期', + `end_date` date DEFAULT NULL COMMENT '结束日期', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + UNIQUE KEY `claim_no` (`claim_no`), + KEY `animal_id` (`animal_id`), + KEY `user_id` (`user_id`), + KEY `idx_status` (`status`), + KEY `idx_created_at` (`created_at`), + CONSTRAINT `animal_claims_ibfk_1` FOREIGN KEY (`animal_id`) REFERENCES `animals` (`id`) ON DELETE CASCADE, + CONSTRAINT `animal_claims_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='动物认领表'; + +-- ================================ +-- 6. 旅行计划表 +-- ================================ +CREATE TABLE IF NOT EXISTS `travel_plans` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(100) NOT NULL COMMENT '标题', + `description` text COMMENT '描述', + `destination` varchar(100) NOT NULL COMMENT '目的地', + `start_date` date NOT NULL COMMENT '开始日期', + `end_date` date NOT NULL COMMENT '结束日期', + `max_participants` int(11) DEFAULT 20 COMMENT '最大参与人数', + `current_participants` int(11) DEFAULT 0 COMMENT '当前参与人数', + `price_per_person` decimal(10,2) NOT NULL COMMENT '每人价格', + `includes` json DEFAULT NULL COMMENT '包含项目', + `excludes` json DEFAULT NULL COMMENT '不包含项目', + `itinerary` json DEFAULT NULL COMMENT '行程安排', + `images` json DEFAULT NULL COMMENT '图片', + `requirements` text COMMENT '参与要求', + `created_by` int(11) NOT NULL COMMENT '创建者ID', + `status` enum('draft','published','cancelled','completed') DEFAULT 'draft' COMMENT '状态', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `created_by` (`created_by`), + KEY `idx_status` (`status`), + KEY `idx_start_date` (`start_date`), + KEY `idx_destination` (`destination`), + CONSTRAINT `travel_plans_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='旅行计划表'; + +-- ================================ +-- 7. 旅行报名表 +-- ================================ +CREATE TABLE IF NOT EXISTS `travel_registrations` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `travel_plan_id` int(11) NOT NULL COMMENT '旅行计划ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `participants` int(11) DEFAULT 1 COMMENT '参与人数', + `message` text COMMENT '留言', + `emergency_contact` varchar(50) DEFAULT NULL COMMENT '紧急联系人', + `emergency_phone` varchar(20) DEFAULT NULL COMMENT '紧急联系电话', + `status` enum('pending','approved','rejected','cancelled') DEFAULT 'pending' COMMENT '状态', + `reject_reason` text COMMENT '拒绝原因', + `applied_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', + `responded_at` timestamp NULL DEFAULT NULL COMMENT '响应时间', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_registration` (`travel_plan_id`,`user_id`), + KEY `user_id` (`user_id`), + KEY `idx_status` (`status`), + CONSTRAINT `travel_registrations_ibfk_1` FOREIGN KEY (`travel_plan_id`) REFERENCES `travel_plans` (`id`) ON DELETE CASCADE, + CONSTRAINT `travel_registrations_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='旅行报名表'; + +-- ================================ +-- 8. 鲜花表 +-- ================================ +CREATE TABLE IF NOT EXISTS `flowers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL COMMENT '花卉名称', + `scientific_name` varchar(100) DEFAULT NULL COMMENT '学名', + `category` enum('rose','lily','tulip','sunflower','orchid','carnation','other') NOT NULL COMMENT '花卉类别', + `color` varchar(30) DEFAULT NULL COMMENT '颜色', + `description` text COMMENT '描述', + `care_instructions` text COMMENT '养护说明', + `image` varchar(255) DEFAULT NULL COMMENT '主图片', + `images` json DEFAULT NULL COMMENT '多张图片', + `price` decimal(8,2) NOT NULL COMMENT '价格', + `stock_quantity` int(11) DEFAULT 0 COMMENT '库存数量', + `farmer_id` int(11) DEFAULT NULL COMMENT '农户ID', + `status` enum('available','out_of_stock','discontinued') DEFAULT 'available' COMMENT '状态', + `seasonal_availability` json DEFAULT NULL COMMENT '季节性供应', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_category` (`category`), + KEY `idx_status` (`status`), + KEY `idx_farmer_id` (`farmer_id`), + KEY `idx_price` (`price`), + CONSTRAINT `flowers_ibfk_1` FOREIGN KEY (`farmer_id`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鲜花表'; + +-- ================================ +-- 9. 订单表 +-- ================================ +CREATE TABLE IF NOT EXISTS `orders` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `order_no` varchar(50) NOT NULL COMMENT '订单号', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `type` enum('animal_claim','travel','flower','service') NOT NULL COMMENT '订单类型', + `related_id` int(11) DEFAULT NULL COMMENT '关联ID', + `title` varchar(200) NOT NULL COMMENT '订单标题', + `description` text COMMENT '订单描述', + `total_amount` decimal(15,2) NOT NULL COMMENT '总金额', + `discount_amount` decimal(15,2) DEFAULT 0.00 COMMENT '优惠金额', + `final_amount` decimal(15,2) NOT NULL COMMENT '实付金额', + `status` enum('pending','paid','processing','shipped','completed','cancelled','refunded') DEFAULT 'pending' COMMENT '订单状态', + `payment_status` enum('unpaid','paid','refunded','partial_refund') DEFAULT 'unpaid' COMMENT '支付状态', + `payment_method` varchar(50) DEFAULT NULL COMMENT '支付方式', + `payment_time` timestamp NULL DEFAULT NULL COMMENT '支付时间', + `shipping_address` json DEFAULT NULL COMMENT '收货地址', + `contact_info` json DEFAULT NULL COMMENT '联系信息', + `notes` text COMMENT '备注', + `ordered_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `order_no` (`order_no`), + KEY `user_id` (`user_id`), + KEY `idx_type` (`type`), + KEY `idx_status` (`status`), + KEY `idx_payment_status` (`payment_status`), + KEY `idx_ordered_at` (`ordered_at`), + CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表'; + +-- ================================ +-- 10. 支付表 +-- ================================ +CREATE TABLE IF NOT EXISTS `payments` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `payment_no` varchar(50) NOT NULL COMMENT '支付订单号', + `order_id` int(11) NOT NULL COMMENT '订单ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `amount` decimal(15,2) NOT NULL COMMENT '支付金额', + `payment_method` enum('wechat','alipay','bank_card','balance') NOT NULL COMMENT '支付方式', + `status` enum('pending','paid','failed','cancelled','refunded') DEFAULT 'pending' COMMENT '支付状态', + `transaction_id` varchar(100) DEFAULT NULL COMMENT '第三方交易号', + `paid_amount` decimal(15,2) DEFAULT NULL COMMENT '实际支付金额', + `paid_at` timestamp NULL DEFAULT NULL COMMENT '支付时间', + `failure_reason` varchar(255) DEFAULT NULL COMMENT '失败原因', + `return_url` varchar(255) DEFAULT NULL COMMENT '返回URL', + `notify_url` varchar(255) DEFAULT NULL COMMENT '通知URL', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + UNIQUE KEY `payment_no` (`payment_no`), + KEY `order_id` (`order_id`), + KEY `user_id` (`user_id`), + KEY `idx_status` (`status`), + KEY `idx_payment_method` (`payment_method`), + KEY `idx_created_at` (`created_at`), + CONSTRAINT `payments_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE, + CONSTRAINT `payments_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付表'; + +-- ================================ +-- 11. 退款表 +-- ================================ +CREATE TABLE IF NOT EXISTS `refunds` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `refund_no` varchar(50) NOT NULL COMMENT '退款订单号', + `payment_id` int(11) NOT NULL COMMENT '支付ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `refund_amount` decimal(15,2) NOT NULL COMMENT '退款金额', + `refund_reason` varchar(255) NOT NULL COMMENT '退款原因', + `status` enum('pending','processing','completed','rejected') DEFAULT 'pending' COMMENT '退款状态', + `processed_by` int(11) DEFAULT NULL COMMENT '处理人ID', + `processed_at` timestamp NULL DEFAULT NULL COMMENT '处理时间', + `process_remark` text COMMENT '处理备注', + `refund_transaction_id` varchar(100) DEFAULT NULL COMMENT '退款交易号', + `refunded_at` timestamp NULL DEFAULT NULL COMMENT '退款完成时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + UNIQUE KEY `refund_no` (`refund_no`), + KEY `payment_id` (`payment_id`), + KEY `user_id` (`user_id`), + KEY `idx_status` (`status`), + CONSTRAINT `refunds_ibfk_1` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE CASCADE, + CONSTRAINT `refunds_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='退款表'; + +-- ================================ +-- 12. 辅助表 +-- ================================ + +-- 邮箱验证表 +CREATE TABLE IF NOT EXISTS `email_verifications` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(100) NOT NULL COMMENT '邮箱', + `code` varchar(10) NOT NULL COMMENT '验证码', + `expires_at` timestamp NOT NULL COMMENT '过期时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`), + KEY `idx_expires_at` (`expires_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邮箱验证表'; + +-- 密码重置表 +CREATE TABLE IF NOT EXISTS `password_resets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL COMMENT '用户ID', + `token` varchar(255) NOT NULL COMMENT '重置令牌', + `expires_at` timestamp NOT NULL COMMENT '过期时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `user_id` (`user_id`), + UNIQUE KEY `token` (`token`), + KEY `idx_expires_at` (`expires_at`), + CONSTRAINT `password_resets_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='密码重置表'; + +-- 登录尝试表 +CREATE TABLE IF NOT EXISTS `login_attempts` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `identifier` varchar(100) NOT NULL COMMENT '标识符(用户名/邮箱/IP)', + `attempts` int(11) DEFAULT 1 COMMENT '尝试次数', + `last_attempt` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '最后尝试时间', + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `identifier` (`identifier`), + KEY `idx_last_attempt` (`last_attempt`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录尝试表'; + +SET FOREIGN_KEY_CHECKS = 1; + +-- 创建完成提示 +SELECT '数据库表结构创建完成!' as message; \ No newline at end of file diff --git a/backend/scripts/init-test-data-complete.js b/backend/scripts/init-test-data-complete.js new file mode 100644 index 0000000..6088669 --- /dev/null +++ b/backend/scripts/init-test-data-complete.js @@ -0,0 +1,219 @@ +#!/usr/bin/env node + +/** + * 完整测试数据初始化脚本 + * 用于开发环境创建完整的测试数据 + */ + +const mysql = require('mysql2/promise'); +const bcrypt = require('bcryptjs'); +const config = require('../config/env'); + +async function initCompleteTestData() { + let connection; + + try { + console.log('🚀 开始初始化完整测试数据...'); + + // 创建数据库连接 + const dbConfig = { + host: config.mysql.host, + port: config.mysql.port, + user: config.mysql.user, + password: config.mysql.password, + database: config.mysql.database, + charset: config.mysql.charset, + timezone: config.mysql.timezone + }; + + connection = await mysql.createConnection(dbConfig); + console.log('✅ 数据库连接成功'); + + // 清理现有测试数据(可选) + console.log('🧹 清理现有测试数据...'); + const tablesToClean = [ + 'refunds', 'payments', 'orders', 'travel_registrations', 'travel_plans', + 'animal_claims', 'animals', 'flowers', 'merchants', 'users', 'admins', + 'email_verifications', 'password_resets', 'login_attempts' + ]; + + for (const table of tablesToClean) { + try { + await connection.execute(`DELETE FROM ${table} WHERE id > 0`); + await connection.execute(`ALTER TABLE ${table} AUTO_INCREMENT = 1`); + console.log(` 清理表: ${table}`); + } catch (error) { + console.log(` 跳过表: ${table} (${error.message})`); + } + } + + // 1. 插入管理员数据 + console.log('👨‍💼 插入管理员数据...'); + const adminPassword = await bcrypt.hash('admin123', 10); + const managerPassword = await bcrypt.hash('manager123', 10); + + await connection.execute(` + INSERT INTO admins (username, password, email, nickname, role, status) VALUES + ('admin', ?, 'admin@jiebanke.com', '超级管理员', 'super_admin', 1), + ('manager', ?, 'manager@jiebanke.com', '运营经理', 'admin', 1), + ('operator', ?, 'operator@jiebanke.com', '运营专员', 'operator', 1) + `, [adminPassword, managerPassword, await bcrypt.hash('operator123', 10)]); + console.log(' ✅ 管理员数据插入完成'); + + // 2. 插入用户数据 + console.log('👥 插入用户数据...'); + const userPassword = await bcrypt.hash('user123', 10); + const farmerPassword = await bcrypt.hash('farmer123', 10); + const merchantPassword = await bcrypt.hash('merchant123', 10); + + await connection.execute(` + INSERT INTO users (username, password_hash, email, phone, real_name, user_type, status, balance, points) VALUES + ('tourist1', ?, 'tourist1@jiebanke.com', '13800138001', '张三', 'tourist', 'active', 1000.00, 100), + ('tourist2', ?, 'tourist2@jiebanke.com', '13800138002', '李四', 'tourist', 'active', 500.00, 50), + ('farmer1', ?, 'farmer1@jiebanke.com', '13800138003', '王农夫', 'farmer', 'active', 2000.00, 200), + ('farmer2', ?, 'farmer2@jiebanke.com', '13800138004', '赵农民', 'farmer', 'active', 1500.00, 150), + ('merchant1', ?, 'merchant1@jiebanke.com', '13800138005', '刘老板', 'merchant', 'active', 5000.00, 500), + ('merchant2', ?, 'merchant2@jiebanke.com', '13800138006', '陈经理', 'merchant', 'active', 3000.00, 300) + `, [userPassword, userPassword, farmerPassword, farmerPassword, merchantPassword, merchantPassword]); + console.log(' ✅ 用户数据插入完成'); + + // 3. 插入商家数据 + console.log('🏪 插入商家数据...'); + await connection.execute(` + INSERT INTO merchants (user_id, business_name, business_type, description, address, contact_phone, status) VALUES + (5, '山水农家乐', 'restaurant', '提供正宗农家菜和住宿服务', '北京市密云区某某村', '13800138005', 'approved'), + (6, '绿野仙踪度假村', 'hotel', '生态度假村,环境优美', '河北省承德市某某镇', '13800138006', 'approved') + `); + console.log(' ✅ 商家数据插入完成'); + + // 4. 插入动物数据 + console.log('🐷 插入动物数据...'); + await connection.execute(` + INSERT INTO animals (name, type, breed, age, weight, gender, description, price, daily_cost, farmer_id, status) VALUES + ('小花', 'pig', '土猪', 6, 50.5, 'female', '健康活泼的小母猪', 800.00, 5.00, 3, 'available'), + ('大黄', 'chicken', '土鸡', 8, 2.5, 'male', '散养公鸡,肉质鲜美', 120.00, 2.00, 3, 'available'), + ('小白', 'sheep', '绵羊', 12, 35.0, 'female', '温顺的小绵羊', 600.00, 4.00, 4, 'available'), + ('老黑', 'cow', '黄牛', 24, 300.0, 'male', '强壮的耕牛', 2000.00, 10.00, 4, 'available'), + ('小灰', 'rabbit', '肉兔', 3, 1.8, 'female', '可爱的小兔子', 80.00, 1.50, 3, 'available') + `); + console.log(' ✅ 动物数据插入完成'); + + // 5. 插入鲜花数据 + console.log('🌸 插入鲜花数据...'); + await connection.execute(` + INSERT INTO flowers (name, category, color, description, price, stock_quantity, farmer_id, status) VALUES + ('红玫瑰', 'rose', '红色', '经典红玫瑰,象征爱情', 15.00, 100, 3, 'available'), + ('白百合', 'lily', '白色', '纯洁的白百合,适合送礼', 25.00, 50, 3, 'available'), + ('黄郁金香', 'tulip', '黄色', '明亮的黄郁金香', 20.00, 80, 4, 'available'), + ('向日葵', 'sunflower', '黄色', '阳光般的向日葵', 12.00, 120, 4, 'available'), + ('粉康乃馨', 'carnation', '粉色', '温馨的粉色康乃馨', 18.00, 90, 3, 'available') + `); + console.log(' ✅ 鲜花数据插入完成'); + + // 6. 插入旅行计划数据 + console.log('✈️ 插入旅行计划数据...'); + await connection.execute(` + INSERT INTO travel_plans (title, description, destination, start_date, end_date, max_participants, price_per_person, created_by, status) VALUES + ('密云水库生态游', '体验密云水库的自然风光,品尝农家美食', '北京密云', '2024-05-01', '2024-05-03', 20, 299.00, 5, 'published'), + ('承德避暑山庄文化游', '探访承德避暑山庄,了解清朝历史文化', '河北承德', '2024-06-15', '2024-06-17', 15, 599.00, 6, 'published'), + ('农场体验亲子游', '带孩子体验农场生活,学习农业知识', '北京郊区', '2024-07-01', '2024-07-02', 25, 199.00, 3, 'published') + `); + console.log(' ✅ 旅行计划数据插入完成'); + + // 7. 插入动物认领数据 + console.log('🐾 插入动物认领数据...'); + await connection.execute(` + INSERT INTO animal_claims (claim_no, animal_id, user_id, claim_reason, claim_duration, total_amount, status, start_date, end_date) VALUES + ('AC202401001', 1, 1, '想体验养猪的乐趣', 30, 950.00, 'approved', '2024-04-01', '2024-04-30'), + ('AC202401002', 2, 2, '孩子喜欢小鸡', 15, 150.00, 'pending', '2024-04-15', '2024-04-29'), + ('AC202401003', 3, 1, '认领小羊作为宠物', 60, 840.00, 'approved', '2024-03-01', '2024-04-29') + `); + console.log(' ✅ 动物认领数据插入完成'); + + // 8. 插入旅行报名数据 + console.log('📝 插入旅行报名数据...'); + await connection.execute(` + INSERT INTO travel_registrations (travel_plan_id, user_id, participants, message, emergency_contact, emergency_phone, status) VALUES + (1, 1, 2, '期待这次旅行', '张三妻子', '13900139001', 'approved'), + (1, 2, 1, '第一次参加农家游', '李四父亲', '13900139002', 'pending'), + (2, 1, 3, '全家一起出游', '张三妻子', '13900139001', 'approved'), + (3, 2, 2, '带孩子体验农场', '李四妻子', '13900139003', 'pending') + `); + console.log(' ✅ 旅行报名数据插入完成'); + + // 9. 插入订单数据 + console.log('📦 插入订单数据...'); + await connection.execute(` + INSERT INTO orders (order_no, user_id, type, related_id, title, total_amount, final_amount, status, payment_status) VALUES + ('ORD202401001', 1, 'animal_claim', 1, '认领小花猪30天', 950.00, 950.00, 'completed', 'paid'), + ('ORD202401002', 2, 'animal_claim', 2, '认领大黄鸡15天', 150.00, 150.00, 'pending', 'unpaid'), + ('ORD202401003', 1, 'travel', 1, '密云水库生态游 2人', 598.00, 598.00, 'completed', 'paid'), + ('ORD202401004', 1, 'flower', 1, '红玫瑰 10支', 150.00, 150.00, 'completed', 'paid') + `); + console.log(' ✅ 订单数据插入完成'); + + // 10. 插入支付数据 + console.log('💳 插入支付数据...'); + await connection.execute(` + INSERT INTO payments (payment_no, order_id, user_id, amount, payment_method, status, paid_amount, paid_at) VALUES + ('PAY202401001', 1, 1, 950.00, 'wechat', 'paid', 950.00, '2024-04-01 10:30:00'), + ('PAY202401002', 3, 1, 598.00, 'alipay', 'paid', 598.00, '2024-04-02 14:20:00'), + ('PAY202401003', 4, 1, 150.00, 'wechat', 'paid', 150.00, '2024-04-03 16:45:00') + `); + console.log(' ✅ 支付数据插入完成'); + + // 统计插入的数据 + console.log('\n📊 数据统计:'); + const tables = ['admins', 'users', 'merchants', 'animals', 'flowers', 'travel_plans', 'animal_claims', 'travel_registrations', 'orders', 'payments']; + + for (const table of tables) { + try { + const [rows] = await connection.execute(`SELECT COUNT(*) as count FROM ${table}`); + console.log(` ${table}: ${rows[0].count} 条记录`); + } catch (error) { + console.log(` ${table}: 表不存在或查询失败`); + } + } + + console.log('\n🎉 完整测试数据初始化完成!'); + console.log('📋 测试账号信息:'); + console.log(' 管理员账号:'); + console.log(' admin / admin123 (超级管理员)'); + console.log(' manager / manager123 (运营经理)'); + console.log(' operator / operator123 (运营专员)'); + console.log(' 用户账号:'); + console.log(' tourist1 / user123 (游客)'); + console.log(' tourist2 / user123 (游客)'); + console.log(' farmer1 / farmer123 (农户)'); + console.log(' farmer2 / farmer123 (农户)'); + console.log(' merchant1 / merchant123 (商家)'); + console.log(' merchant2 / merchant123 (商家)'); + + } catch (error) { + console.error('❌ 初始化测试数据失败:', error.message); + if (error.code === 'ER_NO_SUCH_TABLE') { + console.log('💡 提示: 请先运行数据库结构创建脚本'); + } + throw error; + } finally { + if (connection) { + await connection.end(); + console.log('🔒 数据库连接已关闭'); + } + } +} + +// 如果是直接运行此文件,则执行初始化 +if (require.main === module) { + initCompleteTestData() + .then(() => { + console.log('✅ 脚本执行成功'); + process.exit(0); + }) + .catch((error) => { + console.error('❌ 脚本执行失败:', error.message); + process.exit(1); + }); +} + +module.exports = { initCompleteTestData }; \ No newline at end of file diff --git a/backend/scripts/test-backend-connection.js b/backend/scripts/test-backend-connection.js new file mode 100644 index 0000000..caa7518 --- /dev/null +++ b/backend/scripts/test-backend-connection.js @@ -0,0 +1,124 @@ +#!/usr/bin/env node + +/** + * 后端数据库连接测试脚本 + * 测试backend中的数据库连接配置是否正常工作 + */ + +const { testConnection, query } = require('../src/config/database'); + +async function testBackendConnection() { + console.log('🚀 测试后端数据库连接配置...'); + console.log('='.repeat(50)); + + try { + // 测试数据库连接 + console.log('🔍 测试数据库连接...'); + const isConnected = await testConnection(); + + if (!isConnected) { + console.error('❌ 数据库连接失败'); + return false; + } + + // 测试查询功能 + console.log('🔍 测试查询功能...'); + const testResult = await query('SELECT 1 + 1 as result, NOW() as server_time'); + console.log(`✅ 查询测试成功: ${testResult[0].result}, 时间: ${testResult[0].server_time}`); + + // 测试管理员数据查询 + console.log('🔍 测试管理员数据查询...'); + const admins = await query('SELECT id, username, role, status FROM admins LIMIT 3'); + console.log(`📊 管理员数据: ${admins.length} 条记录`); + admins.forEach(admin => { + console.log(` - ID:${admin.id} ${admin.username} (${admin.role}, 状态:${admin.status})`); + }); + + // 测试用户数据查询 + console.log('🔍 测试用户数据查询...'); + const users = await query('SELECT id, username, user_type, status, balance FROM users LIMIT 5'); + console.log(`📊 用户数据: ${users.length} 条记录`); + users.forEach(user => { + console.log(` - ID:${user.id} ${user.username} (${user.user_type}, 余额:${user.balance})`); + }); + + // 测试动物数据查询 + console.log('🔍 测试动物数据查询...'); + const animals = await query('SELECT id, name, type, price, status FROM animals LIMIT 5'); + console.log(`📊 动物数据: ${animals.length} 条记录`); + animals.forEach(animal => { + console.log(` - ID:${animal.id} ${animal.name} (${animal.type}, 价格:${animal.price})`); + }); + + // 测试订单数据查询 + console.log('🔍 测试订单数据查询...'); + const orders = await query(` + SELECT o.id, o.order_no, o.type, o.final_amount, o.status, u.username + FROM orders o + LEFT JOIN users u ON o.user_id = u.id + LIMIT 5 + `); + console.log(`📊 订单数据: ${orders.length} 条记录`); + orders.forEach(order => { + console.log(` - ${order.order_no} (${order.type}, ¥${order.final_amount}, ${order.status}) - ${order.username}`); + }); + + // 测试支付数据查询 + console.log('🔍 测试支付数据查询...'); + const payments = await query(` + SELECT p.id, p.payment_no, p.amount, p.payment_method, p.status, u.username + FROM payments p + LEFT JOIN users u ON p.user_id = u.id + LIMIT 5 + `); + console.log(`📊 支付数据: ${payments.length} 条记录`); + payments.forEach(payment => { + console.log(` - ${payment.payment_no} (¥${payment.amount}, ${payment.payment_method}, ${payment.status}) - ${payment.username}`); + }); + + // 统计数据 + console.log('\n📊 数据库统计:'); + const stats = await query(` + SELECT + (SELECT COUNT(*) FROM admins) as admin_count, + (SELECT COUNT(*) FROM users) as user_count, + (SELECT COUNT(*) FROM merchants) as merchant_count, + (SELECT COUNT(*) FROM animals) as animal_count, + (SELECT COUNT(*) FROM flowers) as flower_count, + (SELECT COUNT(*) FROM travel_plans) as travel_plan_count, + (SELECT COUNT(*) FROM orders) as order_count, + (SELECT COUNT(*) FROM payments) as payment_count + `); + + const stat = stats[0]; + console.log(` 管理员: ${stat.admin_count} 个`); + console.log(` 用户: ${stat.user_count} 个`); + console.log(` 商家: ${stat.merchant_count} 个`); + console.log(` 动物: ${stat.animal_count} 个`); + console.log(` 鲜花: ${stat.flower_count} 个`); + console.log(` 旅行计划: ${stat.travel_plan_count} 个`); + console.log(` 订单: ${stat.order_count} 个`); + console.log(` 支付记录: ${stat.payment_count} 个`); + + console.log('\n🎉 后端数据库连接测试完成!'); + console.log('✅ 所有功能正常'); + + return true; + + } catch (error) { + console.error('❌ 后端数据库连接测试失败:', error.message); + console.error('🔍 错误详情:', error); + return false; + } +} + +// 如果是直接运行此文件,则执行测试 +if (require.main === module) { + testBackendConnection() + .then((success) => { + process.exit(success ? 0 : 1); + }) + .catch(() => process.exit(1)); +} + +module.exports = { testBackendConnection }; \ No newline at end of file diff --git a/backend/scripts/test-database-connection-fixed.js b/backend/scripts/test-database-connection-fixed.js new file mode 100644 index 0000000..691326d --- /dev/null +++ b/backend/scripts/test-database-connection-fixed.js @@ -0,0 +1,204 @@ +#!/usr/bin/env node + +/** + * 数据库连接测试脚本 - 修复版 + * 用于验证MySQL数据库连接配置的正确性 + */ + +const mysql = require('mysql2/promise'); +const config = require('../config/env'); + +async function testDatabaseConnection() { + let connection; + + try { + console.log('🚀 开始数据库连接测试...'); + console.log(`📊 环境: ${process.env.NODE_ENV || 'development'}`); + + // 使用env.js中的mysql配置 + const dbConfig = { + host: config.mysql.host, + port: config.mysql.port, + user: config.mysql.user, + password: config.mysql.password, + database: config.mysql.database, + charset: config.mysql.charset, + timezone: config.mysql.timezone + }; + + console.log(`🔗 连接信息: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`); + console.log('='.repeat(50)); + + // 测试连接 + console.log('🔍 测试数据库连接...'); + connection = await mysql.createConnection(dbConfig); + console.log('✅ 数据库连接成功'); + + // 测试查询 + console.log('🔍 测试基本查询...'); + const [rows] = await connection.execute('SELECT 1 + 1 AS result, NOW() as current_time'); + console.log(`✅ 查询测试成功: ${rows[0].result}, 服务器时间: ${rows[0].current_time}`); + + // 检查数据库版本 + console.log('🔍 检查数据库版本...'); + const [versionRows] = await connection.execute('SELECT VERSION() as version'); + console.log(`📊 MySQL版本: ${versionRows[0].version}`); + + // 检查数据库字符集 + console.log('🔍 检查数据库字符集...'); + const [charsetRows] = await connection.execute( + 'SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?', + [dbConfig.database] + ); + if (charsetRows.length > 0) { + console.log(`📝 数据库字符集: ${charsetRows[0].DEFAULT_CHARACTER_SET_NAME}`); + console.log(`📝 数据库排序规则: ${charsetRows[0].DEFAULT_COLLATION_NAME}`); + } + + // 检查表结构 + console.log('🔍 检查核心表结构...'); + const tablesToCheck = [ + 'admins', 'users', 'merchants', 'orders', 'payments', + 'animals', 'animal_claims', 'travel_plans', 'travel_registrations', + 'flowers', 'flower_orders' + ]; + + const existingTables = []; + const missingTables = []; + + for (const table of tablesToCheck) { + try { + const [tableInfo] = await connection.execute( + `SELECT COUNT(*) as count FROM information_schema.tables + WHERE table_schema = ? AND table_name = ?`, + [dbConfig.database, table] + ); + + if (tableInfo[0].count > 0) { + console.log(`✅ 表存在: ${table}`); + existingTables.push(table); + + // 检查表记录数 + const [countRows] = await connection.execute(`SELECT COUNT(*) as count FROM ${table}`); + console.log(` 📊 记录数: ${countRows[0].count}`); + } else { + console.log(`⚠️ 表不存在: ${table}`); + missingTables.push(table); + } + } catch (error) { + console.log(`❌ 检查表失败: ${table} - ${error.message}`); + missingTables.push(table); + } + } + + // 检查管理员表数据 + if (existingTables.includes('admins')) { + console.log('🔍 检查管理员数据...'); + try { + const [adminCount] = await connection.execute('SELECT COUNT(*) as count FROM admins'); + console.log(`📊 管理员记录数: ${adminCount[0].count}`); + + if (adminCount[0].count > 0) { + const [admins] = await connection.execute('SELECT username, role, status FROM admins LIMIT 5'); + console.log('👥 管理员样例:'); + admins.forEach(admin => { + console.log(` - ${admin.username} (${admin.role}, 状态: ${admin.status})`); + }); + } + } catch (error) { + console.log('❌ 检查管理员数据失败:', error.message); + } + } + + // 检查用户表数据 + if (existingTables.includes('users')) { + console.log('🔍 检查用户数据...'); + try { + const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users'); + console.log(`📊 用户记录数: ${userCount[0].count}`); + + if (userCount[0].count > 0) { + const [users] = await connection.execute('SELECT username, user_type, status FROM users LIMIT 5'); + console.log('👤 用户样例:'); + users.forEach(user => { + console.log(` - ${user.username} (${user.user_type || '未知'}, 状态: ${user.status})`); + }); + } + } catch (error) { + console.log('❌ 检查用户数据失败:', error.message); + } + } + + // 检查连接池配置 + console.log('🔍 检查连接配置...'); + console.log(`📈 连接池限制: ${config.mysql.connectionLimit || 10}`); + console.log(`🔤 字符集: ${config.mysql.charset}`); + console.log(`⏰ 时区: ${config.mysql.timezone}`); + + console.log('\n📋 数据库状态总结:'); + console.log(`✅ 存在的表: ${existingTables.length}/${tablesToCheck.length}`); + if (missingTables.length > 0) { + console.log(`⚠️ 缺失的表: ${missingTables.join(', ')}`); + console.log('💡 建议运行数据库迁移脚本创建缺失的表'); + } + + console.log('\n🎉 数据库连接测试完成!'); + console.log('✅ 数据库连接正常'); + + return { + success: true, + existingTables, + missingTables, + dbConfig: { + host: dbConfig.host, + port: dbConfig.port, + database: dbConfig.database, + user: dbConfig.user + } + }; + + } catch (error) { + console.error('❌ 数据库连接测试失败:', error.message); + console.error('💡 可能的原因:'); + console.error(' - 数据库服务未启动'); + console.error(' - 连接配置错误'); + console.error(' - 网络连接问题'); + console.error(' - 数据库权限不足'); + console.error(' - 防火墙限制'); + console.error(' - IP地址未授权'); + + if (error.code) { + console.error(`🔍 错误代码: ${error.code}`); + } + + console.error('🔍 连接详情:', { + host: config.mysql.host, + port: config.mysql.port, + user: config.mysql.user, + database: config.mysql.database + }); + + return { + success: false, + error: error.message, + code: error.code + }; + + } finally { + if (connection) { + await connection.end(); + console.log('🔒 数据库连接已关闭'); + } + } +} + +// 如果是直接运行此文件,则执行测试 +if (require.main === module) { + testDatabaseConnection() + .then((result) => { + process.exit(result.success ? 0 : 1); + }) + .catch(() => process.exit(1)); +} + +module.exports = { testDatabaseConnection }; \ No newline at end of file diff --git a/docs/API接口文档.md b/docs/API接口文档.md deleted file mode 100644 index eb0a34c..0000000 --- a/docs/API接口文档.md +++ /dev/null @@ -1,466 +0,0 @@ -# 📚 结伴客API接口文档 - -## 📋 文档说明 - -本文档详细描述了结伴客项目的所有API接口,包括请求参数、响应格式和错误代码。结伴客是一个专注于结伴旅行和动物认领的社交平台。 - -**基础信息:** -- 基础URL:`https://api.jiebanke.com` -- API版本:`v1` -- 数据格式:`JSON` -- 字符编码:`UTF-8` - -## 🔐 认证方式 - -### JWT Token 认证 -所有需要认证的API必须在请求头中携带Token: -```http -Authorization: Bearer -``` - -### Token 获取 -通过微信登录接口获取Token,Token有效期为7天。 - -## 📊 通用响应格式 - -### 成功响应 -```json -{ - "code": 200, - "message": "操作成功", - "data": { - // 具体数据内容 - } -} -``` - -### 错误响应 -```json -{ - "code": 400, - "message": "错误描述", - "error": "详细错误信息" -} -``` - -### 状态码说明 -- `200`: 请求成功 -- `400`: 请求参数错误 -- `401`: 未授权访问 -- `403`: 权限不足 -- `404`: 资源不存在 -- `500`: 服务器内部错误 - -## 👥 用户管理模块 - -### 微信用户登录 - -**接口地址:** `POST /api/v1/auth/wechat-login` - -**接口描述:** 用户通过微信授权登录系统 - -**请求参数:** -```json -{ - "code": "string, required, 微信登录授权码", - "userInfo": { - "nickName": "string, required, 用户昵称", - "avatarUrl": "string, required, 用户头像URL", - "gender": "number, optional, 性别(0:未知,1:男,2:女)", - "province": "string, optional, 省份", - "city": "string, optional, 城市" - } -} -``` - -**响应示例:** -```json -{ - "code": 200, - "message": "登录成功", - "data": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "user": { - "id": 1, - "openid": "wx1234567890", - "nickname": "旅行达人", - "avatar": "https://avatar.url", - "gender": "male", - "phone": "13800138000", - "isNewUser": false - } - } -} -``` - -### 获取用户信息 - -**接口地址:** `GET /api/v1/users/profile` - -**接口描述:** 获取当前登录用户的详细信息 - -**请求头:** -```http -Authorization: Bearer -``` - -**响应示例:** -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "id": 1, - "openid": "wx1234567890", - "nickname": "旅行达人", - "avatar": "https://avatar.url", - "gender": "male", - "birthday": "1990-01-01", - "phone": "13800138000", - "email": "user@jiebanke.com", - "travelCount": 5, - "animalClaimCount": 2, - "createdAt": "2024-01-01T00:00:00.000Z", - "updatedAt": "2024-01-01T00:00:00.000Z" - } -} -``` - -### 更新用户信息 - -**接口地址:** `PUT /api/v1/users/profile` - -**接口描述:** 更新用户个人信息 - -**请求参数:** -```json -{ - "nickname": "string, optional, 用户昵称", - "avatar": "string, optional, 头像URL", - "birthday": "string, optional, 生日(YYYY-MM-DD)", - "phone": "string, optional, 手机号码", - "email": "string, optional, 邮箱地址", - "interests": "array, optional, 兴趣爱好标签" -} -``` - -## 🧳 旅行结伴模块 - -### 发布旅行计划 - -**接口地址:** `POST /api/v1/travel/plans` - -**接口描述:** 用户发布新的旅行计划 - -**请求参数:** -```json -{ - "destination": "string, required, 目的地", - "startDate": "string, required, 开始日期(YYYY-MM-DD)", - "endDate": "string, required, 结束日期(YYYY-MM-DD)", - "budget": "number, optional, 预算金额", - "interests": "string, optional, 兴趣偏好", - "description": "string, optional, 行程描述", - "visibility": "string, optional, 可见性(public/friends/private)", - "maxParticipants": "number, optional, 最大参与人数" -} -``` - -**响应示例:** -```json -{ - "code": 200, - "message": "旅行计划发布成功", - "data": { - "id": 1001, - "userId": 1, - "destination": "云南大理", - "startDate": "2024-06-01", - "endDate": "2024-06-07", - "budget": 2000, - "interests": "美食,摄影,文化", - "status": "active", - "participantCount": 1, - "maxParticipants": 4, - "createdAt": "2024-01-01T00:00:00.000Z" - } -} -``` - -### 获取旅行计划列表 - -**接口地址:** `GET /api/v1/travel/plans` - -**接口描述:** 获取旅行计划列表,支持筛选和分页 - -**查询参数:** -``` -page: number, optional, 页码(默认1) -limit: number, optional, 每页数量(默认10) -destination: string, optional, 目的地筛选 -startDate: string, optional, 开始日期筛选 -endDate: string, optional, 结束日期筛选 -budget: number, optional, 预算筛选 -status: string, optional, 状态筛选(active/completed/cancelled) -``` - -**响应示例:** -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "plans": [ - { - "id": 1001, - "user": { - "id": 1, - "nickname": "旅行达人", - "avatar": "https://avatar.url" - }, - "destination": "云南大理", - "startDate": "2024-06-01", - "endDate": "2024-06-07", - "budget": 2000, - "participantCount": 2, - "maxParticipants": 4, - "status": "active" - } - ], - "pagination": { - "page": 1, - "limit": 10, - "total": 25, - "totalPages": 3 - } - } -} -``` - -### 申请加入旅行计划 - -**接口地址:** `POST /api/v1/travel/plans/{planId}/join` - -**接口描述:** 申请加入指定的旅行计划 - -**请求参数:** -```json -{ - "message": "string, optional, 申请留言" -} -``` - -## 🐄 动物认领模块 - -### 获取可认领动物列表 - -**接口地址:** `GET /api/v1/animals/available` - -**接口描述:** 获取可认领的动物列表 - -**查询参数:** -``` -type: string, optional, 动物类型(cow/sheep/pig/chicken) -farmId: number, optional, 农场ID筛选 -page: number, optional, 页码 -limit: number, optional, 每页数量 -``` - -**响应示例:** -```json -{ - "code": 200, - "message": "获取成功", - "data": { - "animals": [ - { - "id": 2001, - "name": "小花牛", - "type": "cow", - "age": 6, - "gender": "female", - "description": "温顺可爱的小花牛", - "images": ["https://image1.url", "https://image2.url"], - "price": 1200, - "farm": { - "id": 101, - "name": "阳光农场", - "location": "四川成都" - }, - "status": "available" - } - ], - "pagination": { - "page": 1, - "limit": 10, - "total": 15 - } - } -} -``` - -### 认领动物 - -**接口地址:** `POST /api/v1/animals/{animalId}/claim` - -**接口描述:** 认领指定的动物 - -**请求参数:** -```json -{ - "duration": "number, required, 认领时长(月)", - "message": "string, optional, 认领留言" -} -``` - -### 获取我的认领记录 - -**接口地址:** `GET /api/v1/animals/my-claims` - -**接口描述:** 获取当前用户的动物认领记录 - -## 🏪 商家服务模块 - -### 商家注册 - -**接口地址:** `POST /api/v1/merchants/register` - -**接口描述:** 商家用户注册 - -**请求参数:** -```json -{ - "businessName": "string, required, 商家名称", - "businessType": "string, required, 商家类型(flower_shop/farm/activity_organizer)", - "contactName": "string, required, 联系人姓名", - "contactPhone": "string, required, 联系电话", - "businessLicense": "string, required, 营业执照号", - "address": "string, required, 经营地址", - "description": "string, optional, 商家描述" -} -``` - -### 获取商家产品列表 - -**接口地址:** `GET /api/v1/merchants/{merchantId}/products` - -**接口描述:** 获取指定商家的产品列表 - -## 📦 订单管理模块 - -### 创建订单 - -**接口地址:** `POST /api/v1/orders` - -**接口描述:** 创建新订单 - -**请求参数:** -```json -{ - "type": "string, required, 订单类型(animal_claim/product_purchase/activity_booking)", - "items": [ - { - "itemId": "number, required, 商品/服务ID", - "quantity": "number, required, 数量", - "price": "number, required, 单价" - } - ], - "totalAmount": "number, required, 总金额", - "deliveryAddress": "object, optional, 配送地址信息" -} -``` - -### 获取订单列表 - -**接口地址:** `GET /api/v1/orders` - -**接口描述:** 获取用户订单列表 - -**查询参数:** -``` -status: string, optional, 订单状态筛选 -type: string, optional, 订单类型筛选 -page: number, optional, 页码 -limit: number, optional, 每页数量 -``` - -## 🎯 活动管理模块 - -### 获取活动列表 - -**接口地址:** `GET /api/v1/activities` - -**接口描述:** 获取活动列表 - -**查询参数:** -``` -type: string, optional, 活动类型 -location: string, optional, 地点筛选 -date: string, optional, 日期筛选 -status: string, optional, 状态筛选 -``` - -### 报名参加活动 - -**接口地址:** `POST /api/v1/activities/{activityId}/register` - -**接口描述:** 报名参加指定活动 - -## 💬 消息通知模块 - -### 获取消息列表 - -**接口地址:** `GET /api/v1/messages` - -**接口描述:** 获取用户消息列表 - -### 发送消息 - -**接口地址:** `POST /api/v1/messages` - -**接口描述:** 发送消息给其他用户 - -## 🔧 系统管理模块 - -### 获取系统配置 - -**接口地址:** `GET /api/v1/system/config` - -**接口描述:** 获取系统配置信息 - -### 文件上传 - -**接口地址:** `POST /api/v1/upload` - -**接口描述:** 上传文件(图片、文档等) - -**请求格式:** `multipart/form-data` - -**请求参数:** -``` -file: File, required, 上传的文件 -type: string, optional, 文件类型(avatar/product/document) -``` - -## 📱 错误代码参考 - -| 错误代码 | 错误描述 | 解决方案 | -|---------|---------|---------| -| 400 | 请求参数错误 | 检查请求参数格式和必填项 | -| 401 | 未授权访问 | 检查Token是否有效 | -| 403 | 权限不足 | 检查用户权限 | -| 404 | 资源不存在 | 检查请求的资源ID | -| 409 | 资源冲突 | 检查是否重复操作 | -| 429 | 请求频率限制 | 降低请求频率 | -| 500 | 服务器内部错误 | 联系技术支持 | - -## 📞 技术支持 - -如有API使用问题,请联系技术支持: -- 邮箱:tech-support@jiebanke.com -- 微信群:结伴客开发者群 - ---- - -*文档版本:v1.0* -*最后更新:2025年1月* \ No newline at end of file diff --git a/docs/前端开发文档.md b/docs/前端开发文档.md deleted file mode 100644 index f84e940..0000000 --- a/docs/前端开发文档.md +++ /dev/null @@ -1,1659 +0,0 @@ -# 解班客前端开发文档 - -## 📋 概述 - -本文档详细介绍解班客项目前端开发的技术架构、组件设计、开发规范和最佳实践。前端采用Vue.js 3 + TypeScript + Element Plus技术栈,提供现代化的用户界面和良好的用户体验。 - -## 🏗️ 技术架构 - -### 核心技术栈 - -#### 基础框架 -- **Vue.js 3.4+** - 渐进式JavaScript框架 -- **TypeScript 5.0+** - 类型安全的JavaScript超集 -- **Vite 5.0+** - 现代化构建工具 -- **Vue Router 4** - 官方路由管理器 -- **Pinia** - 状态管理库 - -#### UI组件库 -- **Element Plus** - 基于Vue 3的组件库 -- **@element-plus/icons-vue** - Element Plus图标库 -- **Tailwind CSS** - 原子化CSS框架 -- **SCSS** - CSS预处理器 - -#### 工具库 -- **Axios** - HTTP客户端 -- **Day.js** - 轻量级日期处理库 -- **VueUse** - Vue组合式API工具集 -- **Lodash-es** - JavaScript工具库 -- **@vueuse/core** - Vue组合式函数集合 - -#### 开发工具 -- **ESLint** - 代码检查工具 -- **Prettier** - 代码格式化工具 -- **Husky** - Git钩子工具 -- **Lint-staged** - 暂存文件检查 -- **Commitizen** - 规范化提交工具 - -### 项目结构 - -``` -frontend/ -├── public/ # 静态资源 -│ ├── favicon.ico -│ └── index.html -├── src/ -│ ├── api/ # API接口 -│ │ ├── modules/ # 按模块分类的API -│ │ │ ├── auth.ts # 认证相关API -│ │ │ ├── user.ts # 用户相关API -│ │ │ ├── animal.ts # 动物相关API -│ │ │ └── adoption.ts # 认领相关API -│ │ ├── request.ts # 请求拦截器 -│ │ └── types.ts # API类型定义 -│ ├── assets/ # 静态资源 -│ │ ├── images/ # 图片资源 -│ │ ├── icons/ # 图标资源 -│ │ └── styles/ # 全局样式 -│ │ ├── index.scss # 主样式文件 -│ │ ├── variables.scss # SCSS变量 -│ │ └── mixins.scss # SCSS混入 -│ ├── components/ # 公共组件 -│ │ ├── common/ # 通用组件 -│ │ │ ├── AppHeader.vue # 应用头部 -│ │ │ ├── AppFooter.vue # 应用底部 -│ │ │ ├── Loading.vue # 加载组件 -│ │ │ └── Pagination.vue # 分页组件 -│ │ └── business/ # 业务组件 -│ │ ├── AnimalCard.vue # 动物卡片 -│ │ ├── UserAvatar.vue # 用户头像 -│ │ └── MapView.vue # 地图组件 -│ ├── composables/ # 组合式函数 -│ │ ├── useAuth.ts # 认证相关 -│ │ ├── useApi.ts # API调用 -│ │ ├── useForm.ts # 表单处理 -│ │ └── useMap.ts # 地图功能 -│ ├── layouts/ # 布局组件 -│ │ ├── DefaultLayout.vue # 默认布局 -│ │ ├── AuthLayout.vue # 认证布局 -│ │ └── AdminLayout.vue # 管理布局 -│ ├── pages/ # 页面组件 -│ │ ├── home/ # 首页 -│ │ ├── auth/ # 认证页面 -│ │ ├── animal/ # 动物相关页面 -│ │ ├── user/ # 用户相关页面 -│ │ └── adoption/ # 认领相关页面 -│ ├── router/ # 路由配置 -│ │ ├── index.ts # 主路由文件 -│ │ ├── guards.ts # 路由守卫 -│ │ └── routes.ts # 路由定义 -│ ├── stores/ # 状态管理 -│ │ ├── modules/ # 按模块分类的store -│ │ │ ├── auth.ts # 认证状态 -│ │ │ ├── user.ts # 用户状态 -│ │ │ └── animal.ts # 动物状态 -│ │ └── index.ts # Store入口 -│ ├── types/ # 类型定义 -│ │ ├── api.ts # API类型 -│ │ ├── user.ts # 用户类型 -│ │ ├── animal.ts # 动物类型 -│ │ └── common.ts # 通用类型 -│ ├── utils/ # 工具函数 -│ │ ├── auth.ts # 认证工具 -│ │ ├── format.ts # 格式化工具 -│ │ ├── validate.ts # 验证工具 -│ │ └── constants.ts # 常量定义 -│ ├── App.vue # 根组件 -│ └── main.ts # 应用入口 -├── .env.development # 开发环境变量 -├── .env.production # 生产环境变量 -├── .eslintrc.js # ESLint配置 -├── .prettierrc # Prettier配置 -├── index.html # HTML模板 -├── package.json # 项目配置 -├── tsconfig.json # TypeScript配置 -└── vite.config.ts # Vite配置 -``` - -## 🎨 UI设计规范 - -### 设计系统 - -#### 色彩规范 -```scss -// 主色调 -$primary-color: #409EFF; // 主要品牌色 -$success-color: #67C23A; // 成功色 -$warning-color: #E6A23C; // 警告色 -$danger-color: #F56C6C; // 危险色 -$info-color: #909399; // 信息色 - -// 中性色 -$text-primary: #303133; // 主要文字 -$text-regular: #606266; // 常规文字 -$text-secondary: #909399; // 次要文字 -$text-placeholder: #C0C4CC; // 占位文字 - -// 边框色 -$border-base: #DCDFE6; // 基础边框 -$border-light: #E4E7ED; // 浅色边框 -$border-lighter: #EBEEF5; // 更浅边框 -$border-extra-light: #F2F6FC; // 极浅边框 - -// 背景色 -$bg-color: #FFFFFF; // 基础背景 -$bg-page: #F2F3F5; // 页面背景 -$bg-overlay: rgba(0,0,0,0.8); // 遮罩背景 -``` - -#### 字体规范 -```scss -// 字体大小 -$font-size-extra-large: 20px; // 超大字体 -$font-size-large: 18px; // 大字体 -$font-size-medium: 16px; // 中等字体 -$font-size-base: 14px; // 基础字体 -$font-size-small: 13px; // 小字体 -$font-size-extra-small: 12px; // 超小字体 - -// 字体粗细 -$font-weight-primary: 500; // 主要字重 -$font-weight-secondary: 400; // 次要字重 - -// 行高 -$line-height-primary: 24px; // 主要行高 -$line-height-secondary: 16px; // 次要行高 -``` - -#### 间距规范 -```scss -// 间距系统 (8px基准) -$spacing-xs: 4px; // 超小间距 -$spacing-sm: 8px; // 小间距 -$spacing-md: 16px; // 中等间距 -$spacing-lg: 24px; // 大间距 -$spacing-xl: 32px; // 超大间距 -$spacing-xxl: 48px; // 极大间距 -``` - -### 组件设计原则 - -#### 1. 一致性原则 -- 保持视觉风格统一 -- 交互行为一致 -- 命名规范统一 - -#### 2. 可访问性原则 -- 支持键盘导航 -- 提供语义化标签 -- 考虑屏幕阅读器 - -#### 3. 响应式原则 -- 移动端优先设计 -- 断点适配 -- 弹性布局 - -## 🧩 组件开发规范 - -### 组件命名规范 - -#### 文件命名 -``` -// ✅ 正确 - 使用PascalCase -AnimalCard.vue -UserProfile.vue -SearchForm.vue - -// ❌ 错误 -animalCard.vue -user-profile.vue -searchform.vue -``` - -#### 组件注册 -```typescript -// ✅ 正确 - 组件名使用PascalCase -export default defineComponent({ - name: 'AnimalCard', - // ... -}) - -// 全局注册 -app.component('AnimalCard', AnimalCard) -``` - -### 组件结构规范 - -#### 标准组件模板 -```vue - - - - - -``` - -### Props和Emits规范 - -#### Props定义 -```typescript -// ✅ 使用TypeScript接口定义Props -interface Props { - // 必需属性 - userId: number - - // 可选属性 - showAvatar?: boolean - - // 带默认值的属性 - size?: 'small' | 'medium' | 'large' - - // 复杂类型 - user?: User | null - - // 数组类型 - tags?: string[] - - // 函数类型 - onUpdate?: (value: string) => void -} - -// 设置默认值 -const props = withDefaults(defineProps(), { - showAvatar: true, - size: 'medium', - user: null, - tags: () => [], - onUpdate: undefined -}) -``` - -#### Emits定义 -```typescript -// ✅ 使用TypeScript接口定义Emits -interface Emits { - // 简单事件 - close: [] - - // 带参数的事件 - update: [value: string] - - // 多参数事件 - change: [id: number, value: string, meta?: any] - - // 对象参数事件 - submit: [data: { name: string; email: string }] -} - -const emit = defineEmits() - -// 触发事件 -const handleSubmit = () => { - emit('submit', { name: 'John', email: 'john@example.com' }) -} -``` - -## 🔄 状态管理 - -### Pinia Store设计 - -#### Store结构 -```typescript -// stores/modules/auth.ts -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { User, LoginForm, RegisterForm } from '@/types/user' -import { authApi } from '@/api/modules/auth' - -export const useAuthStore = defineStore('auth', () => { - // State - const user = ref(null) - const token = ref(localStorage.getItem('token')) - const loading = ref(false) - - // Getters - const isAuthenticated = computed(() => !!token.value && !!user.value) - const userRole = computed(() => user.value?.role || 'guest') - const permissions = computed(() => user.value?.permissions || []) - - // Actions - const login = async (form: LoginForm) => { - loading.value = true - try { - const response = await authApi.login(form) - token.value = response.token - user.value = response.user - - // 保存到localStorage - localStorage.setItem('token', response.token) - localStorage.setItem('user', JSON.stringify(response.user)) - - return response - } catch (error) { - console.error('Login failed:', error) - throw error - } finally { - loading.value = false - } - } - - const register = async (form: RegisterForm) => { - loading.value = true - try { - const response = await authApi.register(form) - return response - } catch (error) { - console.error('Register failed:', error) - throw error - } finally { - loading.value = false - } - } - - const logout = async () => { - try { - await authApi.logout() - } catch (error) { - console.error('Logout failed:', error) - } finally { - // 清除本地数据 - token.value = null - user.value = null - localStorage.removeItem('token') - localStorage.removeItem('user') - } - } - - const fetchUserInfo = async () => { - if (!token.value) return - - try { - const response = await authApi.getUserInfo() - user.value = response.user - localStorage.setItem('user', JSON.stringify(response.user)) - } catch (error) { - console.error('Fetch user info failed:', error) - // 如果获取用户信息失败,可能token已过期 - logout() - } - } - - const updateProfile = async (data: Partial) => { - try { - const response = await authApi.updateProfile(data) - user.value = { ...user.value, ...response.user } - localStorage.setItem('user', JSON.stringify(user.value)) - return response - } catch (error) { - console.error('Update profile failed:', error) - throw error - } - } - - // 初始化 - const init = () => { - const savedUser = localStorage.getItem('user') - if (savedUser && token.value) { - try { - user.value = JSON.parse(savedUser) - // 验证token有效性 - fetchUserInfo() - } catch (error) { - console.error('Parse saved user failed:', error) - logout() - } - } - } - - return { - // State - user, - token, - loading, - - // Getters - isAuthenticated, - userRole, - permissions, - - // Actions - login, - register, - logout, - fetchUserInfo, - updateProfile, - init - } -}) -``` - -### 组合式函数 (Composables) - -#### 认证相关 -```typescript -// composables/useAuth.ts -import { computed } from 'vue' -import { useRouter } from 'vue-router' -import { useAuthStore } from '@/stores/modules/auth' -import { ElMessage } from 'element-plus' - -export function useAuth() { - const authStore = useAuthStore() - const router = useRouter() - - // 计算属性 - const isLoggedIn = computed(() => authStore.isAuthenticated) - const currentUser = computed(() => authStore.user) - const userRole = computed(() => authStore.userRole) - - // 登录方法 - const login = async (form: LoginForm) => { - try { - await authStore.login(form) - ElMessage.success('登录成功') - - // 重定向到之前的页面或首页 - const redirect = router.currentRoute.value.query.redirect as string - router.push(redirect || '/') - } catch (error) { - ElMessage.error('登录失败,请检查用户名和密码') - throw error - } - } - - // 登出方法 - const logout = async () => { - try { - await authStore.logout() - ElMessage.success('已退出登录') - router.push('/login') - } catch (error) { - ElMessage.error('退出登录失败') - } - } - - // 权限检查 - const hasPermission = (permission: string) => { - return authStore.permissions.includes(permission) - } - - const hasRole = (role: string) => { - return authStore.userRole === role - } - - // 需要登录的操作 - const requireAuth = (callback: () => void) => { - if (isLoggedIn.value) { - callback() - } else { - ElMessage.warning('请先登录') - router.push('/login') - } - } - - return { - isLoggedIn, - currentUser, - userRole, - login, - logout, - hasPermission, - hasRole, - requireAuth - } -} -``` - -#### API调用 -```typescript -// composables/useApi.ts -import { ref, unref } from 'vue' -import type { Ref } from 'vue' -import { ElMessage } from 'element-plus' - -interface UseApiOptions { - immediate?: boolean - showError?: boolean - showSuccess?: boolean - successMessage?: string -} - -export function useApi( - apiFunction: (params?: P) => Promise, - options: UseApiOptions = {} -) { - const { - immediate = false, - showError = true, - showSuccess = false, - successMessage = '操作成功' - } = options - - const data = ref(null) - const loading = ref(false) - const error = ref(null) - - const execute = async (params?: P) => { - loading.value = true - error.value = null - - try { - const result = await apiFunction(params) - data.value = result - - if (showSuccess) { - ElMessage.success(successMessage) - } - - return result - } catch (err) { - error.value = err as Error - - if (showError) { - ElMessage.error(err.message || '操作失败') - } - - throw err - } finally { - loading.value = false - } - } - - // 立即执行 - if (immediate) { - execute() - } - - return { - data, - loading, - error, - execute - } -} - -// 使用示例 -export function useAnimalList() { - const { data: animals, loading, execute: fetchAnimals } = useApi( - animalApi.getList, - { immediate: true, showError: true } - ) - - const { execute: deleteAnimal } = useApi( - animalApi.delete, - { showSuccess: true, successMessage: '删除成功' } - ) - - return { - animals, - loading, - fetchAnimals, - deleteAnimal - } -} -``` - -## 🛣️ 路由设计 - -### 路由配置 -```typescript -// router/routes.ts -import type { RouteRecordRaw } from 'vue-router' - -export const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'Home', - component: () => import('@/layouts/DefaultLayout.vue'), - children: [ - { - path: '', - name: 'HomePage', - component: () => import('@/pages/home/HomePage.vue'), - meta: { - title: '首页', - requiresAuth: false - } - }, - { - path: '/animals', - name: 'AnimalList', - component: () => import('@/pages/animal/AnimalList.vue'), - meta: { - title: '动物列表', - requiresAuth: false - } - }, - { - path: '/animals/:id', - name: 'AnimalDetail', - component: () => import('@/pages/animal/AnimalDetail.vue'), - meta: { - title: '动物详情', - requiresAuth: false - } - } - ] - }, - { - path: '/auth', - component: () => import('@/layouts/AuthLayout.vue'), - children: [ - { - path: 'login', - name: 'Login', - component: () => import('@/pages/auth/Login.vue'), - meta: { - title: '登录', - requiresAuth: false, - hideForAuth: true - } - }, - { - path: 'register', - name: 'Register', - component: () => import('@/pages/auth/Register.vue'), - meta: { - title: '注册', - requiresAuth: false, - hideForAuth: true - } - } - ] - }, - { - path: '/user', - component: () => import('@/layouts/DefaultLayout.vue'), - meta: { - requiresAuth: true - }, - children: [ - { - path: 'profile', - name: 'UserProfile', - component: () => import('@/pages/user/Profile.vue'), - meta: { - title: '个人资料' - } - }, - { - path: 'animals', - name: 'UserAnimals', - component: () => import('@/pages/user/Animals.vue'), - meta: { - title: '我的动物' - } - }, - { - path: 'adoptions', - name: 'UserAdoptions', - component: () => import('@/pages/user/Adoptions.vue'), - meta: { - title: '我的认领' - } - } - ] - }, - { - path: '/admin', - component: () => import('@/layouts/AdminLayout.vue'), - meta: { - requiresAuth: true, - requiresRole: 'admin' - }, - children: [ - { - path: '', - name: 'AdminDashboard', - component: () => import('@/pages/admin/Dashboard.vue'), - meta: { - title: '管理后台' - } - } - ] - }, - { - path: '/:pathMatch(.*)*', - name: 'NotFound', - component: () => import('@/pages/error/NotFound.vue'), - meta: { - title: '页面不存在' - } - } -] -``` - -### 路由守卫 -```typescript -// router/guards.ts -import type { Router } from 'vue-router' -import { useAuthStore } from '@/stores/modules/auth' -import { ElMessage } from 'element-plus' - -export function setupRouterGuards(router: Router) { - // 全局前置守卫 - router.beforeEach(async (to, from, next) => { - const authStore = useAuthStore() - - // 设置页面标题 - if (to.meta.title) { - document.title = `${to.meta.title} - 解班客` - } - - // 检查是否需要认证 - if (to.meta.requiresAuth) { - if (!authStore.isAuthenticated) { - ElMessage.warning('请先登录') - next({ - name: 'Login', - query: { redirect: to.fullPath } - }) - return - } - - // 检查角色权限 - if (to.meta.requiresRole) { - if (authStore.userRole !== to.meta.requiresRole) { - ElMessage.error('权限不足') - next({ name: 'Home' }) - return - } - } - - // 检查具体权限 - if (to.meta.requiresPermission) { - if (!authStore.permissions.includes(to.meta.requiresPermission)) { - ElMessage.error('权限不足') - next({ name: 'Home' }) - return - } - } - } - - // 已登录用户访问登录/注册页面时重定向 - if (to.meta.hideForAuth && authStore.isAuthenticated) { - next({ name: 'Home' }) - return - } - - next() - }) - - // 全局后置钩子 - router.afterEach((to, from) => { - // 页面切换后的处理 - // 例如:埋点统计、页面加载完成事件等 - }) -} -``` - -## 🔧 工具函数 - -### 格式化工具 -```typescript -// utils/format.ts -import dayjs from 'dayjs' -import 'dayjs/locale/zh-cn' -import relativeTime from 'dayjs/plugin/relativeTime' - -dayjs.locale('zh-cn') -dayjs.extend(relativeTime) - -/** - * 格式化日期 - */ -export const formatDate = ( - date: string | number | Date, - format = 'YYYY-MM-DD HH:mm:ss' -): string => { - return dayjs(date).format(format) -} - -/** - * 格式化相对时间 - */ -export const formatRelativeTime = (date: string | number | Date): string => { - return dayjs(date).fromNow() -} - -/** - * 格式化文件大小 - */ -export const formatFileSize = (bytes: number): string => { - if (bytes === 0) return '0 B' - - const k = 1024 - const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] -} - -/** - * 格式化数字 - */ -export const formatNumber = (num: number): string => { - return num.toLocaleString('zh-CN') -} - -/** - * 格式化手机号 - */ -export const formatPhone = (phone: string): string => { - return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3') -} - -/** - * 格式化金额 - */ -export const formatMoney = (amount: number): string => { - return `¥${amount.toFixed(2)}` -} -``` - -### 验证工具 -```typescript -// utils/validate.ts - -/** - * 验证邮箱 - */ -export const isEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - return emailRegex.test(email) -} - -/** - * 验证手机号 - */ -export const isPhone = (phone: string): boolean => { - const phoneRegex = /^1[3-9]\d{9}$/ - return phoneRegex.test(phone) -} - -/** - * 验证身份证号 - */ -export const isIdCard = (idCard: string): boolean => { - const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ - return idCardRegex.test(idCard) -} - -/** - * 验证密码强度 - */ -export const validatePassword = (password: string): { - isValid: boolean - strength: 'weak' | 'medium' | 'strong' - message: string -} => { - if (password.length < 8) { - return { - isValid: false, - strength: 'weak', - message: '密码长度至少8位' - } - } - - let score = 0 - - // 包含小写字母 - if (/[a-z]/.test(password)) score++ - - // 包含大写字母 - if (/[A-Z]/.test(password)) score++ - - // 包含数字 - if (/\d/.test(password)) score++ - - // 包含特殊字符 - if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score++ - - if (score < 2) { - return { - isValid: false, - strength: 'weak', - message: '密码强度太弱,请包含字母和数字' - } - } else if (score < 3) { - return { - isValid: true, - strength: 'medium', - message: '密码强度中等' - } - } else { - return { - isValid: true, - strength: 'strong', - message: '密码强度很强' - } - } -} - -/** - * 表单验证规则 - */ -export const validationRules = { - required: { - required: true, - message: '此字段为必填项', - trigger: 'blur' - }, - - email: { - validator: (rule: any, value: string, callback: Function) => { - if (value && !isEmail(value)) { - callback(new Error('请输入正确的邮箱地址')) - } else { - callback() - } - }, - trigger: 'blur' - }, - - phone: { - validator: (rule: any, value: string, callback: Function) => { - if (value && !isPhone(value)) { - callback(new Error('请输入正确的手机号')) - } else { - callback() - } - }, - trigger: 'blur' - }, - - password: { - validator: (rule: any, value: string, callback: Function) => { - const result = validatePassword(value) - if (!result.isValid) { - callback(new Error(result.message)) - } else { - callback() - } - }, - trigger: 'blur' - } -} -``` - -## 🎯 性能优化 - -### 代码分割 -```typescript -// 路由懒加载 -const routes = [ - { - path: '/animals', - component: () => import('@/pages/animal/AnimalList.vue') - } -] - -// 组件懒加载 -const LazyComponent = defineAsyncComponent(() => import('@/components/HeavyComponent.vue')) - -// 条件加载 -const ConditionalComponent = defineAsyncComponent({ - loader: () => import('@/components/ConditionalComponent.vue'), - loadingComponent: Loading, - errorComponent: Error, - delay: 200, - timeout: 3000 -}) -``` - -### 缓存策略 -```typescript -// 组件缓存 - - -// API缓存 -const cache = new Map() - -export const cachedApi = { - async get(url: string, ttl = 5 * 60 * 1000) { - const cached = cache.get(url) - - if (cached && Date.now() - cached.timestamp < ttl) { - return cached.data - } - - const data = await api.get(url) - cache.set(url, { - data, - timestamp: Date.now() - }) - - return data - } -} -``` - -### 虚拟滚动 -```vue - - - -``` - -## 🧪 测试规范 - -### 单元测试 -```typescript -// tests/components/AnimalCard.test.ts -import { describe, it, expect, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import AnimalCard from '@/components/business/AnimalCard.vue' -import type { Animal } from '@/types/animal' - -const mockAnimal: Animal = { - id: 1, - name: '小白', - type: 'dog', - status: 'available', - description: '一只可爱的小狗', - avatar: 'https://example.com/avatar.jpg' -} - -describe('AnimalCard', () => { - it('renders animal information correctly', () => { - const wrapper = mount(AnimalCard, { - props: { - animal: mockAnimal - } - }) - - expect(wrapper.find('.animal-card__title').text()).toBe('小白') - expect(wrapper.find('.animal-card__description').text()).toBe('一只可爱的小狗') - expect(wrapper.find('.animal-card__image').attributes('src')).toBe(mockAnimal.avatar) - }) - - it('emits adopt event when adopt button is clicked', async () => { - const wrapper = mount(AnimalCard, { - props: { - animal: mockAnimal - } - }) - - await wrapper.find('.el-button').trigger('click') - - expect(wrapper.emitted('adopt')).toBeTruthy() - expect(wrapper.emitted('adopt')[0]).toEqual([mockAnimal.id]) - }) - - it('shows correct status', () => { - const wrapper = mount(AnimalCard, { - props: { - animal: { ...mockAnimal, status: 'adopted' } - } - }) - - const statusElement = wrapper.find('.animal-card__status--adopted') - expect(statusElement.exists()).toBe(true) - expect(statusElement.text()).toBe('已认领') - }) - - it('handles image error', async () => { - const wrapper = mount(AnimalCard, { - props: { - animal: mockAnimal - } - }) - - await wrapper.find('.animal-card__image').trigger('error') - - expect(wrapper.emitted('imageError')).toBeTruthy() - expect(wrapper.emitted('imageError')[0]).toEqual([mockAnimal]) - }) -}) -``` - -### 集成测试 -```typescript -// tests/pages/AnimalList.test.ts -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { mount } from '@vue/test-utils' -import { createPinia, setActivePinia } from 'pinia' -import AnimalList from '@/pages/animal/AnimalList.vue' -import { animalApi } from '@/api/modules/animal' - -// Mock API -vi.mock('@/api/modules/animal', () => ({ - animalApi: { - getList: vi.fn() - } -})) - -describe('AnimalList', () => { - beforeEach(() => { - setActivePinia(createPinia()) - vi.clearAllMocks() - }) - - it('loads and displays animals on mount', async () => { - const mockAnimals = [ - { id: 1, name: '小白', type: 'dog', status: 'available' }, - { id: 2, name: '小黑', type: 'cat', status: 'available' } - ] - - vi.mocked(animalApi.getList).mockResolvedValue({ - data: mockAnimals, - total: 2 - }) - - const wrapper = mount(AnimalList) - - // 等待异步操作完成 - await wrapper.vm.$nextTick() - - expect(animalApi.getList).toHaveBeenCalled() - expect(wrapper.findAll('.animal-card')).toHaveLength(2) - }) - - it('handles search functionality', async () => { - const wrapper = mount(AnimalList) - - const searchInput = wrapper.find('input[placeholder="搜索动物"]') - await searchInput.setValue('小白') - await searchInput.trigger('input') - - // 验证搜索参数 - expect(animalApi.getList).toHaveBeenCalledWith({ - keyword: '小白', - page: 1, - limit: 20 - }) - }) -}) -``` - -## 📱 响应式设计 - -### 断点系统 -```scss -// 断点定义 -$breakpoints: ( - xs: 0, - sm: 576px, - md: 768px, - lg: 992px, - xl: 1200px, - xxl: 1400px -); - -// 媒体查询混入 -@mixin respond-to($breakpoint) { - @if map-has-key($breakpoints, $breakpoint) { - @media (min-width: map-get($breakpoints, $breakpoint)) { - @content; - } - } -} - -// 使用示例 -.container { - padding: 16px; - - @include respond-to(md) { - padding: 24px; - } - - @include respond-to(lg) { - padding: 32px; - } -} -``` - -### 移动端适配 -```vue - - - -``` - -## 🔍 调试和开发工具 - -### Vue DevTools配置 -```typescript -// main.ts -import { createApp } from 'vue' -import App from './App.vue' - -const app = createApp(App) - -// 开发环境配置 -if (import.meta.env.DEV) { - // 启用Vue DevTools - app.config.devtools = true - - // 全局错误处理 - app.config.errorHandler = (err, vm, info) => { - console.error('Vue Error:', err) - console.error('Component:', vm) - console.error('Info:', info) - } - - // 全局警告处理 - app.config.warnHandler = (msg, vm, trace) => { - console.warn('Vue Warning:', msg) - console.warn('Component:', vm) - console.warn('Trace:', trace) - } -} -``` - -### 开发环境配置 -```typescript -// vite.config.ts -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import { resolve } from 'path' - -export default defineConfig({ - plugins: [vue()], - - resolve: { - alias: { - '@': resolve(__dirname, 'src') - } - }, - - server: { - port: 3000, - open: true, - cors: true, - proxy: { - '/api': { - target: 'http://localhost:3001', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') - } - } - }, - - build: { - sourcemap: true, - rollupOptions: { - output: { - manualChunks: { - vendor: ['vue', 'vue-router', 'pinia'], - element: ['element-plus'], - utils: ['axios', 'dayjs', 'lodash-es'] - } - } - } - } -}) -``` - -## 📚 总结 - -本文档详细介绍了解班客项目前端开发的各个方面,包括技术架构、组件设计、状态管理、路由配置、性能优化等。遵循这些规范和最佳实践,可以确保代码质量、提高开发效率、增强项目的可维护性。 - -### 关键要点 - -1. **技术选型**: Vue 3 + TypeScript + Element Plus提供现代化开发体验 -2. **组件化**: 采用组合式API和单文件组件,提高代码复用性 -3. **状态管理**: 使用Pinia进行状态管理,支持TypeScript -4. **路由设计**: 基于角色的权限控制和懒加载优化 -5. **性能优化**: 代码分割、缓存策略、虚拟滚动等技术 -6. **响应式设计**: 移动端优先,多断点适配 -7. **测试覆盖**: 单元测试和集成测试保证代码质量 - -### 后续计划 - -- 完善组件库和设计系统 -- 增加更多性能优化策略 -- 完善测试用例覆盖 -- 添加国际化支持 -- 集成更多开发工具 - ---- - -**文档版本**: v1.0.0 -**最后更新**: 2024年1月15日 -**维护人员**: 前端开发团队 \ No newline at end of file diff --git a/docs/动物认领系统API文档.md b/docs/动物认领系统API文档.md deleted file mode 100644 index 9e1c40f..0000000 --- a/docs/动物认领系统API文档.md +++ /dev/null @@ -1,478 +0,0 @@ -# 动物认领系统API文档 - -## 概述 - -动物认领系统提供了完整的动物认领申请、审核、管理功能,支持用户申请认领动物、管理员审核申请、认领续期等功能。 - -## 基础信息 - -- **基础URL**: `/api/v1/animal-claims` -- **认证方式**: Bearer Token -- **数据格式**: JSON -- **字符编码**: UTF-8 - -## 数据模型 - -### 认领申请 (AnimalClaim) - -```json -{ - "id": 1, - "claim_no": "CLAIM20241201001", - "animal_id": 1, - "animal_name": "小白", - "animal_type": "狗", - "animal_image": "/uploads/animals/dog1.jpg", - "user_id": 2, - "username": "张三", - "user_phone": "13800138001", - "claim_reason": "我很喜欢这只小狗", - "claim_duration": 12, - "total_amount": 1200.00, - "contact_info": "手机:13800138001,微信:user001", - "status": "pending", - "start_date": "2024-12-01T11:30:00.000Z", - "end_date": "2025-12-01T11:30:00.000Z", - "reviewed_by": 1, - "reviewer_name": "管理员", - "review_remark": "申请材料完整,同意认领", - "reviewed_at": "2024-12-01T11:30:00.000Z", - "approved_at": "2024-12-01T11:30:00.000Z", - "cancelled_at": null, - "cancel_reason": null, - "created_at": "2024-12-01T10:00:00.000Z", - "updated_at": "2024-12-01T11:30:00.000Z" -} -``` - -### 认领统计 (ClaimStatistics) - -```json -{ - "basic": { - "total_claims": 100, - "pending_claims": 10, - "approved_claims": 80, - "rejected_claims": 8, - "cancelled_claims": 2, - "total_amount": 120000.00, - "avg_duration": 12.5 - }, - "by_type": [ - { - "type": "狗", - "claim_count": 50, - "approved_count": 45, - "total_amount": 60000.00 - } - ], - "by_month": [ - { - "month": "2024-12", - "claim_count": 20, - "approved_count": 18, - "total_amount": 24000.00 - } - ] -} -``` - -## API接口 - -### 1. 申请认领动物 - -**接口地址**: `POST /api/v1/animal-claims` - -**请求头**: -``` -Authorization: Bearer {token} -Content-Type: application/json -``` - -**请求参数**: -```json -{ - "animal_id": 1, - "claim_reason": "我很喜欢这只小狗,希望能够认领它", - "claim_duration": 12, - "contact_info": "手机:13800138001,微信:user001" -} -``` - -**参数说明**: -- `animal_id` (必填): 动物ID -- `claim_reason` (可选): 认领理由 -- `claim_duration` (可选): 认领时长(月),默认12个月,范围1-60 -- `contact_info` (必填): 联系方式 - -**响应示例**: -```json -{ - "success": true, - "message": "认领申请提交成功", - "data": { - "id": 1, - "claim_no": "CLAIM20241201001", - "animal_id": 1, - "user_id": 2, - "status": "pending", - "total_amount": 1200.00 - } -} -``` - -### 2. 获取我的认领申请列表 - -**接口地址**: `GET /api/v1/animal-claims/my` - -**请求参数**: -- `page` (可选): 页码,默认1 -- `limit` (可选): 每页数量,默认10,最大100 -- `status` (可选): 申请状态 (pending/approved/rejected/cancelled) -- `animal_type` (可选): 动物类型 -- `start_date` (可选): 开始日期 (YYYY-MM-DD) -- `end_date` (可选): 结束日期 (YYYY-MM-DD) - -**响应示例**: -```json -{ - "success": true, - "message": "获取认领申请列表成功", - "data": [ - { - "id": 1, - "claim_no": "CLAIM20241201001", - "animal_name": "小白", - "status": "pending" - } - ], - "pagination": { - "page": 1, - "limit": 10, - "total": 1, - "pages": 1 - } -} -``` - -### 3. 取消认领申请 - -**接口地址**: `PUT /api/v1/animal-claims/{id}/cancel` - -**路径参数**: -- `id`: 认领申请ID - -**响应示例**: -```json -{ - "success": true, - "message": "认领申请已取消", - "data": { - "id": 1, - "status": "cancelled", - "cancelled_at": "2024-12-01T15:00:00.000Z", - "cancel_reason": "用户主动取消" - } -} -``` - -### 4. 审核认领申请(管理员) - -**接口地址**: `PUT /api/v1/animal-claims/{id}/review` - -**权限要求**: 管理员或经理 - -**请求参数**: -```json -{ - "status": "approved", - "review_remark": "申请材料完整,同意认领" -} -``` - -**参数说明**: -- `status` (必填): 审核状态 (approved/rejected) -- `review_remark` (可选): 审核备注 - -**响应示例**: -```json -{ - "success": true, - "message": "认领申请审核通过", - "data": { - "id": 1, - "status": "approved", - "reviewed_at": "2024-12-01T11:30:00.000Z", - "start_date": "2024-12-01T11:30:00.000Z", - "end_date": "2025-12-01T11:30:00.000Z" - } -} -``` - -### 5. 获取所有认领申请列表(管理员) - -**接口地址**: `GET /api/v1/animal-claims` - -**权限要求**: 管理员或经理 - -**请求参数**: -- `page` (可选): 页码,默认1 -- `limit` (可选): 每页数量,默认10,最大100 -- `status` (可选): 申请状态 -- `animal_type` (可选): 动物类型 -- `user_id` (可选): 用户ID -- `start_date` (可选): 开始日期 -- `end_date` (可选): 结束日期 -- `keyword` (可选): 关键词搜索(订单号、动物名称、用户名) - -### 6. 获取动物的认领申请列表(管理员) - -**接口地址**: `GET /api/v1/animal-claims/animal/{animal_id}` - -**权限要求**: 管理员或经理 - -**路径参数**: -- `animal_id`: 动物ID - -### 7. 检查认领权限 - -**接口地址**: `GET /api/v1/animal-claims/check-permission/{animal_id}` - -**路径参数**: -- `animal_id`: 动物ID - -**响应示例**: -```json -{ - "success": true, - "message": "检查认领权限成功", - "data": { - "can_claim": true - } -} -``` - -### 8. 续期认领 - -**接口地址**: `POST /api/v1/animal-claims/{id}/renew` - -**请求参数**: -```json -{ - "duration": 6, - "payment_method": "wechat" -} -``` - -**参数说明**: -- `duration` (必填): 续期时长(月),范围1-60 -- `payment_method` (必填): 支付方式 (wechat/alipay/bank_transfer) - -**响应示例**: -```json -{ - "success": true, - "message": "续期申请已提交,请完成支付", - "data": { - "renewal": { - "id": 1, - "claim_id": 1, - "duration": 6, - "amount": 600.00, - "status": "pending" - }, - "amount": 600.00, - "message": "续期申请已提交,请完成支付" - } -} -``` - -### 9. 获取认领统计信息(管理员) - -**接口地址**: `GET /api/v1/animal-claims/statistics` - -**权限要求**: 管理员或经理 - -**请求参数**: -- `start_date` (可选): 开始日期 -- `end_date` (可选): 结束日期 -- `animal_type` (可选): 动物类型 - -**响应示例**: -```json -{ - "success": true, - "message": "获取认领统计信息成功", - "data": { - "basic": { - "total_claims": 100, - "pending_claims": 10, - "approved_claims": 80, - "rejected_claims": 8, - "cancelled_claims": 2, - "total_amount": 120000.00, - "avg_duration": 12.5 - }, - "by_type": [...], - "by_month": [...] - } -} -``` - -## 状态说明 - -### 认领申请状态 - -- `pending`: 待审核 -- `approved`: 已通过 -- `rejected`: 已拒绝 -- `cancelled`: 已取消 -- `expired`: 已过期(系统自动设置) - -### 续期状态 - -- `pending`: 待支付 -- `paid`: 已支付 -- `cancelled`: 已取消 - -## 支付方式 - -- `wechat`: 微信支付 -- `alipay`: 支付宝 -- `bank_transfer`: 银行转账 - -## 错误码说明 - -| 错误码 | 说明 | -|--------|------| -| 400 | 请求参数错误 | -| 401 | 未授权,需要登录 | -| 403 | 权限不足 | -| 404 | 资源不存在 | -| 500 | 服务器内部错误 | -| 503 | 服务不可用(无数据库模式) | - -## 业务规则 - -### 认领申请规则 - -1. 每个用户对同一动物只能有一个有效的认领申请 -2. 只有状态为"可认领"的动物才能被申请认领 -3. 认领时长范围为1-60个月 -4. 认领申请通过后,动物状态自动变为"已认领" - -### 审核规则 - -1. 只有待审核状态的申请才能被审核 -2. 审核通过后自动设置开始和结束时间 -3. 审核拒绝后动物状态保持不变 - -### 续期规则 - -1. 只有已通过的认领申请才能续期 -2. 距离到期30天内才能申请续期 -3. 续期需要完成支付才能生效 - -### 取消规则 - -1. 待审核和已通过的申请可以取消 -2. 取消已通过的申请会恢复动物为可认领状态 - -## 注意事项 - -1. 所有时间字段均为UTC时间,前端需要根据时区进行转换 -2. 金额字段为浮点数,建议前端使用专门的货币处理库 -3. 图片路径为相对路径,需要拼接完整的URL -4. 分页查询建议设置合理的limit值,避免一次性查询过多数据 -5. 关键词搜索支持模糊匹配,会搜索订单号、动物名称、用户名 - -## 集成示例 - -### JavaScript示例 - -```javascript -// 申请认领动物 -async function claimAnimal(animalId, claimData) { - const response = await fetch('/api/v1/animal-claims', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - animal_id: animalId, - ...claimData - }) - }); - - return await response.json(); -} - -// 获取我的认领申请列表 -async function getMyClaimList(params = {}) { - const queryString = new URLSearchParams(params).toString(); - const response = await fetch(`/api/v1/animal-claims/my?${queryString}`, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); - - return await response.json(); -} - -// 取消认领申请 -async function cancelClaim(claimId) { - const response = await fetch(`/api/v1/animal-claims/${claimId}/cancel`, { - method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}` - } - }); - - return await response.json(); -} -``` - -### 前端状态管理示例 - -```javascript -// 认领申请状态管理 -const claimStore = { - state: { - myClaimList: [], - currentClaim: null, - loading: false - }, - - mutations: { - SET_CLAIM_LIST(state, list) { - state.myClaimList = list; - }, - - SET_CURRENT_CLAIM(state, claim) { - state.currentClaim = claim; - }, - - UPDATE_CLAIM_STATUS(state, { claimId, status }) { - const claim = state.myClaimList.find(c => c.id === claimId); - if (claim) { - claim.status = status; - } - } - }, - - actions: { - async fetchMyClaimList({ commit }, params) { - commit('SET_LOADING', true); - try { - const result = await getMyClaimList(params); - if (result.success) { - commit('SET_CLAIM_LIST', result.data); - } - } finally { - commit('SET_LOADING', false); - } - } - } -}; -``` \ No newline at end of file diff --git a/docs/后端开发文档.md b/docs/后端开发文档.md new file mode 100644 index 0000000..87092bc --- /dev/null +++ b/docs/后端开发文档.md @@ -0,0 +1,862 @@ +# 后端开发文档 + +## 1. 项目概述 + +### 1.1 项目简介 +解班客后端服务是一个基于Node.js + TypeScript + Express的微服务架构系统,为小程序端、管理后台和官网提供API服务支持。 + +### 1.2 技术栈 +- **运行环境**:Node.js 18.x +- **开发语言**:TypeScript 5.x +- **Web框架**:Express.js 4.x +- **数据库**:MySQL 8.0 + Redis 7.x +- **ORM框架**:TypeORM 0.3.x +- **认证授权**:JWT + 微信授权 +- **文件存储**:阿里云OSS +- **消息队列**:Redis + Bull Queue +- **监控日志**:Winston + PM2 +- **测试框架**:Jest + Supertest +- **代码规范**:ESLint + Prettier + +### 1.3 项目结构 +``` +backend/ +├── src/ +│ ├── config/ # 配置文件 +│ ├── controllers/ # 控制器 +│ ├── services/ # 业务逻辑层 +│ ├── models/ # 数据模型 +│ ├── middlewares/ # 中间件 +│ ├── utils/ # 工具函数 +│ ├── types/ # 类型定义 +│ ├── routes/ # 路由定义 +│ ├── jobs/ # 队列任务 +│ └── app.ts # 应用入口 +├── tests/ # 测试文件 +├── docs/ # 接口文档 +├── scripts/ # 脚本文件 +├── docker/ # Docker配置 +├── package.json +├── tsconfig.json +├── .env.example +└── README.md +``` + +## 2. 开发环境搭建 + +### 2.1 环境要求 +- Node.js >= 18.0.0 +- npm >= 9.0.0 +- MySQL >= 8.0 +- Redis >= 7.0 +- Git >= 2.30 + +### 2.2 环境搭建步骤 + +#### 2.2.1 克隆项目 +```bash +git clone https://github.com/your-org/jiebanke-backend.git +cd jiebanke-backend +``` + +#### 2.2.2 安装依赖 +```bash +npm install +``` + +#### 2.2.3 配置环境变量 +```bash +cp .env.example .env +# 编辑 .env 文件,配置数据库连接等信息 +``` + +#### 2.2.4 数据库初始化 +```bash +# 创建数据库 +npm run db:create + +# 运行迁移 +npm run db:migrate + +# 填充测试数据 +npm run db:seed +``` + +#### 2.2.5 启动开发服务器 +```bash +npm run dev +``` + +### 2.3 开发工具配置 + +#### 2.3.1 VSCode配置 +推荐安装以下插件: +- TypeScript Importer +- ESLint +- Prettier +- REST Client +- GitLens + +#### 2.3.2 代码规范配置 +```json +// .eslintrc.json +{ + "extends": [ + "@typescript-eslint/recommended", + "prettier" + ], + "rules": { + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/explicit-function-return-type": "warn" + } +} +``` + +## 3. 开发计划与任务分解 + +### 3.1 开发阶段划分 + +#### 阶段一:基础架构搭建(预计15个工作日) +- 项目初始化和环境配置 +- 数据库设计和迁移 +- 基础中间件和工具类开发 +- 认证授权系统 + +#### 阶段二:核心业务模块(预计25个工作日) +- 用户管理模块 +- 旅行结伴模块 +- 动物认领模块 +- 支付系统模块 + +#### 阶段三:辅助功能模块(预计15个工作日) +- 文件上传模块 +- 消息通知模块 +- 搜索功能模块 +- 统计分析模块 + +#### 阶段四:系统优化和测试(预计10个工作日) +- 性能优化 +- 安全加固 +- 单元测试和集成测试 +- 部署和运维配置 + +### 3.2 详细任务分解 + +#### 3.2.1 阶段一:基础架构搭建 + +##### 任务1.1:项目初始化(2个工作日) +**负责人**:架构师 + 后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 创建项目目录结构 +- 配置TypeScript和构建工具 +- 设置代码规范和Git hooks +- 配置开发环境和调试工具 + +**具体子任务**: +1. 初始化npm项目和依赖安装(4人时) +2. 配置TypeScript编译选项(2人时) +3. 设置ESLint和Prettier规范(2人时) +4. 配置Webpack/Vite构建工具(4人时) +5. 设置Git hooks和CI/CD基础配置(4人时) + +**验收标准**: +- 项目可以正常启动和热重载 +- 代码规范检查通过 +- Git提交触发自动化检查 + +##### 任务1.2:数据库设计和迁移(3个工作日) +**负责人**:数据库设计师 + 后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 设计数据库表结构 +- 创建TypeORM实体模型 +- 编写数据库迁移脚本 +- 创建种子数据 + +**具体子任务**: +1. 设计用户相关表结构(6人时) +2. 设计旅行和动物相关表结构(8人时) +3. 设计订单和支付相关表结构(4人时) +4. 创建TypeORM实体和关系映射(4人时) +5. 编写迁移脚本和种子数据(2人时) + +**验收标准**: +- 数据库表结构符合设计要求 +- 实体关系映射正确 +- 迁移脚本可以正常执行 + +##### 任务1.3:基础中间件开发(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 开发认证中间件 +- 开发权限控制中间件 +- 开发日志记录中间件 +- 开发错误处理中间件 + +**具体子任务**: +1. JWT认证中间件开发(8人时) +2. 权限控制中间件开发(6人时) +3. 请求日志中间件开发(4人时) +4. 全局错误处理中间件开发(6人时) +5. 参数验证中间件开发(4人时) +6. 限流中间件开发(4人时) + +**验收标准**: +- 中间件功能正常 +- 单元测试覆盖率达到80% +- 性能测试通过 + +##### 任务1.4:工具类和配置管理(2个工作日) +**负责人**:后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 开发通用工具类 +- 配置管理系统 +- 缓存工具类 +- 文件处理工具类 + +**具体子任务**: +1. 加密解密工具类(4人时) +2. 日期时间工具类(2人时) +3. 字符串处理工具类(2人时) +4. 配置管理工具(4人时) +5. Redis缓存工具类(4人时) + +**验收标准**: +- 工具类功能完整 +- 配置可以动态加载 +- 缓存操作正常 + +##### 任务1.5:认证授权系统(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 微信登录集成 +- JWT Token管理 +- 用户权限系统 +- 会话管理 + +**具体子任务**: +1. 微信小程序登录接口(8人时) +2. JWT Token生成和验证(6人时) +3. 用户权限模型设计(6人时) +4. 会话管理和刷新机制(6人时) +5. 权限装饰器开发(6人时) + +**验收标准**: +- 微信登录流程正常 +- Token验证机制完善 +- 权限控制精确 + +#### 3.2.2 阶段二:核心业务模块 + +##### 任务2.1:用户管理模块(6个工作日) +**负责人**:后端开发工程师 +**工时估算**:48人时 +**任务描述**: +- 用户信息管理 +- 用户认证和授权 +- 用户资料完善 +- 用户状态管理 + +**具体子任务**: +1. 用户注册和登录接口(8人时) +2. 用户信息CRUD接口(8人时) +3. 用户头像上传功能(6人时) +4. 用户实名认证功能(8人时) +5. 用户状态管理(6人时) +6. 用户统计信息接口(6人时) +7. 用户关注和粉丝功能(6人时) + +**验收标准**: +- 用户注册登录流程完整 +- 用户信息管理功能正常 +- 实名认证流程通畅 + +##### 任务2.2:旅行结伴模块(8个工作日) +**负责人**:后端开发工程师 +**工时估算**:64人时 +**任务描述**: +- 旅行活动管理 +- 参与申请处理 +- 活动状态管理 +- 评论和互动功能 + +**具体子任务**: +1. 旅行活动CRUD接口(12人时) +2. 活动搜索和筛选功能(8人时) +3. 参与申请管理接口(10人时) +4. 活动状态流转管理(8人时) +5. 活动评论和点赞功能(8人时) +6. 活动图片和视频管理(6人时) +7. 活动推荐算法(8人时) +8. 活动统计分析接口(4人时) + +**验收标准**: +- 旅行活动管理功能完整 +- 申请审核流程正常 +- 搜索和推荐准确 + +##### 任务2.3:动物认领模块(7个工作日) +**负责人**:后端开发工程师 +**工时估算**:56人时 +**任务描述**: +- 动物信息管理 +- 认领申请处理 +- 认领状态跟踪 +- 动物成长记录 + +**具体子任务**: +1. 动物信息CRUD接口(10人时) +2. 动物搜索和筛选功能(8人时) +3. 认领申请管理接口(10人时) +4. 认领状态管理(8人时) +5. 动物成长记录功能(8人时) +6. 动物健康档案管理(6人时) +7. 认领费用计算(6人时) + +**验收标准**: +- 动物信息管理完整 +- 认领流程顺畅 +- 成长记录功能正常 + +##### 任务2.4:支付系统模块(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 订单管理系统 +- 微信支付集成 +- 支付状态跟踪 +- 退款处理 + +**具体子任务**: +1. 订单创建和管理接口(8人时) +2. 微信支付接口集成(10人时) +3. 支付回调处理(6人时) +4. 退款功能开发(6人时) +5. 支付状态同步(2人时) + +**验收标准**: +- 订单管理功能完整 +- 微信支付流程正常 +- 退款处理准确 + +#### 3.2.3 阶段三:辅助功能模块 + +##### 任务3.1:文件上传模块(3个工作日) +**负责人**:后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 文件上传接口 +- 图片处理功能 +- 文件存储管理 +- CDN集成 + +**具体子任务**: +1. 文件上传接口开发(8人时) +2. 图片压缩和处理(6人时) +3. 阿里云OSS集成(6人时) +4. 文件管理接口(4人时) + +**验收标准**: +- 文件上传功能正常 +- 图片处理效果良好 +- 存储和访问稳定 + +##### 任务3.2:消息通知模块(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 站内消息系统 +- 微信模板消息 +- 消息推送功能 +- 消息状态管理 + +**具体子任务**: +1. 站内消息CRUD接口(8人时) +2. 微信模板消息集成(8人时) +3. 消息推送队列(8人时) +4. 消息状态管理(4人时) +5. 消息统计功能(4人时) + +**验收标准**: +- 消息发送接收正常 +- 推送功能稳定 +- 状态管理准确 + +##### 任务3.3:搜索功能模块(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 全文搜索功能 +- 搜索建议 +- 搜索统计 +- 搜索优化 + +**具体子任务**: +1. 搜索接口开发(10人时) +2. 搜索建议功能(6人时) +3. 搜索结果排序(8人时) +4. 搜索统计分析(4人时) +5. 搜索性能优化(4人时) + +**验收标准**: +- 搜索结果准确 +- 响应速度快 +- 建议功能智能 + +##### 任务3.4:统计分析模块(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 用户行为统计 +- 业务数据分析 +- 报表生成 +- 数据可视化接口 + +**具体子任务**: +1. 用户行为统计接口(8人时) +2. 业务数据统计接口(8人时) +3. 报表生成功能(8人时) +4. 数据导出功能(4人时) +5. 统计数据缓存优化(4人时) + +**验收标准**: +- 统计数据准确 +- 报表生成正常 +- 性能满足要求 + +#### 3.2.4 阶段四:系统优化和测试 + +##### 任务4.1:性能优化(3个工作日) +**负责人**:后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 数据库查询优化 +- 缓存策略优化 +- 接口性能优化 +- 系统监控 + +**具体子任务**: +1. SQL查询优化(8人时) +2. Redis缓存策略优化(6人时) +3. 接口响应时间优化(6人时) +4. 系统监控配置(4人时) + +**验收标准**: +- 接口响应时间<200ms +- 数据库查询效率提升50% +- 缓存命中率>80% + +##### 任务4.2:安全加固(2个工作日) +**负责人**:后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 安全漏洞检查 +- 数据加密处理 +- 接口安全加固 +- 安全审计 + +**具体子任务**: +1. SQL注入防护(4人时) +2. XSS攻击防护(4人时) +3. 敏感数据加密(4人时) +4. 接口访问控制(4人时) + +**验收标准**: +- 安全扫描无高危漏洞 +- 敏感数据加密存储 +- 接口访问控制完善 + +##### 任务4.3:测试开发(3个工作日) +**负责人**:后端开发工程师 + 测试工程师 +**工时估算**:24人时 +**任务描述**: +- 单元测试编写 +- 集成测试开发 +- 接口测试自动化 +- 性能测试 + +**具体子任务**: +1. 单元测试编写(10人时) +2. 集成测试开发(8人时) +3. 接口自动化测试(4人时) +4. 性能测试脚本(2人时) + +**验收标准**: +- 单元测试覆盖率>80% +- 集成测试通过率100% +- 接口测试自动化完成 + +##### 任务4.4:部署和运维配置(2个工作日) +**负责人**:运维工程师 + 后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- Docker容器化 +- CI/CD流水线 +- 监控告警配置 +- 日志收集配置 + +**具体子任务**: +1. Docker镜像构建(4人时) +2. CI/CD流水线配置(6人时) +3. 监控告警配置(4人时) +4. 日志收集配置(2人时) + +**验收标准**: +- 容器化部署成功 +- CI/CD流水线正常 +- 监控告警及时 + +## 4. 开发规范 + +### 4.1 代码规范 + +#### 4.1.1 命名规范 +- **文件命名**:使用kebab-case,如`user-service.ts` +- **类命名**:使用PascalCase,如`UserService` +- **方法命名**:使用camelCase,如`getUserById` +- **常量命名**:使用UPPER_SNAKE_CASE,如`MAX_FILE_SIZE` + +#### 4.1.2 目录结构规范 +``` +src/ +├── controllers/ +│ ├── user.controller.ts +│ ├── travel.controller.ts +│ └── animal.controller.ts +├── services/ +│ ├── user.service.ts +│ ├── travel.service.ts +│ └── animal.service.ts +├── models/ +│ ├── user.model.ts +│ ├── travel.model.ts +│ └── animal.model.ts +└── routes/ + ├── user.routes.ts + ├── travel.routes.ts + └── animal.routes.ts +``` + +#### 4.1.3 注释规范 +```typescript +/** + * 用户服务类 + * 处理用户相关的业务逻辑 + */ +export class UserService { + /** + * 根据ID获取用户信息 + * @param id 用户ID + * @returns 用户信息对象 + * @throws {NotFoundError} 用户不存在时抛出 + */ + async getUserById(id: number): Promise { + // 实现逻辑 + } +} +``` + +### 4.2 API设计规范 + +#### 4.2.1 RESTful接口规范 +- **GET** `/api/v1/users` - 获取用户列表 +- **GET** `/api/v1/users/{id}` - 获取单个用户 +- **POST** `/api/v1/users` - 创建用户 +- **PUT** `/api/v1/users/{id}` - 更新用户 +- **DELETE** `/api/v1/users/{id}` - 删除用户 + +#### 4.2.2 响应格式规范 +```typescript +interface ApiResponse { + code: number; + message: string; + data?: T; + timestamp: string; + request_id: string; +} +``` + +#### 4.2.3 错误处理规范 +```typescript +export class ApiError extends Error { + constructor( + public code: number, + public message: string, + public details?: any + ) { + super(message); + } +} +``` + +### 4.3 数据库操作规范 + +#### 4.3.1 实体定义规范 +```typescript +@Entity('users') +export class User { + @PrimaryGeneratedColumn() + id: number; + + @Column({ length: 50, unique: true }) + username: string; + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; +} +``` + +#### 4.3.2 查询规范 +```typescript +// 使用Repository模式 +const user = await this.userRepository.findOne({ + where: { id }, + relations: ['profile'] +}); + +// 使用QueryBuilder进行复杂查询 +const users = await this.userRepository + .createQueryBuilder('user') + .leftJoinAndSelect('user.profile', 'profile') + .where('user.status = :status', { status: 1 }) + .getMany(); +``` + +### 4.4 测试规范 + +#### 4.4.1 单元测试规范 +```typescript +describe('UserService', () => { + let service: UserService; + let repository: Repository; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [UserService, mockUserRepository] + }).compile(); + + service = module.get(UserService); + repository = module.get>(getRepositoryToken(User)); + }); + + it('should create a user', async () => { + const userData = { username: 'test', email: 'test@example.com' }; + const result = await service.createUser(userData); + + expect(result).toBeDefined(); + expect(result.username).toBe(userData.username); + }); +}); +``` + +#### 4.4.2 集成测试规范 +```typescript +describe('User API', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/users (POST)', () => { + return request(app.getHttpServer()) + .post('/users') + .send({ username: 'test', email: 'test@example.com' }) + .expect(201) + .expect((res) => { + expect(res.body.data.username).toBe('test'); + }); + }); +}); +``` + +## 5. 质量保证 + +### 5.1 代码质量检查 + +#### 5.1.1 静态代码分析 +- **ESLint**:代码风格和潜在问题检查 +- **Prettier**:代码格式化 +- **SonarQube**:代码质量分析 +- **TypeScript**:类型检查 + +#### 5.1.2 代码审查流程 +1. 开发者提交Pull Request +2. 自动化检查(ESLint、测试、构建) +3. 同行代码审查 +4. 技术负责人最终审查 +5. 合并到主分支 + +### 5.2 测试策略 + +#### 5.2.1 测试金字塔 +- **单元测试**:70% - 测试单个函数和类 +- **集成测试**:20% - 测试模块间交互 +- **端到端测试**:10% - 测试完整业务流程 + +#### 5.2.2 测试覆盖率要求 +- **代码覆盖率**:>80% +- **分支覆盖率**:>70% +- **函数覆盖率**:>90% + +### 5.3 性能要求 + +#### 5.3.1 响应时间要求 +- **简单查询接口**:<100ms +- **复杂查询接口**:<500ms +- **文件上传接口**:<2s +- **支付接口**:<1s + +#### 5.3.2 并发性能要求 +- **并发用户数**:1000+ +- **QPS**:500+ +- **数据库连接池**:20-50 +- **内存使用**:<512MB + +## 6. 部署和运维 + +### 6.1 环境配置 + +#### 6.1.1 开发环境 +- **Node.js**:18.x +- **MySQL**:8.0 +- **Redis**:7.x +- **端口**:3000 + +#### 6.1.2 测试环境 +- **服务器**:2核4GB +- **数据库**:独立实例 +- **域名**:api-test.jiebanke.com +- **HTTPS**:Let's Encrypt + +#### 6.1.3 生产环境 +- **服务器**:4核8GB(可扩展) +- **数据库**:主从复制 +- **缓存**:Redis集群 +- **CDN**:阿里云CDN +- **监控**:Prometheus + Grafana + +### 6.2 CI/CD流程 + +#### 6.2.1 持续集成 +```yaml +# .github/workflows/ci.yml +name: CI +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18' + - run: npm ci + - run: npm run lint + - run: npm run test + - run: npm run build +``` + +#### 6.2.2 持续部署 +```yaml +# .github/workflows/cd.yml +name: CD +on: + push: + branches: [main] +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build Docker image + run: docker build -t jiebanke-backend . + - name: Deploy to server + run: | + docker stop jiebanke-backend || true + docker rm jiebanke-backend || true + docker run -d --name jiebanke-backend -p 3000:3000 jiebanke-backend +``` + +### 6.3 监控和日志 + +#### 6.3.1 应用监控 +- **健康检查**:/health端点 +- **性能监控**:响应时间、吞吐量 +- **错误监控**:错误率、异常堆栈 +- **业务监控**:用户活跃度、订单量 + +#### 6.3.2 日志管理 +```typescript +// 日志配置 +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + new winston.transports.File({ filename: 'error.log', level: 'error' }), + new winston.transports.File({ filename: 'combined.log' }) + ] +}); +``` + +## 7. 风险管理 + +### 7.1 技术风险 + +#### 7.1.1 性能风险 +- **风险**:高并发下性能下降 +- **应对**:负载测试、缓存优化、数据库优化 +- **监控**:响应时间、QPS、资源使用率 + +#### 7.1.2 安全风险 +- **风险**:数据泄露、攻击 +- **应对**:安全审计、加密存储、访问控制 +- **监控**:异常访问、安全事件 + +### 7.2 业务风险 + +#### 7.2.1 数据一致性风险 +- **风险**:分布式事务失败 +- **应对**:事务补偿、幂等性设计 +- **监控**:数据一致性检查 + +#### 7.2.2 第三方依赖风险 +- **风险**:微信API、支付接口不可用 +- **应对**:降级方案、重试机制 +- **监控**:第三方服务可用性 + +## 8. 总结 + +本开发文档详细规划了解班客后端系统的开发计划,包括: + +### 8.1 开发计划 +- **总工期**:65个工作日 +- **团队规模**:3-4名后端开发工程师 +- **关键里程碑**:基础架构、核心业务、辅助功能、系统优化 + +### 8.2 质量保证 +- **代码规范**:统一的编码标准和最佳实践 +- **测试策略**:完整的测试金字塔和覆盖率要求 +- **性能要求**:明确的性能指标和优化方案 + +### 8.3 风险控制 +- **技术风险**:性能、安全、稳定性 +- **业务风险**:数据一致性、第三方依赖 +- **应对措施**:监控、降级、补偿机制 + +通过严格按照本开发文档执行,可以确保后端系统的高质量交付和稳定运行。 \ No newline at end of file diff --git a/docs/后端架构文档.md b/docs/后端架构文档.md new file mode 100644 index 0000000..9aff673 --- /dev/null +++ b/docs/后端架构文档.md @@ -0,0 +1,1886 @@ +# 解班客后端架构文档 + +## 1. 后端架构概述 + +### 1.1 架构目标 +- **高性能**:支持高并发访问和快速响应 +- **高可用**:99.9%以上的服务可用性 +- **可扩展**:支持业务快速发展和功能扩展 +- **安全性**:全方位的安全防护机制 +- **可维护**:清晰的代码结构和完善的文档 + +### 1.2 技术选型 +| 技术栈 | 版本 | 选型理由 | +|--------|------|----------| +| Node.js | 18+ LTS | 高性能、生态丰富、开发效率高 | +| Express.js | 4.x | 成熟的Web框架,中间件丰富 | +| TypeScript | 5.x | 类型安全,提高代码质量 | +| MySQL | 8.0+ | 成熟稳定的关系型数据库 | +| Redis | 7.x | 高性能缓存和会话存储 | +| MongoDB | 6.x | 灵活的文档型数据库 | +| RabbitMQ | 3.11+ | 可靠的消息队列 | +| Elasticsearch | 8.x | 强大的搜索和分析引擎 | + +### 1.3 架构原则 +- **单一职责**:每个服务专注于特定的业务领域 +- **松耦合**:服务间通过标准接口通信 +- **高内聚**:相关功能聚合在同一服务内 +- **无状态**:服务设计为无状态,支持水平扩展 +- **容错性**:具备故障隔离和自动恢复能力 + +## 2. 系统架构设计 + +### 2.1 整体架构图 +```mermaid +graph TB + subgraph "客户端" + A1[微信小程序] + A2[管理后台] + A3[官方网站] + end + + subgraph "网关层" + B1[API Gateway] + B2[Load Balancer] + end + + subgraph "业务服务层" + C1[用户服务
User Service] + C2[活动服务
Activity Service] + C3[认领服务
Adoption Service] + C4[支付服务
Payment Service] + C5[消息服务
Message Service] + C6[文件服务
File Service] + C7[管理服务
Admin Service] + C8[通知服务
Notification Service] + end + + subgraph "数据层" + D1[MySQL
主数据库] + D2[MySQL
从数据库] + D3[Redis
缓存集群] + D4[MongoDB
文档数据库] + D5[Elasticsearch
搜索引擎] + end + + subgraph "消息队列" + E1[RabbitMQ
消息队列] + end + + subgraph "第三方服务" + F1[微信API] + F2[支付接口] + F3[短信服务] + F4[对象存储] + end + + A1 --> B1 + A2 --> B1 + A3 --> B1 + + B1 --> B2 + B2 --> C1 + B2 --> C2 + B2 --> C3 + B2 --> C4 + B2 --> C5 + B2 --> C6 + B2 --> C7 + B2 --> C8 + + C1 --> D1 + C2 --> D1 + C3 --> D1 + C4 --> D1 + C7 --> D1 + + C1 --> D2 + C2 --> D2 + C3 --> D2 + C4 --> D2 + C7 --> D2 + + C1 --> D3 + C2 --> D3 + C3 --> D3 + C4 --> D3 + C5 --> D3 + C8 --> D3 + + C6 --> D4 + C2 --> D5 + C3 --> D5 + + C2 --> E1 + C3 --> E1 + C4 --> E1 + C5 --> E1 + C8 --> E1 + + C1 --> F1 + C4 --> F2 + C8 --> F3 + C6 --> F4 +``` + +### 2.2 服务分层架构 +```mermaid +graph TB + subgraph "表现层 Presentation Layer" + A1[REST API] + A2[GraphQL API] + A3[WebSocket API] + end + + subgraph "业务逻辑层 Business Logic Layer" + B1[用户业务逻辑] + B2[活动业务逻辑] + B3[认领业务逻辑] + B4[支付业务逻辑] + B5[消息业务逻辑] + end + + subgraph "服务层 Service Layer" + C1[用户服务] + C2[活动服务] + C3[认领服务] + C4[支付服务] + C5[消息服务] + C6[通知服务] + end + + subgraph "数据访问层 Data Access Layer" + D1[MySQL DAO] + D2[Redis DAO] + D3[MongoDB DAO] + D4[ES DAO] + end + + subgraph "数据存储层 Data Storage Layer" + E1[MySQL数据库] + E2[Redis缓存] + E3[MongoDB文档库] + E4[Elasticsearch] + end + + A1 --> B1 + A1 --> B2 + A1 --> B3 + A1 --> B4 + A1 --> B5 + + B1 --> C1 + B2 --> C2 + B3 --> C3 + B4 --> C4 + B5 --> C5 + + C1 --> D1 + C2 --> D1 + C3 --> D1 + C4 --> D1 + C5 --> D2 + C6 --> D3 + + D1 --> E1 + D2 --> E2 + D3 --> E3 + D4 --> E4 +``` + +## 3. 核心服务设计 + +### 3.1 用户服务 (User Service) + +#### 3.1.1 服务职责 +- 用户注册、登录、认证 +- 用户资料管理 +- 权限验证和授权 +- 用户关系管理(关注、粉丝) +- 用户行为记录 + +#### 3.1.2 核心模块 +```mermaid +graph TB + A[用户服务] --> B[认证模块] + A --> C[用户管理模块] + A --> D[权限模块] + A --> E[关系模块] + A --> F[行为记录模块] + + B --> B1[微信登录] + B --> B2[JWT Token] + B --> B3[刷新Token] + + C --> C1[用户注册] + C --> C2[资料管理] + C --> C3[实名认证] + + D --> D1[角色管理] + D --> D2[权限验证] + D --> D3[资源控制] + + E --> E1[关注关系] + E --> E2[好友推荐] + E --> E3[社交图谱] + + F --> F1[登录记录] + F --> F2[操作日志] + F --> F3[行为分析] +``` + +#### 3.1.3 数据模型 +```sql +-- 用户基本信息表 +CREATE TABLE users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + openid VARCHAR(64) UNIQUE NOT NULL, + unionid VARCHAR(64), + phone VARCHAR(20), + nickname VARCHAR(50), + avatar VARCHAR(255), + gender TINYINT DEFAULT 0, + birthday DATE, + city VARCHAR(50), + bio TEXT, + status TINYINT DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_openid (openid), + INDEX idx_phone (phone), + INDEX idx_status (status) +); + +-- 用户认证信息表 +CREATE TABLE user_auths ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + real_name VARCHAR(50), + id_card VARCHAR(18), + auth_status TINYINT DEFAULT 0, + auth_time TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_user_id (user_id), + INDEX idx_auth_status (auth_status) +); + +-- 用户关系表 +CREATE TABLE user_relations ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + follower_id BIGINT NOT NULL, + following_id BIGINT NOT NULL, + status TINYINT DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (follower_id) REFERENCES users(id), + FOREIGN KEY (following_id) REFERENCES users(id), + UNIQUE KEY uk_relation (follower_id, following_id), + INDEX idx_follower (follower_id), + INDEX idx_following (following_id) +); +``` + +### 3.2 活动服务 (Activity Service) + +#### 3.2.1 服务职责 +- 活动发布和管理 +- 活动报名和参与 +- 活动搜索和推荐 +- 活动评价和反馈 +- 活动统计分析 + +#### 3.2.2 核心模块 +```mermaid +graph TB + A[活动服务] --> B[活动管理模块] + A --> C[报名管理模块] + A --> D[搜索推荐模块] + A --> E[评价模块] + A --> F[统计模块] + + B --> B1[活动发布] + B --> B2[活动编辑] + B --> B3[活动状态管理] + + C --> C1[报名申请] + C --> C2[报名审核] + C --> C3[参与者管理] + + D --> D1[活动搜索] + D --> D2[个性化推荐] + D --> D3[热门活动] + + E --> E1[活动评价] + E --> E2[用户反馈] + E --> E3[评分统计] + + F --> F1[活动数据统计] + F --> F2[用户行为分析] + F --> F3[运营报表] +``` + +#### 3.2.3 数据模型 +```sql +-- 活动信息表 +CREATE TABLE activities ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + creator_id BIGINT NOT NULL, + title VARCHAR(100) NOT NULL, + description TEXT, + category_id INT, + start_time TIMESTAMP NOT NULL, + end_time TIMESTAMP NOT NULL, + location VARCHAR(255), + latitude DECIMAL(10,8), + longitude DECIMAL(11,8), + max_participants INT DEFAULT 0, + current_participants INT DEFAULT 0, + fee_type TINYINT DEFAULT 0, -- 0:免费 1:AA制 2:付费 + fee_amount DECIMAL(10,2) DEFAULT 0, + status TINYINT DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (creator_id) REFERENCES users(id), + INDEX idx_creator (creator_id), + INDEX idx_category (category_id), + INDEX idx_start_time (start_time), + INDEX idx_location (latitude, longitude), + INDEX idx_status (status) +); + +-- 活动报名表 +CREATE TABLE activity_participants ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + activity_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + status TINYINT DEFAULT 0, -- 0:待审核 1:已通过 2:已拒绝 3:已取消 + apply_message TEXT, + apply_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + review_time TIMESTAMP NULL, + FOREIGN KEY (activity_id) REFERENCES activities(id), + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE KEY uk_participant (activity_id, user_id), + INDEX idx_activity (activity_id), + INDEX idx_user (user_id), + INDEX idx_status (status) +); + +-- 活动评价表 +CREATE TABLE activity_reviews ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + activity_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + rating TINYINT NOT NULL, + comment TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (activity_id) REFERENCES activities(id), + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE KEY uk_review (activity_id, user_id), + INDEX idx_activity (activity_id), + INDEX idx_rating (rating) +); +``` + +### 3.3 认领服务 (Adoption Service) + +#### 3.3.1 服务职责 +- 动物信息管理 +- 认领申请处理 +- 认领关系维护 +- 探访预约管理 +- 成长记录管理 + +#### 3.3.2 核心模块 +```mermaid +graph TB + A[认领服务] --> B[动物管理模块] + A --> C[认领管理模块] + A --> D[探访管理模块] + A --> E[成长记录模块] + A --> F[农场管理模块] + + B --> B1[动物信息] + B --> B2[动物分类] + B --> B3[动物状态] + + C --> C1[认领申请] + C --> C2[认领审核] + C --> C3[认领关系] + + D --> D1[探访预约] + D --> D2[探访记录] + D --> D3[探访路线] + + E --> E1[成长日记] + E --> E2[照片记录] + E --> E3[健康档案] + + F --> F1[农场信息] + F --> F2[农场服务] + F --> F3[农产品] +``` + +#### 3.3.3 数据模型 +```sql +-- 农场信息表 +CREATE TABLE farms ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(100) NOT NULL, + description TEXT, + address VARCHAR(255), + latitude DECIMAL(10,8), + longitude DECIMAL(11,8), + contact_phone VARCHAR(20), + contact_person VARCHAR(50), + images JSON, + status TINYINT DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_status (status), + INDEX idx_location (latitude, longitude) +); + +-- 动物信息表 +CREATE TABLE animals ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + farm_id BIGINT NOT NULL, + name VARCHAR(50), + species VARCHAR(50) NOT NULL, + breed VARCHAR(50), + gender TINYINT, + birth_date DATE, + description TEXT, + images JSON, + adoption_fee DECIMAL(10,2), + adoption_period INT, -- 认领周期(月) + status TINYINT DEFAULT 1, -- 1:可认领 2:已认领 3:不可认领 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (farm_id) REFERENCES farms(id), + INDEX idx_farm (farm_id), + INDEX idx_species (species), + INDEX idx_status (status) +); + +-- 认领关系表 +CREATE TABLE adoptions ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + animal_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + adoption_name VARCHAR(50), -- 用户给动物起的名字 + start_date DATE NOT NULL, + end_date DATE NOT NULL, + total_fee DECIMAL(10,2), + status TINYINT DEFAULT 1, -- 1:认领中 2:已到期 3:已取消 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (animal_id) REFERENCES animals(id), + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_animal (animal_id), + INDEX idx_user (user_id), + INDEX idx_status (status), + INDEX idx_date_range (start_date, end_date) +); + +-- 探访记录表 +CREATE TABLE visits ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + adoption_id BIGINT NOT NULL, + visit_date DATE NOT NULL, + visit_time TIME, + visitor_count INT DEFAULT 1, + notes TEXT, + photos JSON, + status TINYINT DEFAULT 1, -- 1:已预约 2:已完成 3:已取消 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (adoption_id) REFERENCES adoptions(id), + INDEX idx_adoption (adoption_id), + INDEX idx_visit_date (visit_date), + INDEX idx_status (status) +); +``` + +### 3.4 支付服务 (Payment Service) + +#### 3.4.1 服务职责 +- 支付订单管理 +- 支付流程处理 +- 退款处理 +- 账户余额管理 +- 交易记录查询 + +#### 3.4.2 核心模块 +```mermaid +graph TB + A[支付服务] --> B[订单管理模块] + A --> C[支付处理模块] + A --> D[退款模块] + A --> E[账户模块] + A --> F[对账模块] + + B --> B1[订单创建] + B --> B2[订单查询] + B --> B3[订单状态管理] + + C --> C1[微信支付] + C --> C2[支付宝支付] + C --> C3[余额支付] + + D --> D1[退款申请] + D --> D2[退款处理] + D --> D3[退款查询] + + E --> E1[余额管理] + E --> E2[充值提现] + E --> E3[交易记录] + + F --> F1[支付对账] + F --> F2[财务报表] + F --> F3[异常处理] +``` + +#### 3.4.3 数据模型 +```sql +-- 支付订单表 +CREATE TABLE payment_orders ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_no VARCHAR(32) UNIQUE NOT NULL, + user_id BIGINT NOT NULL, + business_type TINYINT NOT NULL, -- 1:活动支付 2:认领支付 3:商品支付 + business_id BIGINT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + currency VARCHAR(3) DEFAULT 'CNY', + payment_method TINYINT, -- 1:微信 2:支付宝 3:余额 + payment_channel VARCHAR(20), + status TINYINT DEFAULT 0, -- 0:待支付 1:支付中 2:支付成功 3:支付失败 4:已取消 + paid_at TIMESTAMP NULL, + expired_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_order_no (order_no), + INDEX idx_user (user_id), + INDEX idx_business (business_type, business_id), + INDEX idx_status (status), + INDEX idx_created_at (created_at) +); + +-- 支付记录表 +CREATE TABLE payment_records ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + transaction_id VARCHAR(64), + payment_method TINYINT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + status TINYINT DEFAULT 0, -- 0:处理中 1:成功 2:失败 + response_data JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES payment_orders(id), + INDEX idx_order (order_id), + INDEX idx_transaction (transaction_id), + INDEX idx_status (status) +); + +-- 用户账户表 +CREATE TABLE user_accounts ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT UNIQUE NOT NULL, + balance DECIMAL(10,2) DEFAULT 0.00, + frozen_balance DECIMAL(10,2) DEFAULT 0.00, + total_income DECIMAL(10,2) DEFAULT 0.00, + total_expense DECIMAL(10,2) DEFAULT 0.00, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_user (user_id) +); + +-- 账户流水表 +CREATE TABLE account_transactions ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + type TINYINT NOT NULL, -- 1:收入 2:支出 3:冻结 4:解冻 + amount DECIMAL(10,2) NOT NULL, + balance_after DECIMAL(10,2) NOT NULL, + business_type TINYINT, + business_id BIGINT, + description VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_user (user_id), + INDEX idx_type (type), + INDEX idx_business (business_type, business_id), + INDEX idx_created_at (created_at) +); +``` + +### 3.5 消息服务 (Message Service) + +#### 3.5.1 服务职责 +- 即时消息处理 +- 群聊管理 +- 消息存储和查询 +- 消息推送 +- 敏感词过滤 + +#### 3.5.2 核心模块 +```mermaid +graph TB + A[消息服务] --> B[即时消息模块] + A --> C[群聊管理模块] + A --> D[消息存储模块] + A --> E[推送模块] + A --> F[内容审核模块] + + B --> B1[私聊消息] + B --> B2[消息发送] + B --> B3[消息接收] + + C --> C1[群组创建] + C --> C2[成员管理] + C --> C3[群消息] + + D --> D1[消息持久化] + D --> D2[消息查询] + D --> D3[消息同步] + + E --> E1[实时推送] + E --> E2[离线推送] + E --> E3[推送统计] + + F --> F1[敏感词检测] + F --> F2[图片审核] + F --> F3[内容过滤] +``` + +### 3.6 文件服务 (File Service) + +#### 3.6.1 服务职责 +- 文件上传处理 +- 图片压缩和处理 +- 文件存储管理 +- CDN加速 +- 文件安全检查 + +#### 3.6.2 核心模块 +```mermaid +graph TB + A[文件服务] --> B[上传处理模块] + A --> C[图片处理模块] + A --> D[存储管理模块] + A --> E[CDN模块] + A --> F[安全检查模块] + + B --> B1[文件上传] + B --> B2[分片上传] + B --> B3[断点续传] + + C --> C1[图片压缩] + C --> C2[格式转换] + C --> C3[水印添加] + + D --> D1[本地存储] + D --> D2[云存储] + D --> D3[存储策略] + + E --> E1[CDN分发] + E --> E2[缓存管理] + E --> E3[访问优化] + + F --> F1[病毒扫描] + F --> F2[内容检测] + F --> F3[访问控制] +``` + +## 4. 数据架构设计 + +### 4.1 数据库架构 + +#### 4.1.1 MySQL主从架构 +```mermaid +graph TB + A[应用服务] --> B[数据库代理] + B --> C[MySQL主库] + B --> D[MySQL从库1] + B --> E[MySQL从库2] + + C --> F[写操作] + D --> G[读操作] + E --> G + + C --> H[主从同步] + H --> D + H --> E + + I[备份服务] --> C + I --> J[备份存储] +``` + +#### 4.1.2 数据分库分表策略 +```mermaid +graph TB + A[业务数据] --> B[用户相关数据] + A --> C[活动相关数据] + A --> D[认领相关数据] + A --> E[支付相关数据] + + B --> B1[用户库1
user_id % 4 = 0] + B --> B2[用户库2
user_id % 4 = 1] + B --> B3[用户库3
user_id % 4 = 2] + B --> B4[用户库4
user_id % 4 = 3] + + C --> C1[活动库1
按时间分表] + C --> C2[活动库2
按时间分表] + + D --> D1[认领库1
按农场分表] + D --> D2[认领库2
按农场分表] + + E --> E1[支付库1
按时间分表] + E --> E2[支付库2
按时间分表] +``` + +### 4.2 缓存架构 + +#### 4.2.1 Redis集群架构 +```mermaid +graph TB + A[应用服务] --> B[Redis代理] + B --> C[Redis主节点1] + B --> D[Redis主节点2] + B --> E[Redis主节点3] + + C --> F[Redis从节点1] + D --> G[Redis从节点2] + E --> H[Redis从节点3] + + I[哨兵节点1] --> C + I --> D + I --> E + + J[哨兵节点2] --> C + J --> D + J --> E + + K[哨兵节点3] --> C + K --> D + K --> E +``` + +#### 4.2.2 缓存策略 +- **用户信息缓存**:用户基本信息,过期时间30分钟 +- **活动列表缓存**:热门活动列表,过期时间10分钟 +- **搜索结果缓存**:搜索结果,过期时间5分钟 +- **计数器缓存**:点赞数、评论数等,实时更新 +- **会话缓存**:用户登录状态,过期时间7天 + +### 4.3 搜索架构 + +#### 4.3.1 Elasticsearch集群 +```mermaid +graph TB + A[应用服务] --> B[ES负载均衡] + B --> C[ES主节点1] + B --> D[ES主节点2] + B --> E[ES主节点3] + + C --> F[ES数据节点1] + C --> G[ES数据节点2] + D --> H[ES数据节点3] + D --> I[ES数据节点4] + E --> J[ES数据节点5] + E --> K[ES数据节点6] + + L[数据同步服务] --> M[MySQL] + L --> B +``` + +#### 4.3.2 索引设计 +```json +{ + "activities": { + "mappings": { + "properties": { + "id": {"type": "long"}, + "title": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart" + }, + "description": { + "type": "text", + "analyzer": "ik_max_word" + }, + "category": {"type": "keyword"}, + "location": {"type": "geo_point"}, + "start_time": {"type": "date"}, + "creator_id": {"type": "long"}, + "status": {"type": "integer"}, + "tags": {"type": "keyword"} + } + } + } +} +``` + +## 5. API设计规范 + +### 5.1 RESTful API设计 + +#### 5.1.1 URL设计规范 +``` +GET /api/v1/users # 获取用户列表 +GET /api/v1/users/{id} # 获取指定用户 +POST /api/v1/users # 创建用户 +PUT /api/v1/users/{id} # 更新用户 +DELETE /api/v1/users/{id} # 删除用户 + +GET /api/v1/activities # 获取活动列表 +POST /api/v1/activities # 创建活动 +GET /api/v1/activities/{id} # 获取活动详情 +PUT /api/v1/activities/{id} # 更新活动 +DELETE /api/v1/activities/{id} # 删除活动 + +POST /api/v1/activities/{id}/join # 参加活动 +DELETE /api/v1/activities/{id}/leave # 退出活动 +``` + +#### 5.1.2 请求响应格式 +```json +// 请求格式 +{ + "data": { + "title": "周末爬山活动", + "description": "一起去爬山,享受自然风光", + "start_time": "2024-01-20T09:00:00Z", + "location": "香山公园" + } +} + +// 成功响应格式 +{ + "code": 200, + "message": "success", + "data": { + "id": 12345, + "title": "周末爬山活动", + "description": "一起去爬山,享受自然风光", + "start_time": "2024-01-20T09:00:00Z", + "location": "香山公园", + "created_at": "2024-01-15T10:30:00Z" + }, + "timestamp": 1705312200 +} + +// 错误响应格式 +{ + "code": 400, + "message": "参数错误", + "error": { + "field": "start_time", + "reason": "开始时间不能早于当前时间" + }, + "timestamp": 1705312200 +} +``` + +#### 5.1.3 状态码规范 +| 状态码 | 说明 | +|--------|------| +| 200 | 请求成功 | +| 201 | 创建成功 | +| 400 | 请求参数错误 | +| 401 | 未授权 | +| 403 | 禁止访问 | +| 404 | 资源不存在 | +| 409 | 资源冲突 | +| 422 | 参数验证失败 | +| 500 | 服务器内部错误 | +| 502 | 网关错误 | +| 503 | 服务不可用 | + +### 5.2 认证授权机制 + +#### 5.2.1 JWT Token设计 +```json +// JWT Header +{ + "alg": "HS256", + "typ": "JWT" +} + +// JWT Payload +{ + "user_id": 12345, + "openid": "wx_openid_123", + "role": "user", + "permissions": ["activity:read", "activity:create"], + "iat": 1705312200, + "exp": 1705398600 +} +``` + +#### 5.2.2 权限控制 +```typescript +// 权限定义 +enum Permission { + // 用户权限 + USER_READ = 'user:read', + USER_WRITE = 'user:write', + + // 活动权限 + ACTIVITY_READ = 'activity:read', + ACTIVITY_WRITE = 'activity:write', + ACTIVITY_DELETE = 'activity:delete', + + // 认领权限 + ADOPTION_READ = 'adoption:read', + ADOPTION_WRITE = 'adoption:write', + + // 管理权限 + ADMIN_USER = 'admin:user', + ADMIN_ACTIVITY = 'admin:activity', + ADMIN_SYSTEM = 'admin:system' +} + +// 角色权限映射 +const rolePermissions = { + user: [ + Permission.USER_READ, + Permission.ACTIVITY_READ, + Permission.ACTIVITY_WRITE, + Permission.ADOPTION_READ, + Permission.ADOPTION_WRITE + ], + admin: [ + ...rolePermissions.user, + Permission.ADMIN_USER, + Permission.ADMIN_ACTIVITY, + Permission.ADMIN_SYSTEM + ] +}; +``` + +## 6. 消息队列架构 + +### 6.1 RabbitMQ架构设计 + +#### 6.1.1 集群架构 +```mermaid +graph TB + A[生产者服务] --> B[RabbitMQ集群] + + subgraph "RabbitMQ集群" + C[节点1
主节点] + D[节点2
从节点] + E[节点3
从节点] + + C --> F[队列镜像] + D --> F + E --> F + end + + B --> G[消费者服务1] + B --> H[消费者服务2] + B --> I[消费者服务3] + + J[HAProxy] --> C + J --> D + J --> E +``` + +#### 6.1.2 队列设计 +```typescript +// 队列配置 +const queueConfig = { + // 用户相关队列 + 'user.register': { + durable: true, + exclusive: false, + autoDelete: false, + arguments: { + 'x-message-ttl': 300000, // 5分钟TTL + 'x-dead-letter-exchange': 'dlx.user' + } + }, + + // 活动相关队列 + 'activity.created': { + durable: true, + exclusive: false, + autoDelete: false, + arguments: { + 'x-message-ttl': 600000, // 10分钟TTL + 'x-dead-letter-exchange': 'dlx.activity' + } + }, + + // 支付相关队列 + 'payment.success': { + durable: true, + exclusive: false, + autoDelete: false, + arguments: { + 'x-message-ttl': 1800000, // 30分钟TTL + 'x-dead-letter-exchange': 'dlx.payment' + } + }, + + // 通知相关队列 + 'notification.push': { + durable: true, + exclusive: false, + autoDelete: false, + arguments: { + 'x-message-ttl': 60000, // 1分钟TTL + 'x-dead-letter-exchange': 'dlx.notification' + } + } +}; +``` + +### 6.2 消息处理模式 + +#### 6.2.1 发布订阅模式 +```typescript +// 事件发布 +class EventPublisher { + async publishUserRegistered(userId: number, userData: any) { + const message = { + eventType: 'user.registered', + userId, + userData, + timestamp: Date.now() + }; + + await this.publish('user.events', message); + } + + async publishActivityCreated(activityId: number, activityData: any) { + const message = { + eventType: 'activity.created', + activityId, + activityData, + timestamp: Date.now() + }; + + await this.publish('activity.events', message); + } +} + +// 事件消费 +class EventConsumer { + async handleUserRegistered(message: any) { + // 发送欢迎邮件 + await this.sendWelcomeEmail(message.userId); + + // 初始化用户数据 + await this.initUserData(message.userId); + + // 推送注册成功通知 + await this.pushRegistrationNotification(message.userId); + } + + async handleActivityCreated(message: any) { + // 更新搜索索引 + await this.updateSearchIndex(message.activityId); + + // 推荐给相关用户 + await this.recommendToUsers(message.activityId); + + // 发送创建成功通知 + await this.pushCreationNotification(message.activityId); + } +} +``` + +## 7. 监控和日志 + +### 7.1 监控架构 + +#### 7.1.1 监控体系 +```mermaid +graph TB + A[应用服务] --> B[Metrics收集] + A --> C[日志收集] + A --> D[链路追踪] + + B --> E[Prometheus] + C --> F[ELK Stack] + D --> G[Jaeger] + + E --> H[Grafana] + F --> H + G --> H + + H --> I[告警系统] + I --> J[短信告警] + I --> K[邮件告警] + I --> L[微信告警] +``` + +#### 7.1.2 关键指标监控 +```typescript +// 业务指标 +const businessMetrics = { + // 用户指标 + userRegistrations: 'counter', // 用户注册数 + activeUsers: 'gauge', // 活跃用户数 + userRetention: 'gauge', // 用户留存率 + + // 活动指标 + activitiesCreated: 'counter', // 活动创建数 + activitiesJoined: 'counter', // 活动参与数 + activityCompletionRate: 'gauge', // 活动完成率 + + // 认领指标 + adoptionsCreated: 'counter', // 认领创建数 + adoptionRevenue: 'counter', // 认领收入 + visitCount: 'counter', // 探访次数 + + // 支付指标 + paymentSuccess: 'counter', // 支付成功数 + paymentFailure: 'counter', // 支付失败数 + paymentAmount: 'counter' // 支付金额 +}; + +// 技术指标 +const technicalMetrics = { + // 性能指标 + responseTime: 'histogram', // 响应时间 + throughput: 'counter', // 吞吐量 + errorRate: 'gauge', // 错误率 + + // 系统指标 + cpuUsage: 'gauge', // CPU使用率 + memoryUsage: 'gauge', // 内存使用率 + diskUsage: 'gauge', // 磁盘使用率 + networkIO: 'gauge', // 网络IO + + // 数据库指标 + dbConnections: 'gauge', // 数据库连接数 + dbQueryTime: 'histogram', // 查询时间 + dbSlowQueries: 'counter', // 慢查询数 + + // 缓存指标 + cacheHitRate: 'gauge', // 缓存命中率 + cacheMemoryUsage: 'gauge', // 缓存内存使用 + cacheConnections: 'gauge' // 缓存连接数 +}; +``` + +### 7.2 日志架构 + +#### 7.2.1 日志分类 +```typescript +// 日志级别 +enum LogLevel { + ERROR = 'error', + WARN = 'warn', + INFO = 'info', + DEBUG = 'debug' +} + +// 日志类型 +enum LogType { + ACCESS = 'access', // 访问日志 + ERROR = 'error', // 错误日志 + BUSINESS = 'business', // 业务日志 + SECURITY = 'security', // 安全日志 + PERFORMANCE = 'performance' // 性能日志 +} + +// 日志格式 +interface LogEntry { + timestamp: string; + level: LogLevel; + type: LogType; + service: string; + traceId: string; + userId?: number; + message: string; + data?: any; + error?: { + name: string; + message: string; + stack: string; + }; +} +``` + +#### 7.2.2 日志收集流程 +```mermaid +graph LR + A[应用服务] --> B[日志文件] + B --> C[Filebeat] + C --> D[Logstash] + D --> E[Elasticsearch] + E --> F[Kibana] + + G[实时日志] --> H[Fluentd] + H --> D + + I[错误日志] --> J[告警系统] + J --> K[通知服务] +``` + +## 8. 安全架构 + +### 8.1 安全防护体系 + +#### 8.1.1 多层安全防护 +```mermaid +graph TB + A[用户请求] --> B[CDN/WAF] + B --> C[负载均衡器] + C --> D[API网关] + D --> E[应用服务] + E --> F[数据库] + + G[DDoS防护] --> B + H[SQL注入防护] --> D + I[XSS防护] --> D + J[CSRF防护] --> D + K[数据加密] --> F + L[访问控制] --> F +``` + +#### 8.1.2 安全措施 +```typescript +// 安全配置 +const securityConfig = { + // JWT配置 + jwt: { + secret: process.env.JWT_SECRET, + expiresIn: '7d', + refreshExpiresIn: '30d', + algorithm: 'HS256' + }, + + // 密码策略 + password: { + minLength: 8, + requireUppercase: true, + requireLowercase: true, + requireNumbers: true, + requireSymbols: true, + maxAttempts: 5, + lockoutDuration: 300000 // 5分钟 + }, + + // 接口限流 + rateLimit: { + windowMs: 60000, // 1分钟 + max: 100, // 最大请求数 + skipSuccessfulRequests: false, + skipFailedRequests: false + }, + + // CORS配置 + cors: { + origin: ['https://jiebanke.com', 'https://admin.jiebanke.com'], + credentials: true, + optionsSuccessStatus: 200 + }, + + // 数据加密 + encryption: { + algorithm: 'aes-256-gcm', + keyLength: 32, + ivLength: 16, + tagLength: 16 + } +}; +``` + +### 8.2 数据安全 + +#### 8.2.1 敏感数据加密 +```typescript +// 数据加密服务 +class EncryptionService { + // 加密敏感数据 + async encryptSensitiveData(data: string): Promise { + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipher('aes-256-gcm', key, iv); + + let encrypted = cipher.update(data, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + const tag = cipher.getAuthTag(); + + return JSON.stringify({ + encrypted, + key: key.toString('hex'), + iv: iv.toString('hex'), + tag: tag.toString('hex') + }); + } + + // 解密敏感数据 + async decryptSensitiveData(encryptedData: string): Promise { + const { encrypted, key, iv, tag } = JSON.parse(encryptedData); + + const decipher = crypto.createDecipher('aes-256-gcm', + Buffer.from(key, 'hex'), + Buffer.from(iv, 'hex') + ); + + decipher.setAuthTag(Buffer.from(tag, 'hex')); + + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + } +} +``` + +## 9. 性能优化 + +### 9.1 数据库优化 + +#### 9.1.1 索引优化策略 +```sql +-- 用户表索引优化 +CREATE INDEX idx_users_openid ON users(openid); +CREATE INDEX idx_users_phone ON users(phone); +CREATE INDEX idx_users_status_created ON users(status, created_at); + +-- 活动表索引优化 +CREATE INDEX idx_activities_creator_status ON activities(creator_id, status); +CREATE INDEX idx_activities_category_time ON activities(category_id, start_time); +CREATE INDEX idx_activities_location ON activities(latitude, longitude); +CREATE INDEX idx_activities_status_time ON activities(status, start_time); + +-- 复合索引优化 +CREATE INDEX idx_activity_participants_complex +ON activity_participants(activity_id, status, apply_time); + +-- 覆盖索引优化 +CREATE INDEX idx_activities_list_cover +ON activities(status, start_time, id, title, creator_id, current_participants); +``` + +#### 9.1.2 查询优化 +```typescript +// 分页查询优化 +class ActivityService { + // 使用游标分页替代OFFSET + async getActivitiesByCursor(cursor?: number, limit: number = 20) { + const whereClause = cursor ? 'WHERE id < ?' : ''; + const params = cursor ? [cursor, limit] : [limit]; + + const sql = ` + SELECT id, title, creator_id, start_time, current_participants + FROM activities + ${whereClause} + ORDER BY id DESC + LIMIT ? + `; + + return await this.db.query(sql, params); + } + + // 批量查询优化 + async getActivitiesWithCreators(activityIds: number[]) { + const sql = ` + SELECT + a.id, a.title, a.start_time, + u.id as creator_id, u.nickname, u.avatar + FROM activities a + JOIN users u ON a.creator_id = u.id + WHERE a.id IN (${activityIds.map(() => '?').join(',')}) + `; + + return await this.db.query(sql, activityIds); + } +} +``` + +### 9.2 缓存优化 + +#### 9.2.1 多级缓存策略 +```typescript +// 缓存服务 +class CacheService { + private l1Cache: Map = new Map(); // 内存缓存 + private redis: Redis; // Redis缓存 + + async get(key: string): Promise { + // L1缓存查询 + if (this.l1Cache.has(key)) { + return this.l1Cache.get(key); + } + + // L2缓存查询 + const value = await this.redis.get(key); + if (value) { + const parsed = JSON.parse(value); + // 回写L1缓存 + this.l1Cache.set(key, parsed); + return parsed; + } + + return null; + } + + async set(key: string, value: any, ttl: number = 300): Promise { + // 设置L1缓存 + this.l1Cache.set(key, value); + + // 设置L2缓存 + await this.redis.setex(key, ttl, JSON.stringify(value)); + } + + // 缓存预热 + async warmupCache(): Promise { + // 预热热门活动 + const hotActivities = await this.getHotActivities(); + for (const activity of hotActivities) { + await this.set(`activity:${activity.id}`, activity, 600); + } + + // 预热用户信息 + const activeUsers = await this.getActiveUsers(); + for (const user of activeUsers) { + await this.set(`user:${user.id}`, user, 1800); + } + } +} +``` + +### 9.3 异步处理优化 + +#### 9.3.1 任务队列优化 +```typescript +// 任务队列服务 +class TaskQueueService { + private queues: Map = new Map(); + + constructor() { + this.initQueues(); + } + + private initQueues(): void { + // 高优先级队列 + this.queues.set('high', new Queue('high-priority', { + redis: redisConfig, + defaultJobOptions: { + removeOnComplete: 100, + removeOnFail: 50, + attempts: 3, + backoff: 'exponential' + } + })); + + // 普通优先级队列 + this.queues.set('normal', new Queue('normal-priority', { + redis: redisConfig, + defaultJobOptions: { + removeOnComplete: 50, + removeOnFail: 25, + attempts: 2, + backoff: 'fixed' + } + })); + + // 低优先级队列 + this.queues.set('low', new Queue('low-priority', { + redis: redisConfig, + defaultJobOptions: { + removeOnComplete: 20, + removeOnFail: 10, + attempts: 1 + } + })); + } + + async addTask(priority: string, taskType: string, data: any): Promise { + const queue = this.queues.get(priority); + if (!queue) { + throw new Error(`Queue ${priority} not found`); + } + + await queue.add(taskType, data, { + priority: this.getPriorityValue(priority), + delay: this.getDelayTime(taskType) + }); + } + + private getPriorityValue(priority: string): number { + const priorities = { high: 10, normal: 5, low: 1 }; + return priorities[priority] || 1; + } + + private getDelayTime(taskType: string): number { + const delays = { + 'send-email': 0, + 'update-search-index': 5000, + 'generate-report': 10000 + }; + return delays[taskType] || 0; + } +} +``` + +## 10. 部署和运维 + +### 10.1 Docker容器化 + +#### 10.1.1 Dockerfile优化 +```dockerfile +# 多阶段构建 +FROM node:18-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production && npm cache clean --force + +COPY . . +RUN npm run build + +# 生产镜像 +FROM node:18-alpine AS production + +# 创建非root用户 +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nodejs -u 1001 + +WORKDIR /app + +# 复制构建产物 +COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist +COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./ + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +USER nodejs + +EXPOSE 3000 + +CMD ["node", "dist/index.js"] +``` + +#### 10.1.2 Docker Compose配置 +```yaml +version: '3.8' + +services: + app: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DB_HOST=mysql + - REDIS_HOST=redis + depends_on: + - mysql + - redis + restart: unless-stopped + + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: jiebanke + volumes: + - mysql_data:/var/lib/mysql + - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql + restart: unless-stopped + + redis: + image: redis:7-alpine + command: redis-server --appendonly yes + volumes: + - redis_data:/data + restart: unless-stopped + + nginx: + image: nginx:alpine + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - ./ssl:/etc/nginx/ssl + depends_on: + - app + restart: unless-stopped + +volumes: + mysql_data: + redis_data: +``` + +### 10.2 Kubernetes部署 + +#### 10.2.1 应用部署配置 +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jiebanke-backend + labels: + app: jiebanke-backend +spec: + replicas: 3 + selector: + matchLabels: + app: jiebanke-backend + template: + metadata: + labels: + app: jiebanke-backend + spec: + containers: + - name: backend + image: jiebanke/backend:latest + ports: + - containerPort: 3000 + env: + - name: NODE_ENV + value: "production" + - name: DB_HOST + valueFrom: + secretKeyRef: + name: db-secret + key: host + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: db-secret + key: password + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ready + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + +--- +apiVersion: v1 +kind: Service +metadata: + name: jiebanke-backend-service +spec: + selector: + app: jiebanke-backend + ports: + - protocol: TCP + port: 80 + targetPort: 3000 + type: ClusterIP + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jiebanke-backend-ingress + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + tls: + - hosts: + - api.jiebanke.com + secretName: jiebanke-tls + rules: + - host: api.jiebanke.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: jiebanke-backend-service + port: + number: 80 +``` + +### 10.3 CI/CD流水线 + +#### 10.3.1 GitHub Actions配置 +```yaml +name: Backend CI/CD + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: test + MYSQL_DATABASE: test + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + redis: + image: redis:7-alpine + options: >- + --health-cmd="redis-cli ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linting + run: npm run lint + + - name: Run tests + run: npm run test:coverage + env: + DB_HOST: localhost + DB_PORT: 3306 + DB_USER: root + DB_PASSWORD: test + DB_NAME: test + REDIS_HOST: localhost + REDIS_PORT: 6379 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + + build-and-deploy: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Build Docker image + run: | + docker build -t jiebanke/backend:${{ github.sha }} . + docker tag jiebanke/backend:${{ github.sha }} jiebanke/backend:latest + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Push Docker image + run: | + docker push jiebanke/backend:${{ github.sha }} + docker push jiebanke/backend:latest + + - name: Deploy to Kubernetes + uses: azure/k8s-deploy@v1 + with: + manifests: | + k8s/deployment.yaml + k8s/service.yaml + k8s/ingress.yaml + images: | + jiebanke/backend:${{ github.sha }} + kubectl-version: 'latest' +``` + +## 11. 总结 + +### 11.1 架构优势 + +#### 11.1.1 技术优势 +- **现代化技术栈**:采用Node.js + TypeScript,开发效率高 +- **微服务架构**:服务解耦,独立部署和扩展 +- **容器化部署**:Docker + Kubernetes,运维便捷 +- **多数据库支持**:MySQL + Redis + MongoDB + ES,满足不同场景需求 +- **消息队列**:RabbitMQ异步处理,提高系统性能 + +#### 11.1.2 性能优势 +- **多级缓存**:内存缓存 + Redis缓存,提高响应速度 +- **读写分离**:MySQL主从架构,提高数据库性能 +- **分库分表**:支持大数据量和高并发 +- **CDN加速**:静态资源CDN分发,提高访问速度 +- **异步处理**:消息队列异步处理,提高系统吞吐量 + +#### 11.1.3 安全优势 +- **多层防护**:WAF + API网关 + 应用层多重安全防护 +- **数据加密**:敏感数据加密存储和传输 +- **权限控制**:RBAC权限模型,细粒度权限控制 +- **安全监控**:实时安全监控和告警 +- **合规性**:符合数据保护和隐私法规要求 + +### 11.2 扩展性设计 + +#### 11.2.1 水平扩展 +- **无状态服务**:支持服务实例水平扩展 +- **数据库扩展**:支持读库扩展和分库分表 +- **缓存扩展**:Redis集群支持水平扩展 +- **消息队列扩展**:RabbitMQ集群支持扩展 + +#### 11.2.2 垂直扩展 +- **功能模块化**:新功能可独立开发和部署 +- **插件化架构**:支持功能插件化扩展 +- **API版本管理**:支持API平滑升级 +- **配置化管理**:业务规则配置化,灵活调整 + +### 11.3 运维保障 + +#### 11.3.1 监控告警 +- **全方位监控**:业务指标 + 技术指标 + 系统指标 +- **实时告警**:多渠道告警通知 +- **可视化面板**:Grafana可视化监控面板 +- **日志分析**:ELK日志分析和查询 + +#### 11.3.2 高可用保障 +- **服务冗余**:多实例部署,故障自动切换 +- **数据备份**:定期数据备份和恢复测试 +- **灾备方案**:异地灾备和快速恢复 +- **故障演练**:定期故障演练和应急响应 + +### 11.4 持续改进 + +#### 11.4.1 技术演进 +- **云原生**:向云原生架构演进 +- **服务网格**:引入Istio服务网格 +- **AI集成**:集成机器学习和AI能力 +- **边缘计算**:支持边缘计算场景 + +#### 11.4.2 性能优化 +- **持续优化**:基于监控数据持续优化性能 +- **新技术引入**:关注新技术,适时引入 +- **架构重构**:根据业务发展适时重构 +- **最佳实践**:总结和推广最佳实践 + +本后端架构文档为解班客项目提供了完整的技术架构指导,涵盖了从服务设计到部署运维的各个方面。通过合理的架构设计和技术选型,确保系统的高性能、高可用、高安全和高扩展性,为业务发展提供坚实的技术保障。 \ No newline at end of file diff --git a/docs/后端管理开发文档.md b/docs/后端管理开发文档.md new file mode 100644 index 0000000..5f11392 --- /dev/null +++ b/docs/后端管理开发文档.md @@ -0,0 +1,1655 @@ +# 后端管理开发文档 + +## 1. 项目概述 + +### 1.1 项目简介 +解班客后端管理系统是一个基于Node.js的企业级后端服务,为管理后台、小程序和官网提供统一的API服务和数据管理功能。 + +### 1.2 技术栈 +- **运行环境**:Node.js 18.x +- **开发框架**:Express.js 4.x +- **开发语言**:TypeScript 5.x +- **数据库**:MySQL 8.0 + Redis 7.x +- **ORM框架**:TypeORM 0.3.x +- **认证授权**:JWT + RBAC +- **文件存储**:阿里云OSS +- **消息队列**:Redis + Bull +- **日志系统**:Winston +- **API文档**:Swagger/OpenAPI +- **测试框架**:Jest + Supertest +- **代码规范**:ESLint + Prettier + +### 1.3 项目结构 +``` +backend/ +├── src/ +│ ├── controllers/ # 控制器 +│ │ ├── admin/ # 管理后台控制器 +│ │ ├── app/ # 小程序控制器 +│ │ └── common/ # 通用控制器 +│ ├── services/ # 业务服务层 +│ │ ├── user/ # 用户服务 +│ │ ├── travel/ # 旅行服务 +│ │ ├── animal/ # 动物服务 +│ │ ├── order/ # 订单服务 +│ │ └── common/ # 通用服务 +│ ├── models/ # 数据模型 +│ │ ├── entities/ # 实体定义 +│ │ ├── dto/ # 数据传输对象 +│ │ └── vo/ # 视图对象 +│ ├── middleware/ # 中间件 +│ │ ├── auth/ # 认证中间件 +│ │ ├── validation/ # 验证中间件 +│ │ └── common/ # 通用中间件 +│ ├── utils/ # 工具函数 +│ │ ├── database/ # 数据库工具 +│ │ ├── cache/ # 缓存工具 +│ │ ├── file/ # 文件处理 +│ │ └── common/ # 通用工具 +│ ├── config/ # 配置文件 +│ │ ├── database.ts # 数据库配置 +│ │ ├── redis.ts # Redis配置 +│ │ └── app.ts # 应用配置 +│ ├── routes/ # 路由定义 +│ │ ├── admin/ # 管理后台路由 +│ │ ├── app/ # 小程序路由 +│ │ └── common/ # 通用路由 +│ ├── jobs/ # 定时任务 +│ ├── migrations/ # 数据库迁移 +│ ├── seeds/ # 数据种子 +│ ├── app.ts # 应用入口 +│ └── server.ts # 服务器启动 +├── tests/ # 测试文件 +│ ├── unit/ # 单元测试 +│ ├── integration/ # 集成测试 +│ └── e2e/ # 端到端测试 +├── docs/ # 文档 +├── scripts/ # 脚本文件 +├── .env.example # 环境变量示例 +├── package.json +├── tsconfig.json +├── jest.config.js +└── README.md +``` + +## 2. 开发环境搭建 + +### 2.1 环境要求 +- Node.js >= 18.0.0 +- npm >= 9.0.0 或 yarn >= 1.22.0 +- MySQL >= 8.0.0 +- Redis >= 7.0.0 +- Git >= 2.0.0 + +### 2.2 环境搭建步骤 + +#### 2.2.1 克隆项目 +```bash +git clone https://github.com/jiebanke/backend.git +cd backend +``` + +#### 2.2.2 安装依赖 +```bash +npm install +# 或 +yarn install +``` + +#### 2.2.3 配置环境变量 +```bash +# 复制环境配置文件 +cp .env.example .env.development +cp .env.example .env.production + +# 编辑配置文件 +vim .env.development +``` + +#### 2.2.4 数据库初始化 +```bash +# 创建数据库 +npm run db:create + +# 运行迁移 +npm run migration:run + +# 运行种子数据 +npm run seed:run +``` + +#### 2.2.5 启动开发服务器 +```bash +npm run dev +# 或 +yarn dev +``` + +### 2.3 开发工具配置 + +#### 2.3.1 VSCode配置 +推荐安装以下插件: +- TypeScript Importer +- ESLint +- Prettier +- REST Client +- MySQL +- Redis + +#### 2.3.2 数据库工具 +- MySQL Workbench +- Navicat +- DBeaver +- Redis Desktop Manager + +## 3. 开发计划与任务分解 + +### 3.1 开发阶段划分 + +#### 阶段一:基础框架搭建(预计8个工作日) +- 项目初始化和环境配置 +- 数据库设计和迁移 +- 基础中间件开发 +- 认证授权系统 + +#### 阶段二:核心业务开发(预计25个工作日) +- 用户管理系统 +- 旅行结伴功能 +- 动物认领功能 +- 订单支付系统 + +#### 阶段三:管理功能开发(预计15个工作日) +- 管理后台API +- 数据统计分析 +- 系统配置管理 +- 日志审计功能 + +#### 阶段四:优化和部署(预计10个工作日) +- 性能优化 +- 安全加固 +- 测试和修复 +- 部署和监控 + +### 3.2 详细任务分解 + +#### 3.2.1 阶段一:基础框架搭建 + +##### 任务1.1:项目初始化(2个工作日) +**负责人**:后端架构师 + 后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 创建Express.js项目结构 +- 配置TypeScript和构建工具 +- 设置代码规范和Git hooks +- 配置开发环境 + +**具体子任务**: +1. 创建Express + TypeScript项目(4人时) +2. 配置ESLint、Prettier和Git hooks(4人时) +3. 设置环境变量和配置管理(4人时) +4. 配置开发和构建脚本(4人时) + +**验收标准**: +- 项目可以正常启动和热重载 +- 代码规范检查通过 +- TypeScript编译无错误 + +##### 任务1.2:数据库设计和迁移(3个工作日) +**负责人**:后端开发工程师 + 数据库工程师 +**工时估算**:24人时 +**任务描述**: +- 设计数据库表结构 +- 创建TypeORM实体 +- 编写数据库迁移 +- 创建种子数据 + +**具体子任务**: +1. 设计核心业务表结构(8人时) +2. 创建TypeORM实体和关系(8人时) +3. 编写数据库迁移脚本(4人时) +4. 创建测试和演示数据(4人时) + +**验收标准**: +- 数据库表结构完整 +- 实体关系正确 +- 迁移脚本可正常执行 + +##### 任务1.3:基础中间件开发(2个工作日) +**负责人**:后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 请求日志中间件 +- 错误处理中间件 +- 参数验证中间件 +- 跨域处理中间件 + +**具体子任务**: +1. 请求日志和响应时间中间件(4人时) +2. 全局错误处理中间件(4人时) +3. 请求参数验证中间件(4人时) +4. CORS和安全头中间件(4人时) + +**验收标准**: +- 中间件功能完整 +- 错误处理规范 +- 日志记录准确 + +##### 任务1.4:认证授权系统(1个工作日) +**负责人**:后端开发工程师 +**工时估算**:8人时 +**任务描述**: +- JWT认证实现 +- RBAC权限控制 +- 用户会话管理 +- 权限验证中间件 + +**具体子任务**: +1. JWT生成和验证(3人时) +2. RBAC权限模型实现(3人时) +3. 权限验证中间件(2人时) + +**验收标准**: +- JWT认证正常 +- 权限控制精确 +- 会话管理安全 + +#### 3.2.2 阶段二:核心业务开发 + +##### 任务2.1:用户管理系统(6个工作日) +**负责人**:后端开发工程师 +**工时估算**:48人时 +**任务描述**: +- 用户注册登录 +- 用户信息管理 +- 实名认证功能 +- 用户行为记录 + +**具体子任务**: +1. 微信登录和手机号登录(12人时) +2. 用户信息CRUD操作(10人时) +3. 实名认证流程和验证(12人时) +4. 用户行为日志记录(8人时) +5. 用户状态管理(6人时) + +**验收标准**: +- 登录流程完整 +- 用户信息管理功能齐全 +- 实名认证流程正确 + +##### 任务2.2:旅行结伴功能(7个工作日) +**负责人**:后端开发工程师 +**工时估算**:56人时 +**任务描述**: +- 旅行活动管理 +- 参与申请处理 +- 活动状态管理 +- 地理位置服务 + +**具体子任务**: +1. 旅行活动CRUD操作(14人时) +2. 参与申请和审核流程(12人时) +3. 活动状态和生命周期管理(10人时) +4. 地理位置搜索和推荐(10人时) +5. 活动评价和反馈(10人时) + +**验收标准**: +- 活动管理功能完整 +- 申请流程顺畅 +- 地理位置服务准确 + +##### 任务2.3:动物认领功能(6个工作日) +**负责人**:后端开发工程师 +**工时估算**:48人时 +**任务描述**: +- 动物信息管理 +- 认领申请处理 +- 认领记录跟踪 +- 动物状态更新 + +**具体子任务**: +1. 动物信息CRUD操作(12人时) +2. 认领申请和审核流程(12人时) +3. 认领记录和历史跟踪(10人时) +4. 动物状态和健康记录(8人时) +5. 认领动态和通知(6人时) + +**验收标准**: +- 动物信息管理完整 +- 认领流程清晰 +- 状态跟踪准确 + +##### 任务2.4:订单支付系统(6个工作日) +**负责人**:后端开发工程师 +**工时估算**:48人时 +**任务描述**: +- 订单创建和管理 +- 支付接口集成 +- 订单状态跟踪 +- 退款处理功能 + +**具体子任务**: +1. 订单创建和状态管理(12人时) +2. 微信支付接口集成(15人时) +3. 支付回调和状态同步(10人时) +4. 退款申请和处理(8人时) +5. 订单查询和统计(3人时) + +**验收标准**: +- 订单管理功能完整 +- 支付流程稳定 +- 退款处理正确 + +#### 3.2.3 阶段三:管理功能开发 + +##### 任务3.1:管理后台API(5个工作日) +**负责人**:后端开发工程师 +**工时估算**:40人时 +**任务描述**: +- 管理员认证授权 +- 用户管理接口 +- 内容管理接口 +- 系统配置接口 + +**具体子任务**: +1. 管理员登录和权限管理(10人时) +2. 用户管理相关接口(10人时) +3. 内容审核和管理接口(10人时) +4. 系统配置和参数管理(10人时) + +**验收标准**: +- 管理接口功能完整 +- 权限控制严格 +- 数据操作安全 + +##### 任务3.2:数据统计分析(4个工作日) +**负责人**:后端开发工程师 +**工时估算**:32人时 +**任务描述**: +- 用户数据统计 +- 业务数据分析 +- 财务数据报表 +- 实时数据监控 + +**具体子任务**: +1. 用户增长和活跃度统计(10人时) +2. 业务数据分析和报表(10人时) +3. 财务收支统计(8人时) +4. 实时数据监控接口(4人时) + +**验收标准**: +- 统计数据准确 +- 报表生成正确 +- 实时监控有效 + +##### 任务3.3:系统配置管理(3个工作日) +**负责人**:后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 系统参数配置 +- 字典数据管理 +- 配置缓存机制 +- 配置变更通知 + +**具体子任务**: +1. 系统参数CRUD操作(8人时) +2. 字典数据管理接口(6人时) +3. 配置缓存和更新机制(6人时) +4. 配置变更通知和日志(4人时) + +**验收标准**: +- 配置管理功能完整 +- 缓存机制有效 +- 变更通知及时 + +##### 任务3.4:日志审计功能(3个工作日) +**负责人**:后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 操作日志记录 +- 审计日志查询 +- 日志分析统计 +- 日志归档清理 + +**具体子任务**: +1. 操作日志记录和存储(8人时) +2. 审计日志查询接口(8人时) +3. 日志分析和统计(4人时) +4. 日志归档和清理机制(4人时) + +**验收标准**: +- 日志记录完整 +- 查询功能强大 +- 归档机制有效 + +#### 3.2.4 阶段四:优化和部署 + +##### 任务4.1:性能优化(3个工作日) +**负责人**:后端开发工程师 +**工时估算**:24人时 +**任务描述**: +- 数据库查询优化 +- 缓存策略优化 +- 接口性能优化 +- 并发处理优化 + +**具体子任务**: +1. SQL查询优化和索引调整(8人时) +2. Redis缓存策略优化(6人时) +3. 接口响应时间优化(6人时) +4. 并发处理和连接池优化(4人时) + +**验收标准**: +- 查询性能提升50% +- 缓存命中率>80% +- 接口响应时间<500ms + +##### 任务4.2:安全加固(2个工作日) +**负责人**:后端开发工程师 +**工时估算**:16人时 +**任务描述**: +- 输入验证加强 +- SQL注入防护 +- XSS攻击防护 +- 接口限流保护 + +**具体子任务**: +1. 输入参数验证和过滤(6人时) +2. SQL注入和XSS防护(4人时) +3. 接口限流和防刷机制(4人时) +4. 敏感数据加密存储(2人时) + +**验收标准**: +- 安全漏洞修复 +- 防护机制有效 +- 敏感数据安全 + +##### 任务4.3:测试和修复(3个工作日) +**负责人**:后端开发工程师 + 测试工程师 +**工时估算**:24人时 +**任务描述**: +- 单元测试编写 +- 集成测试执行 +- 性能测试验证 +- Bug修复和优化 + +**具体子任务**: +1. 单元测试编写和执行(10人时) +2. 集成测试和API测试(8人时) +3. 性能测试和压力测试(4人时) +4. Bug修复和代码优化(2人时) + +**验收标准**: +- 测试覆盖率>80% +- 集成测试通过 +- 性能指标达标 + +##### 任务4.4:部署和监控(2个工作日) +**负责人**:后端开发工程师 + 运维工程师 +**工时估算**:16人时 +**任务描述**: +- 生产环境部署 +- 监控系统配置 +- 日志收集配置 +- 备份恢复机制 + +**具体子任务**: +1. Docker容器化和部署(6人时) +2. 监控和告警配置(4人时) +3. 日志收集和分析配置(4人时) +4. 数据备份和恢复测试(2人时) + +**验收标准**: +- 部署流程自动化 +- 监控系统正常 +- 备份机制有效 + +## 4. 开发规范 + +### 4.1 代码规范 + +#### 4.1.1 项目结构规范 +```typescript +// src/controllers/admin/user.controller.ts +import { Request, Response } from 'express' +import { UserService } from '@/services/user/user.service' +import { CreateUserDto, UpdateUserDto, UserQueryDto } from '@/models/dto/user.dto' +import { ApiResponse } from '@/utils/response' +import { validateDto } from '@/middleware/validation' + +export class AdminUserController { + private userService: UserService + + constructor() { + this.userService = new UserService() + } + + /** + * 获取用户列表 + */ + async getUsers(req: Request, res: Response) { + try { + const query = req.query as UserQueryDto + const result = await this.userService.getUsers(query) + + return ApiResponse.success(res, result, '获取用户列表成功') + } catch (error) { + return ApiResponse.error(res, error.message) + } + } + + /** + * 创建用户 + */ + async createUser(req: Request, res: Response) { + try { + const createUserDto = await validateDto(CreateUserDto, req.body) + const user = await this.userService.createUser(createUserDto) + + return ApiResponse.success(res, user, '创建用户成功') + } catch (error) { + return ApiResponse.error(res, error.message) + } + } + + /** + * 更新用户 + */ + async updateUser(req: Request, res: Response) { + try { + const { id } = req.params + const updateUserDto = await validateDto(UpdateUserDto, req.body) + const user = await this.userService.updateUser(Number(id), updateUserDto) + + return ApiResponse.success(res, user, '更新用户成功') + } catch (error) { + return ApiResponse.error(res, error.message) + } + } + + /** + * 删除用户 + */ + async deleteUser(req: Request, res: Response) { + try { + const { id } = req.params + await this.userService.deleteUser(Number(id)) + + return ApiResponse.success(res, null, '删除用户成功') + } catch (error) { + return ApiResponse.error(res, error.message) + } + } +} +``` + +#### 4.1.2 服务层规范 +```typescript +// src/services/user/user.service.ts +import { Repository } from 'typeorm' +import { AppDataSource } from '@/config/database' +import { User } from '@/models/entities/user.entity' +import { CreateUserDto, UpdateUserDto, UserQueryDto } from '@/models/dto/user.dto' +import { UserVo } from '@/models/vo/user.vo' +import { PageResult } from '@/types/common' +import { CacheService } from '@/utils/cache' +import { LoggerService } from '@/utils/logger' + +export class UserService { + private userRepository: Repository + private cacheService: CacheService + private logger: LoggerService + + constructor() { + this.userRepository = AppDataSource.getRepository(User) + this.cacheService = new CacheService() + this.logger = new LoggerService('UserService') + } + + /** + * 获取用户列表 + */ + async getUsers(query: UserQueryDto): Promise> { + try { + const { page = 1, limit = 10, keyword, status } = query + const queryBuilder = this.userRepository.createQueryBuilder('user') + + // 关键词搜索 + if (keyword) { + queryBuilder.andWhere( + '(user.username LIKE :keyword OR user.email LIKE :keyword OR user.phone LIKE :keyword)', + { keyword: `%${keyword}%` } + ) + } + + // 状态筛选 + if (status !== undefined) { + queryBuilder.andWhere('user.status = :status', { status }) + } + + // 分页 + queryBuilder + .skip((page - 1) * limit) + .take(limit) + .orderBy('user.created_at', 'DESC') + + const [users, total] = await queryBuilder.getManyAndCount() + + // 转换为VO + const userVos = users.map(user => new UserVo(user)) + + return { + data: userVos, + total, + page, + limit, + pages: Math.ceil(total / limit) + } + } catch (error) { + this.logger.error('获取用户列表失败', error) + throw error + } + } + + /** + * 根据ID获取用户 + */ + async getUserById(id: number): Promise { + try { + // 先从缓存获取 + const cacheKey = `user:${id}` + let user = await this.cacheService.get(cacheKey) + + if (!user) { + // 缓存未命中,从数据库获取 + user = await this.userRepository.findOne({ + where: { id }, + relations: ['profile', 'roles'] + }) + + if (user) { + // 存入缓存,过期时间30分钟 + await this.cacheService.set(cacheKey, user, 1800) + } + } + + return user ? new UserVo(user) : null + } catch (error) { + this.logger.error('获取用户详情失败', error) + throw error + } + } + + /** + * 创建用户 + */ + async createUser(createUserDto: CreateUserDto): Promise { + try { + // 检查用户名是否已存在 + const existingUser = await this.userRepository.findOne({ + where: [ + { username: createUserDto.username }, + { email: createUserDto.email } + ] + }) + + if (existingUser) { + throw new Error('用户名或邮箱已存在') + } + + // 创建用户 + const user = this.userRepository.create(createUserDto) + const savedUser = await this.userRepository.save(user) + + this.logger.info('创建用户成功', { userId: savedUser.id }) + return new UserVo(savedUser) + } catch (error) { + this.logger.error('创建用户失败', error) + throw error + } + } + + /** + * 更新用户 + */ + async updateUser(id: number, updateUserDto: UpdateUserDto): Promise { + try { + const user = await this.userRepository.findOne({ where: { id } }) + if (!user) { + throw new Error('用户不存在') + } + + // 更新用户信息 + Object.assign(user, updateUserDto) + const updatedUser = await this.userRepository.save(user) + + // 清除缓存 + await this.cacheService.del(`user:${id}`) + + this.logger.info('更新用户成功', { userId: id }) + return new UserVo(updatedUser) + } catch (error) { + this.logger.error('更新用户失败', error) + throw error + } + } + + /** + * 删除用户 + */ + async deleteUser(id: number): Promise { + try { + const user = await this.userRepository.findOne({ where: { id } }) + if (!user) { + throw new Error('用户不存在') + } + + // 软删除 + await this.userRepository.softDelete(id) + + // 清除缓存 + await this.cacheService.del(`user:${id}`) + + this.logger.info('删除用户成功', { userId: id }) + } catch (error) { + this.logger.error('删除用户失败', error) + throw error + } + } +} +``` + +#### 4.1.3 数据模型规范 +```typescript +// src/models/entities/user.entity.ts +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + OneToOne, + ManyToMany, + JoinTable +} from 'typeorm' +import { UserProfile } from './user-profile.entity' +import { Role } from './role.entity' + +@Entity('users') +export class User { + @PrimaryGeneratedColumn() + id: number + + @Column({ unique: true, length: 50 }) + username: string + + @Column({ unique: true, length: 100 }) + email: string + + @Column({ length: 20, nullable: true }) + phone: string + + @Column({ length: 255 }) + password: string + + @Column({ type: 'tinyint', default: 1, comment: '状态:0-禁用,1-启用' }) + status: number + + @Column({ type: 'datetime', nullable: true, comment: '最后登录时间' }) + last_login_at: Date + + @CreateDateColumn() + created_at: Date + + @UpdateDateColumn() + updated_at: Date + + @DeleteDateColumn() + deleted_at: Date + + // 关联关系 + @OneToOne(() => UserProfile, profile => profile.user) + profile: UserProfile + + @ManyToMany(() => Role, role => role.users) + @JoinTable({ + name: 'user_roles', + joinColumn: { name: 'user_id', referencedColumnName: 'id' }, + inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' } + }) + roles: Role[] +} +``` + +```typescript +// src/models/dto/user.dto.ts +import { IsString, IsEmail, IsOptional, IsNumber, MinLength, MaxLength } from 'class-validator' + +export class CreateUserDto { + @IsString() + @MinLength(3) + @MaxLength(50) + username: string + + @IsEmail() + email: string + + @IsOptional() + @IsString() + @MaxLength(20) + phone?: string + + @IsString() + @MinLength(6) + password: string +} + +export class UpdateUserDto { + @IsOptional() + @IsString() + @MinLength(3) + @MaxLength(50) + username?: string + + @IsOptional() + @IsEmail() + email?: string + + @IsOptional() + @IsString() + @MaxLength(20) + phone?: string + + @IsOptional() + @IsNumber() + status?: number +} + +export class UserQueryDto { + @IsOptional() + @IsNumber() + page?: number + + @IsOptional() + @IsNumber() + limit?: number + + @IsOptional() + @IsString() + keyword?: string + + @IsOptional() + @IsNumber() + status?: number +} +``` + +```typescript +// src/models/vo/user.vo.ts +import { User } from '@/models/entities/user.entity' + +export class UserVo { + id: number + username: string + email: string + phone: string + status: number + last_login_at: Date + created_at: Date + updated_at: Date + + constructor(user: User) { + this.id = user.id + this.username = user.username + this.email = user.email + this.phone = user.phone + this.status = user.status + this.last_login_at = user.last_login_at + this.created_at = user.created_at + this.updated_at = user.updated_at + } +} +``` + +### 4.2 API设计规范 + +#### 4.2.1 路由规范 +```typescript +// src/routes/admin/user.routes.ts +import { Router } from 'express' +import { AdminUserController } from '@/controllers/admin/user.controller' +import { authMiddleware } from '@/middleware/auth' +import { permissionMiddleware } from '@/middleware/permission' + +const router = Router() +const userController = new AdminUserController() + +// 用户管理路由 +router.get( + '/users', + authMiddleware, + permissionMiddleware('user:view'), + userController.getUsers.bind(userController) +) + +router.get( + '/users/:id', + authMiddleware, + permissionMiddleware('user:view'), + userController.getUserById.bind(userController) +) + +router.post( + '/users', + authMiddleware, + permissionMiddleware('user:create'), + userController.createUser.bind(userController) +) + +router.put( + '/users/:id', + authMiddleware, + permissionMiddleware('user:update'), + userController.updateUser.bind(userController) +) + +router.delete( + '/users/:id', + authMiddleware, + permissionMiddleware('user:delete'), + userController.deleteUser.bind(userController) +) + +export default router +``` + +#### 4.2.2 响应格式规范 +```typescript +// src/utils/response.ts +import { Response } from 'express' + +export interface ApiResponseData { + code: number + message: string + data: T + timestamp: number +} + +export class ApiResponse { + /** + * 成功响应 + */ + static success(res: Response, data: T, message = '操作成功'): Response { + return res.json({ + code: 200, + message, + data, + timestamp: Date.now() + }) + } + + /** + * 错误响应 + */ + static error(res: Response, message = '操作失败', code = 500): Response { + return res.status(code).json({ + code, + message, + data: null, + timestamp: Date.now() + }) + } + + /** + * 参数错误响应 + */ + static badRequest(res: Response, message = '参数错误'): Response { + return this.error(res, message, 400) + } + + /** + * 未授权响应 + */ + static unauthorized(res: Response, message = '未授权访问'): Response { + return this.error(res, message, 401) + } + + /** + * 禁止访问响应 + */ + static forbidden(res: Response, message = '禁止访问'): Response { + return this.error(res, message, 403) + } + + /** + * 资源不存在响应 + */ + static notFound(res: Response, message = '资源不存在'): Response { + return this.error(res, message, 404) + } +} +``` + +### 4.3 数据库操作规范 + +#### 4.3.1 查询优化规范 +```typescript +// src/services/travel/travel.service.ts +export class TravelService { + /** + * 获取旅行活动列表(优化版本) + */ + async getTravelActivities(query: TravelQueryDto): Promise> { + const { page = 1, limit = 10, city, start_date, end_date, status } = query + + // 使用QueryBuilder进行复杂查询 + const queryBuilder = this.travelRepository + .createQueryBuilder('travel') + .leftJoinAndSelect('travel.creator', 'creator') + .leftJoinAndSelect('travel.participants', 'participants') + .select([ + 'travel.id', + 'travel.title', + 'travel.description', + 'travel.city', + 'travel.start_date', + 'travel.end_date', + 'travel.max_participants', + 'travel.status', + 'travel.created_at', + 'creator.id', + 'creator.username', + 'creator.avatar_url', + 'participants.id' + ]) + + // 城市筛选 + if (city) { + queryBuilder.andWhere('travel.city = :city', { city }) + } + + // 日期范围筛选 + if (start_date) { + queryBuilder.andWhere('travel.start_date >= :start_date', { start_date }) + } + if (end_date) { + queryBuilder.andWhere('travel.end_date <= :end_date', { end_date }) + } + + // 状态筛选 + if (status !== undefined) { + queryBuilder.andWhere('travel.status = :status', { status }) + } + + // 分页和排序 + queryBuilder + .skip((page - 1) * limit) + .take(limit) + .orderBy('travel.created_at', 'DESC') + + const [travels, total] = await queryBuilder.getManyAndCount() + + return { + data: travels.map(travel => new TravelVo(travel)), + total, + page, + limit, + pages: Math.ceil(total / limit) + } + } + + /** + * 批量更新操作 + */ + async batchUpdateTravelStatus(ids: number[], status: number): Promise { + await this.travelRepository + .createQueryBuilder() + .update(Travel) + .set({ status, updated_at: new Date() }) + .where('id IN (:...ids)', { ids }) + .execute() + } +} +``` + +#### 4.3.2 事务处理规范 +```typescript +// src/services/order/order.service.ts +import { DataSource } from 'typeorm' + +export class OrderService { + constructor(private dataSource: DataSource) {} + + /** + * 创建订单(事务处理) + */ + async createOrder(createOrderDto: CreateOrderDto): Promise { + const queryRunner = this.dataSource.createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction() + + try { + // 1. 创建订单 + const order = queryRunner.manager.create(Order, { + ...createOrderDto, + order_no: this.generateOrderNo(), + status: OrderStatus.PENDING + }) + const savedOrder = await queryRunner.manager.save(order) + + // 2. 创建订单项 + const orderItems = createOrderDto.items.map(item => + queryRunner.manager.create(OrderItem, { + ...item, + order_id: savedOrder.id + }) + ) + await queryRunner.manager.save(orderItems) + + // 3. 更新库存 + for (const item of createOrderDto.items) { + await queryRunner.manager.decrement( + Product, + { id: item.product_id }, + 'stock', + item.quantity + ) + } + + // 4. 记录操作日志 + const log = queryRunner.manager.create(OperationLog, { + user_id: createOrderDto.user_id, + action: 'CREATE_ORDER', + target_type: 'ORDER', + target_id: savedOrder.id, + details: JSON.stringify(createOrderDto) + }) + await queryRunner.manager.save(log) + + await queryRunner.commitTransaction() + + return new OrderVo(savedOrder) + } catch (error) { + await queryRunner.rollbackTransaction() + throw error + } finally { + await queryRunner.release() + } + } +} +``` + +## 5. 质量保证 + +### 5.1 测试策略 + +#### 5.1.1 单元测试 +```typescript +// tests/unit/services/user.service.spec.ts +import { UserService } from '@/services/user/user.service' +import { User } from '@/models/entities/user.entity' +import { CreateUserDto } from '@/models/dto/user.dto' + +describe('UserService', () => { + let userService: UserService + let mockUserRepository: any + + beforeEach(() => { + mockUserRepository = { + findOne: jest.fn(), + create: jest.fn(), + save: jest.fn(), + softDelete: jest.fn(), + createQueryBuilder: jest.fn() + } + + userService = new UserService() + userService['userRepository'] = mockUserRepository + }) + + describe('createUser', () => { + it('should create user successfully', async () => { + const createUserDto: CreateUserDto = { + username: 'testuser', + email: 'test@example.com', + password: 'password123' + } + + const mockUser = { id: 1, ...createUserDto } + + mockUserRepository.findOne.mockResolvedValue(null) + mockUserRepository.create.mockReturnValue(mockUser) + mockUserRepository.save.mockResolvedValue(mockUser) + + const result = await userService.createUser(createUserDto) + + expect(result.username).toBe(createUserDto.username) + expect(result.email).toBe(createUserDto.email) + expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser) + }) + + it('should throw error when user already exists', async () => { + const createUserDto: CreateUserDto = { + username: 'testuser', + email: 'test@example.com', + password: 'password123' + } + + mockUserRepository.findOne.mockResolvedValue({ id: 1 }) + + await expect(userService.createUser(createUserDto)) + .rejects.toThrow('用户名或邮箱已存在') + }) + }) +}) +``` + +#### 5.1.2 集成测试 +```typescript +// tests/integration/controllers/user.controller.spec.ts +import request from 'supertest' +import { app } from '@/app' +import { AppDataSource } from '@/config/database' + +describe('User Controller Integration Tests', () => { + let authToken: string + + beforeAll(async () => { + await AppDataSource.initialize() + + // 获取认证token + const loginResponse = await request(app) + .post('/api/auth/login') + .send({ + username: 'admin', + password: 'admin123' + }) + + authToken = loginResponse.body.data.access_token + }) + + afterAll(async () => { + await AppDataSource.destroy() + }) + + describe('GET /api/admin/users', () => { + it('should return user list', async () => { + const response = await request(app) + .get('/api/admin/users') + .set('Authorization', `Bearer ${authToken}`) + .expect(200) + + expect(response.body.code).toBe(200) + expect(response.body.data).toHaveProperty('data') + expect(response.body.data).toHaveProperty('total') + expect(Array.isArray(response.body.data.data)).toBe(true) + }) + + it('should return 401 without auth token', async () => { + await request(app) + .get('/api/admin/users') + .expect(401) + }) + }) + + describe('POST /api/admin/users', () => { + it('should create user successfully', async () => { + const newUser = { + username: 'newuser', + email: 'newuser@example.com', + password: 'password123' + } + + const response = await request(app) + .post('/api/admin/users') + .set('Authorization', `Bearer ${authToken}`) + .send(newUser) + .expect(200) + + expect(response.body.code).toBe(200) + expect(response.body.data.username).toBe(newUser.username) + expect(response.body.data.email).toBe(newUser.email) + }) + }) +}) +``` + +### 5.2 代码质量检查 + +#### 5.2.1 ESLint配置 +```javascript +// .eslintrc.js +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + '@typescript-eslint/recommended', + 'prettier' + ], + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + 'prefer-const': 'error', + 'no-var': 'error' + } +} +``` + +#### 5.2.2 代码审查流程 +1. 开发者提交Pull Request +2. 自动化检查(ESLint、TypeScript、测试) +3. 同行代码审查 +4. 技术负责人审查 +5. 合并到主分支 + +## 6. 部署和运维 + +### 6.1 Docker配置 + +#### 6.1.1 Dockerfile +```dockerfile +# Dockerfile +FROM node:18-alpine + +WORKDIR /app + +# 复制package文件 +COPY package*.json ./ + +# 安装依赖 +RUN npm ci --only=production + +# 复制源代码 +COPY . . + +# 构建应用 +RUN npm run build + +# 暴露端口 +EXPOSE 3000 + +# 启动应用 +CMD ["npm", "start"] +``` + +#### 6.1.2 Docker Compose +```yaml +# docker-compose.yml +version: '3.8' + +services: + app: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DB_HOST=mysql + - REDIS_HOST=redis + depends_on: + - mysql + - redis + volumes: + - ./logs:/app/logs + + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: jiebanke + MYSQL_USER: jiebanke + MYSQL_PASSWORD: password + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + +volumes: + mysql_data: + redis_data: +``` + +### 6.2 CI/CD配置 + +#### 6.2.1 GitHub Actions +```yaml +# .github/workflows/deploy.yml +name: Deploy to Production + +on: + push: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + redis: + image: redis:7-alpine + options: >- + --health-cmd="redis-cli ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linting + run: npm run lint + + - name: Run tests + run: npm run test + env: + DB_HOST: localhost + DB_PORT: 3306 + DB_USERNAME: root + DB_PASSWORD: root + DB_DATABASE: test + REDIS_HOST: localhost + REDIS_PORT: 6379 + + deploy: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Build Docker image + run: docker build -t jiebanke/backend:${{ github.sha }} . + + - name: Push to registry + run: | + echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + docker push jiebanke/backend:${{ github.sha }} + + - name: Deploy to server + uses: appleboy/ssh-action@v0.1.5 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.SSH_KEY }} + script: | + docker pull jiebanke/backend:${{ github.sha }} + docker stop jiebanke-backend || true + docker rm jiebanke-backend || true + docker run -d --name jiebanke-backend \ + -p 3000:3000 \ + --env-file /opt/jiebanke/.env \ + jiebanke/backend:${{ github.sha }} +``` + +### 6.3 监控和日志 + +#### 6.3.1 日志配置 +```typescript +// src/utils/logger.ts +import winston from 'winston' +import DailyRotateFile from 'winston-daily-rotate-file' + +export class LoggerService { + private logger: winston.Logger + + constructor(private context: string) { + this.logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { service: 'jiebanke-backend', context: this.context }, + transports: [ + // 控制台输出 + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ) + }), + + // 错误日志文件 + new DailyRotateFile({ + filename: 'logs/error-%DATE%.log', + datePattern: 'YYYY-MM-DD', + level: 'error', + maxSize: '20m', + maxFiles: '14d' + }), + + // 应用日志文件 + new DailyRotateFile({ + filename: 'logs/app-%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxSize: '20m', + maxFiles: '14d' + }) + ] + }) + } + + info(message: string, meta?: any) { + this.logger.info(message, meta) + } + + error(message: string, error?: any) { + this.logger.error(message, { error: error?.stack || error }) + } + + warn(message: string, meta?: any) { + this.logger.warn(message, meta) + } + + debug(message: string, meta?: any) { + this.logger.debug(message, meta) + } +} +``` + +#### 6.3.2 性能监控 +```typescript +// src/middleware/monitor.ts +import { Request, Response, NextFunction } from 'express' +import { LoggerService } from '@/utils/logger' + +const logger = new LoggerService('Monitor') + +export const performanceMonitor = (req: Request, res: Response, next: NextFunction) => { + const startTime = Date.now() + + res.on('finish', () => { + const duration = Date.now() - startTime + const { method, originalUrl, ip } = req + const { statusCode } = res + + // 记录请求日志 + logger.info('HTTP Request', { + method, + url: originalUrl, + statusCode, + duration, + ip, + userAgent: req.get('User-Agent') + }) + + // 慢查询告警 + if (duration > 2000) { + logger.warn('Slow Request', { + method, + url: originalUrl, + duration + }) + } + + // 错误状态告警 + if (statusCode >= 500) { + logger.error('Server Error', { + method, + url: originalUrl, + statusCode + }) + } + }) + + next() +} +``` + +## 7. 总结 + +本开发文档详细规划了解班客后端管理系统的开发计划,包括: + +### 7.1 开发计划 +- **总工期**:58个工作日 +- **团队规模**:2-3名后端开发工程师 +- **关键里程碑**:基础框架、核心业务、管理功能、优化部署 + +### 7.2 技术架构 +- **后端框架**:Express.js + TypeScript +- **数据库**:MySQL + Redis +- **ORM框架**:TypeORM +- **认证授权**:JWT + RBAC + +### 7.3 质量保证 +- **代码规范**:ESLint + Prettier +- **测试策略**:单元测试 + 集成测试 +- **性能优化**:查询优化、缓存策略 +- **监控体系**:日志监控 + 性能监控 + +### 7.4 部署运维 +- **容器化**:Docker + Docker Compose +- **CI/CD**:GitHub Actions自动化部署 +- **监控日志**:Winston日志系统 +- **性能监控**:请求监控和告警 + +通过严格按照本开发文档执行,可以确保后端管理系统的高质量交付和稳定运行。 \ No newline at end of file diff --git a/docs/后端管理需求文档.md b/docs/后端管理需求文档.md new file mode 100644 index 0000000..d96fbd9 --- /dev/null +++ b/docs/后端管理需求文档.md @@ -0,0 +1,851 @@ +# 解班客后端管理需求文档 + +## 1. 项目概述 + +### 1.1 系统定位 +后端管理系统是解班客平台的核心服务层,负责提供所有业务逻辑处理、数据管理、API服务以及与第三方系统的集成。系统采用RESTful API架构,为前端应用、小程序和管理后台提供统一的数据服务。 + +### 1.2 核心职责 +- 提供完整的API服务接口 +- 处理业务逻辑和数据验证 +- 管理用户认证和权限控制 +- 集成第三方服务(支付、短信、地图等) +- 确保数据安全和系统稳定性 + +### 1.3 技术架构 +- **运行环境**:Node.js 18+ +- **Web框架**:Express.js +- **数据库**:MySQL 8.0 + Redis 6.0 +- **认证方式**:JWT + Passport +- **API文档**:Swagger/OpenAPI + +## 2. 用户管理模块 + +### 2.1 用户认证服务 +#### 2.1.1 微信登录 +``` +POST /api/auth/wechat/login +功能:微信小程序登录 +参数: +- code: 微信授权码 +- encryptedData: 加密用户信息 +- iv: 初始向量 +返回:JWT token和用户基本信息 +``` + +#### 2.1.2 手机号验证 +``` +POST /api/auth/phone/verify +功能:发送手机验证码 +参数: +- phone: 手机号码 +- type: 验证类型(register/login/reset) + +POST /api/auth/phone/login +功能:手机号验证码登录 +参数: +- phone: 手机号码 +- code: 验证码 +``` + +#### 2.1.3 Token管理 +``` +POST /api/auth/refresh +功能:刷新访问令牌 +参数: +- refreshToken: 刷新令牌 + +POST /api/auth/logout +功能:用户登出 +参数: +- token: 访问令牌 +``` + +### 2.2 用户信息管理 +#### 2.2.1 用户资料 +``` +GET /api/users/profile +功能:获取用户详细信息 + +PUT /api/users/profile +功能:更新用户资料 +参数: +- nickname: 昵称 +- avatar: 头像URL +- gender: 性别 +- birthday: 生日 +- location: 所在地 +- bio: 个人简介 +- interests: 兴趣标签 +``` + +#### 2.2.2 实名认证 +``` +POST /api/users/identity/verify +功能:提交实名认证 +参数: +- realName: 真实姓名 +- idCard: 身份证号 +- idCardFront: 身份证正面照 +- idCardBack: 身份证背面照 + +GET /api/users/identity/status +功能:查询认证状态 +``` + +#### 2.2.3 用户设置 +``` +PUT /api/users/settings +功能:更新用户设置 +参数: +- privacy: 隐私设置 +- notification: 通知设置 +- security: 安全设置 + +GET /api/users/settings +功能:获取用户设置 +``` + +### 2.3 用户关系管理 +#### 2.3.1 关注功能 +``` +POST /api/users/{userId}/follow +功能:关注用户 + +DELETE /api/users/{userId}/follow +功能:取消关注 + +GET /api/users/following +功能:获取关注列表 + +GET /api/users/followers +功能:获取粉丝列表 +``` + +#### 2.3.2 黑名单管理 +``` +POST /api/users/{userId}/block +功能:拉黑用户 + +DELETE /api/users/{userId}/block +功能:取消拉黑 + +GET /api/users/blocked +功能:获取黑名单列表 +``` + +## 3. 活动管理模块 + +### 3.1 活动发布管理 +#### 3.1.1 活动创建 +``` +POST /api/activities +功能:创建新活动 +参数: +- title: 活动标题 +- description: 活动描述 +- type: 活动类型 +- startTime: 开始时间 +- endTime: 结束时间 +- location: 活动地点 +- maxParticipants: 最大参与人数 +- fee: 活动费用 +- requirements: 参与要求 +- images: 活动图片 +- tags: 活动标签 +``` + +#### 3.1.2 活动编辑 +``` +PUT /api/activities/{activityId} +功能:编辑活动信息 + +DELETE /api/activities/{activityId} +功能:删除活动 + +PATCH /api/activities/{activityId}/status +功能:更新活动状态 +参数: +- status: 活动状态(draft/published/cancelled/completed) +``` + +### 3.2 活动搜索和筛选 +#### 3.2.1 活动列表 +``` +GET /api/activities +功能:获取活动列表 +参数: +- page: 页码 +- limit: 每页数量 +- type: 活动类型 +- location: 地理位置 +- startDate: 开始日期 +- endDate: 结束日期 +- priceMin: 最低价格 +- priceMax: 最高价格 +- keyword: 关键词搜索 +- sort: 排序方式 +``` + +#### 3.2.2 活动详情 +``` +GET /api/activities/{activityId} +功能:获取活动详细信息 + +GET /api/activities/{activityId}/participants +功能:获取活动参与者列表 + +GET /api/activities/nearby +功能:获取附近活动 +参数: +- latitude: 纬度 +- longitude: 经度 +- radius: 搜索半径 +``` + +### 3.3 活动报名管理 +#### 3.3.1 报名流程 +``` +POST /api/activities/{activityId}/join +功能:报名参加活动 +参数: +- message: 报名留言 +- emergencyContact: 紧急联系人 + +GET /api/activities/{activityId}/join/status +功能:查询报名状态 + +DELETE /api/activities/{activityId}/join +功能:取消报名 +``` + +#### 3.3.2 报名审核 +``` +PUT /api/activities/{activityId}/participants/{userId}/approve +功能:审核通过报名 + +PUT /api/activities/{activityId}/participants/{userId}/reject +功能:拒绝报名 +参数: +- reason: 拒绝原因 + +GET /api/activities/my/applications +功能:获取我的报名记录 + +GET /api/activities/my/created +功能:获取我创建的活动 +``` + +## 4. 动物认领模块 + +### 4.1 动物信息管理 +#### 4.1.1 动物档案 +``` +POST /api/animals +功能:添加动物信息 +参数: +- name: 动物名称 +- type: 动物类型 +- breed: 品种 +- age: 年龄 +- gender: 性别 +- weight: 体重 +- health: 健康状况 +- description: 描述 +- images: 动物照片 +- farmId: 所属农场ID +- price: 认领价格 +``` + +#### 4.1.2 动物状态管理 +``` +PUT /api/animals/{animalId} +功能:更新动物信息 + +PATCH /api/animals/{animalId}/status +功能:更新动物状态 +参数: +- status: 状态(available/adopted/reserved/unavailable) + +GET /api/animals +功能:获取动物列表 +参数: +- type: 动物类型 +- status: 状态筛选 +- farmId: 农场筛选 +- priceMin: 最低价格 +- priceMax: 最高价格 +``` + +### 4.2 认领流程管理 +#### 4.2.1 认领申请 +``` +POST /api/animals/{animalId}/adopt +功能:提交认领申请 +参数: +- reason: 认领原因 +- experience: 养殖经验 +- visitPlan: 探访计划 +- duration: 认领期限 + +GET /api/adoptions/my +功能:获取我的认领记录 + +GET /api/adoptions/{adoptionId} +功能:获取认领详情 +``` + +#### 4.2.2 认领审核 +``` +PUT /api/adoptions/{adoptionId}/approve +功能:审核通过认领 + +PUT /api/adoptions/{adoptionId}/reject +功能:拒绝认领申请 +参数: +- reason: 拒绝原因 + +POST /api/adoptions/{adoptionId}/contract +功能:生成认领合同 +``` + +### 4.3 农场管理 +#### 4.3.1 农场信息 +``` +POST /api/farms +功能:添加农场信息 +参数: +- name: 农场名称 +- address: 农场地址 +- description: 农场描述 +- contact: 联系方式 +- facilities: 设施介绍 +- images: 农场照片 + +GET /api/farms +功能:获取农场列表 + +GET /api/farms/{farmId}/animals +功能:获取农场动物列表 +``` + +#### 4.3.2 探访管理 +``` +POST /api/farms/{farmId}/visits +功能:预约农场探访 +参数: +- visitDate: 探访日期 +- visitTime: 探访时间 +- visitors: 探访人数 +- purpose: 探访目的 + +GET /api/visits/my +功能:获取我的探访记录 + +PUT /api/visits/{visitId}/confirm +功能:确认探访预约 +``` + +## 5. 商家服务模块 + +### 5.1 商家入驻管理 +#### 5.1.1 入驻申请 +``` +POST /api/merchants/apply +功能:提交入驻申请 +参数: +- businessName: 商家名称 +- businessType: 商家类型 +- contactPerson: 联系人 +- contactPhone: 联系电话 +- businessLicense: 营业执照 +- address: 经营地址 +- description: 商家描述 +- qualifications: 资质证明 +``` + +#### 5.1.2 入驻审核 +``` +GET /api/merchants/applications +功能:获取入驻申请列表 + +PUT /api/merchants/applications/{applicationId}/approve +功能:审核通过入驻 + +PUT /api/merchants/applications/{applicationId}/reject +功能:拒绝入驻申请 +参数: +- reason: 拒绝原因 +``` + +### 5.2 商家信息管理 +#### 5.2.1 商家资料 +``` +GET /api/merchants/profile +功能:获取商家资料 + +PUT /api/merchants/profile +功能:更新商家资料 +参数: +- businessName: 商家名称 +- description: 商家描述 +- logo: 商家Logo +- images: 商家图片 +- businessHours: 营业时间 +- contactInfo: 联系信息 +``` + +#### 5.2.2 商家状态 +``` +PATCH /api/merchants/{merchantId}/status +功能:更新商家状态 +参数: +- status: 状态(active/inactive/suspended) + +GET /api/merchants +功能:获取商家列表 +参数: +- type: 商家类型 +- status: 状态筛选 +- location: 地理位置 +``` + +### 5.3 商品服务管理 +#### 5.3.1 商品管理 +``` +POST /api/merchants/products +功能:添加商品 +参数: +- name: 商品名称 +- category: 商品分类 +- price: 商品价格 +- description: 商品描述 +- images: 商品图片 +- stock: 库存数量 +- specifications: 商品规格 + +PUT /api/merchants/products/{productId} +功能:更新商品信息 + +DELETE /api/merchants/products/{productId} +功能:删除商品 +``` + +#### 5.3.2 订单管理 +``` +GET /api/merchants/orders +功能:获取商家订单列表 + +PUT /api/merchants/orders/{orderId}/confirm +功能:确认订单 + +PUT /api/merchants/orders/{orderId}/ship +功能:发货 +参数: +- trackingNumber: 快递单号 +- carrier: 快递公司 + +PUT /api/merchants/orders/{orderId}/complete +功能:完成订单 +``` + +## 6. 支付系统模块 + +### 6.1 支付接口 +#### 6.1.1 微信支付 +``` +POST /api/payments/wechat/create +功能:创建微信支付订单 +参数: +- orderId: 订单ID +- amount: 支付金额 +- description: 支付描述 +- openid: 用户openid + +POST /api/payments/wechat/notify +功能:微信支付回调处理 + +GET /api/payments/wechat/query/{orderId} +功能:查询支付状态 +``` + +#### 6.1.2 支付宝支付 +``` +POST /api/payments/alipay/create +功能:创建支付宝支付订单 + +POST /api/payments/alipay/notify +功能:支付宝支付回调处理 + +GET /api/payments/alipay/query/{orderId} +功能:查询支付状态 +``` + +### 6.2 订单管理 +#### 6.2.1 订单创建 +``` +POST /api/orders +功能:创建订单 +参数: +- type: 订单类型(activity/product/adoption) +- items: 订单项目 +- amount: 订单金额 +- paymentMethod: 支付方式 +- deliveryAddress: 配送地址 + +GET /api/orders/{orderId} +功能:获取订单详情 + +GET /api/orders/my +功能:获取我的订单列表 +``` + +#### 6.2.2 订单状态管理 +``` +PATCH /api/orders/{orderId}/status +功能:更新订单状态 +参数: +- status: 订单状态 + +POST /api/orders/{orderId}/refund +功能:申请退款 +参数: +- reason: 退款原因 +- amount: 退款金额 + +PUT /api/orders/{orderId}/refund/approve +功能:审核退款申请 +``` + +### 6.3 财务管理 +#### 6.3.1 账户管理 +``` +GET /api/wallet/balance +功能:获取账户余额 + +POST /api/wallet/recharge +功能:账户充值 +参数: +- amount: 充值金额 +- paymentMethod: 支付方式 + +POST /api/wallet/withdraw +功能:申请提现 +参数: +- amount: 提现金额 +- bankAccount: 银行账户 +``` + +#### 6.3.2 交易记录 +``` +GET /api/transactions +功能:获取交易记录 +参数: +- type: 交易类型 +- startDate: 开始日期 +- endDate: 结束日期 +- status: 交易状态 + +GET /api/transactions/{transactionId} +功能:获取交易详情 +``` + +## 7. 消息通知模块 + +### 7.1 消息系统 +#### 7.1.1 站内消息 +``` +POST /api/messages +功能:发送站内消息 +参数: +- receiverId: 接收者ID +- type: 消息类型 +- title: 消息标题 +- content: 消息内容 + +GET /api/messages +功能:获取消息列表 +参数: +- type: 消息类型 +- status: 读取状态 + +PUT /api/messages/{messageId}/read +功能:标记消息已读 +``` + +#### 7.1.2 推送通知 +``` +POST /api/notifications/push +功能:发送推送通知 +参数: +- userIds: 用户ID列表 +- title: 通知标题 +- content: 通知内容 +- type: 通知类型 + +GET /api/notifications/settings +功能:获取通知设置 + +PUT /api/notifications/settings +功能:更新通知设置 +``` + +### 7.2 短信服务 +#### 7.2.1 验证码短信 +``` +POST /api/sms/verification +功能:发送验证码短信 +参数: +- phone: 手机号码 +- type: 验证类型 +- template: 短信模板 + +POST /api/sms/verify +功能:验证短信验证码 +参数: +- phone: 手机号码 +- code: 验证码 +``` + +#### 7.2.2 通知短信 +``` +POST /api/sms/notification +功能:发送通知短信 +参数: +- phone: 手机号码 +- template: 短信模板 +- params: 模板参数 + +GET /api/sms/records +功能:获取短信发送记录 +``` + +### 7.3 邮件服务 +#### 7.3.1 邮件发送 +``` +POST /api/emails/send +功能:发送邮件 +参数: +- to: 收件人邮箱 +- subject: 邮件主题 +- content: 邮件内容 +- template: 邮件模板 + +GET /api/emails/templates +功能:获取邮件模板列表 + +POST /api/emails/templates +功能:创建邮件模板 +``` + +## 8. 文件管理模块 + +### 8.1 文件上传 +#### 8.1.1 图片上传 +``` +POST /api/files/images/upload +功能:上传图片文件 +参数: +- file: 图片文件 +- type: 图片类型(avatar/product/activity等) +- compress: 是否压缩 + +POST /api/files/images/batch +功能:批量上传图片 +参数: +- files: 图片文件数组 +``` + +#### 8.1.2 文档上传 +``` +POST /api/files/documents/upload +功能:上传文档文件 +参数: +- file: 文档文件 +- type: 文档类型 + +GET /api/files/{fileId} +功能:获取文件信息 + +DELETE /api/files/{fileId} +功能:删除文件 +``` + +### 8.2 文件管理 +#### 8.2.1 文件列表 +``` +GET /api/files +功能:获取文件列表 +参数: +- type: 文件类型 +- userId: 用户ID +- page: 页码 +- limit: 每页数量 + +GET /api/files/storage/usage +功能:获取存储使用情况 +``` + +#### 8.2.2 文件处理 +``` +POST /api/files/images/resize +功能:图片尺寸调整 +参数: +- fileId: 文件ID +- width: 宽度 +- height: 高度 + +POST /api/files/images/watermark +功能:添加水印 +参数: +- fileId: 文件ID +- watermark: 水印内容 +``` + +## 9. 数据统计模块 + +### 9.1 用户统计 +#### 9.1.1 用户数据 +``` +GET /api/statistics/users/overview +功能:获取用户概览统计 + +GET /api/statistics/users/growth +功能:获取用户增长统计 +参数: +- startDate: 开始日期 +- endDate: 结束日期 +- granularity: 统计粒度(day/week/month) + +GET /api/statistics/users/activity +功能:获取用户活跃度统计 +``` + +#### 9.1.2 用户行为 +``` +GET /api/statistics/users/behavior +功能:获取用户行为统计 + +POST /api/statistics/events/track +功能:记录用户行为事件 +参数: +- event: 事件名称 +- properties: 事件属性 +- userId: 用户ID +``` + +### 9.2 业务统计 +#### 9.2.1 活动统计 +``` +GET /api/statistics/activities/overview +功能:获取活动概览统计 + +GET /api/statistics/activities/popular +功能:获取热门活动统计 + +GET /api/statistics/activities/conversion +功能:获取活动转化率统计 +``` + +#### 9.2.2 交易统计 +``` +GET /api/statistics/transactions/overview +功能:获取交易概览统计 + +GET /api/statistics/transactions/revenue +功能:获取收入统计 + +GET /api/statistics/merchants/performance +功能:获取商家业绩统计 +``` + +## 10. 系统管理模块 + +### 10.1 配置管理 +#### 10.1.1 系统配置 +``` +GET /api/system/config +功能:获取系统配置 + +PUT /api/system/config +功能:更新系统配置 +参数: +- key: 配置键 +- value: 配置值 +- description: 配置描述 + +GET /api/system/config/{key} +功能:获取指定配置项 +``` + +#### 10.1.2 参数管理 +``` +GET /api/system/parameters +功能:获取系统参数列表 + +PUT /api/system/parameters/{key} +功能:更新系统参数 +参数: +- value: 参数值 +``` + +### 10.2 日志管理 +#### 10.2.1 操作日志 +``` +GET /api/system/logs/operations +功能:获取操作日志 +参数: +- userId: 用户ID +- action: 操作类型 +- startDate: 开始日期 +- endDate: 结束日期 + +POST /api/system/logs/operations +功能:记录操作日志 +参数: +- action: 操作类型 +- resource: 操作资源 +- details: 操作详情 +``` + +#### 10.2.2 错误日志 +``` +GET /api/system/logs/errors +功能:获取错误日志 + +POST /api/system/logs/errors +功能:记录错误日志 +参数: +- level: 错误级别 +- message: 错误消息 +- stack: 错误堆栈 +- context: 错误上下文 +``` + +### 10.3 监控管理 +#### 10.3.1 系统监控 +``` +GET /api/system/health +功能:系统健康检查 + +GET /api/system/metrics +功能:获取系统指标 +参数: +- metric: 指标类型 +- timeRange: 时间范围 + +GET /api/system/status +功能:获取系统状态 +``` + +#### 10.3.2 性能监控 +``` +GET /api/system/performance +功能:获取性能数据 + +GET /api/system/performance/api +功能:获取API性能统计 + +GET /api/system/performance/database +功能:获取数据库性能统计 +``` \ No newline at end of file diff --git a/docs/安全和权限管理文档.md b/docs/安全和权限管理文档.md deleted file mode 100644 index e8c7e3e..0000000 --- a/docs/安全和权限管理文档.md +++ /dev/null @@ -1,2515 +0,0 @@ -# 解班客安全和权限管理文档 - -## 📋 概述 - -本文档详细描述解班客项目的安全架构、权限管理体系、安全防护措施和安全最佳实践。通过多层次的安全防护,确保系统和用户数据的安全性。 - -## 🎯 安全目标 - -### 核心安全原则 -- **最小权限原则**: 用户和系统组件仅获得完成任务所需的最小权限 -- **深度防御**: 多层安全防护,避免单点失效 -- **零信任架构**: 不信任任何内部或外部实体,持续验证 -- **数据保护**: 全生命周期数据安全保护 -- **合规性**: 符合相关法律法规和行业标准 - -### 安全目标 -- **身份认证**: 确保用户身份的真实性和唯一性 -- **访问控制**: 基于角色和权限的精细化访问控制 -- **数据安全**: 敏感数据加密存储和传输 -- **系统安全**: 防范各类网络攻击和安全威胁 -- **审计追踪**: 完整的操作日志和安全审计 - -## 🏗️ 安全架构 - -### 整体安全架构 - -```mermaid -graph TB - subgraph "外部防护层" - A[CDN/WAF] --> B[负载均衡器] - B --> C[反向代理] - end - - subgraph "应用层安全" - C --> D[API网关] - D --> E[身份认证] - E --> F[权限控制] - F --> G[业务逻辑] - end - - subgraph "数据层安全" - G --> H[数据加密] - H --> I[数据库] - I --> J[备份系统] - end - - subgraph "监控层" - K[安全监控] --> L[日志分析] - L --> M[告警系统] - M --> N[事件响应] - end - - style A fill:#ff9999 - style E fill:#99ccff - style H fill:#99ff99 - style K fill:#ffcc99 -``` - -### 安全分层 - -#### 1. 网络安全层 -- **防火墙配置**: 端口访问控制和流量过滤 -- **DDoS防护**: 分布式拒绝服务攻击防护 -- **SSL/TLS加密**: 数据传输加密 -- **VPN访问**: 管理员远程安全访问 - -#### 2. 应用安全层 -- **身份认证**: JWT令牌和多因素认证 -- **权限控制**: RBAC基于角色的访问控制 -- **输入验证**: 防止注入攻击 -- **会话管理**: 安全的会话处理 - -#### 3. 数据安全层 -- **数据加密**: 敏感数据加密存储 -- **数据脱敏**: 测试环境数据脱敏 -- **备份安全**: 加密备份和异地存储 -- **数据销毁**: 安全的数据删除 - -## 🔐 身份认证系统 - -### JWT认证机制 - -#### Token结构 -```javascript -// JWT Token结构 -{ - "header": { - "alg": "HS256", - "typ": "JWT" - }, - "payload": { - "user_id": 12345, - "username": "user@example.com", - "role": "user", - "permissions": ["read:animals", "create:adoption"], - "iat": 1640995200, - "exp": 1641081600, - "jti": "unique-token-id" - }, - "signature": "encrypted-signature" -} -``` - -#### 认证流程 -```javascript -// 用户登录认证 -async function authenticateUser(credentials) { - try { - // 1. 验证用户凭据 - const user = await validateCredentials(credentials) - if (!user) { - throw new Error('用户名或密码错误') - } - - // 2. 检查账户状态 - if (user.status !== 'active') { - throw new Error('账户已被禁用') - } - - // 3. 记录登录日志 - await logSecurityEvent({ - type: 'LOGIN_SUCCESS', - user_id: user.id, - ip_address: credentials.ip, - user_agent: credentials.userAgent, - timestamp: new Date() - }) - - // 4. 生成访问令牌 - const accessToken = generateAccessToken(user) - const refreshToken = generateRefreshToken(user) - - // 5. 存储刷新令牌 - await storeRefreshToken(user.id, refreshToken) - - return { - access_token: accessToken, - refresh_token: refreshToken, - expires_in: 3600, - user: { - id: user.id, - username: user.username, - role: user.role, - permissions: user.permissions - } - } - } catch (error) { - // 记录失败日志 - await logSecurityEvent({ - type: 'LOGIN_FAILED', - username: credentials.username, - ip_address: credentials.ip, - error: error.message, - timestamp: new Date() - }) - throw error - } -} - -// 生成访问令牌 -function generateAccessToken(user) { - const payload = { - user_id: user.id, - username: user.username, - role: user.role, - permissions: user.permissions, - iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 3600, // 1小时过期 - jti: generateUniqueId() - } - - return jwt.sign(payload, process.env.JWT_SECRET, { - algorithm: 'HS256' - }) -} - -// 令牌验证中间件 -function verifyToken(req, res, next) { - try { - const token = extractTokenFromHeader(req.headers.authorization) - if (!token) { - return res.status(401).json({ error: '缺少访问令牌' }) - } - - // 验证令牌 - const decoded = jwt.verify(token, process.env.JWT_SECRET) - - // 检查令牌是否在黑名单中 - if (await isTokenBlacklisted(decoded.jti)) { - return res.status(401).json({ error: '令牌已失效' }) - } - - // 将用户信息添加到请求对象 - req.user = { - id: decoded.user_id, - username: decoded.username, - role: decoded.role, - permissions: decoded.permissions - } - - next() - } catch (error) { - if (error.name === 'TokenExpiredError') { - return res.status(401).json({ error: '令牌已过期' }) - } else if (error.name === 'JsonWebTokenError') { - return res.status(401).json({ error: '无效的令牌' }) - } - return res.status(500).json({ error: '令牌验证失败' }) - } -} -``` - -### 多因素认证 (MFA) - -#### 短信验证码 -```javascript -// 发送短信验证码 -async function sendSMSCode(phoneNumber, purpose) { - try { - // 1. 生成6位数字验证码 - const code = Math.floor(100000 + Math.random() * 900000).toString() - - // 2. 设置过期时间(5分钟) - const expiresAt = new Date(Date.now() + 5 * 60 * 1000) - - // 3. 存储验证码 - await redis.setex( - `sms_code:${phoneNumber}:${purpose}`, - 300, // 5分钟过期 - JSON.stringify({ - code: await bcrypt.hash(code, 10), // 加密存储 - attempts: 0, - created_at: new Date() - }) - ) - - // 4. 发送短信 - await smsService.send({ - to: phoneNumber, - message: `【解班客】您的验证码是:${code},5分钟内有效,请勿泄露。` - }) - - // 5. 记录发送日志 - await logSecurityEvent({ - type: 'SMS_CODE_SENT', - phone_number: phoneNumber, - purpose: purpose, - timestamp: new Date() - }) - - return { success: true, message: '验证码已发送' } - } catch (error) { - logger.error('发送短信验证码失败:', error) - throw new Error('发送验证码失败') - } -} - -// 验证短信验证码 -async function verifySMSCode(phoneNumber, code, purpose) { - try { - const key = `sms_code:${phoneNumber}:${purpose}` - const storedData = await redis.get(key) - - if (!storedData) { - throw new Error('验证码已过期或不存在') - } - - const { code: hashedCode, attempts } = JSON.parse(storedData) - - // 检查尝试次数 - if (attempts >= 3) { - await redis.del(key) - throw new Error('验证码尝试次数过多,请重新获取') - } - - // 验证验证码 - const isValid = await bcrypt.compare(code, hashedCode) - - if (!isValid) { - // 增加尝试次数 - await redis.setex( - key, - await redis.ttl(key), - JSON.stringify({ - code: hashedCode, - attempts: attempts + 1, - created_at: new Date() - }) - ) - throw new Error('验证码错误') - } - - // 验证成功,删除验证码 - await redis.del(key) - - // 记录验证日志 - await logSecurityEvent({ - type: 'SMS_CODE_VERIFIED', - phone_number: phoneNumber, - purpose: purpose, - timestamp: new Date() - }) - - return { success: true, message: '验证码验证成功' } - } catch (error) { - logger.error('验证短信验证码失败:', error) - throw error - } -} -``` - -#### TOTP认证器 -```javascript -// TOTP (Time-based One-Time Password) 实现 -const speakeasy = require('speakeasy') -const QRCode = require('qrcode') - -// 生成TOTP密钥 -async function generateTOTPSecret(userId) { - const secret = speakeasy.generateSecret({ - name: `解班客 (${userId})`, - issuer: '解班客', - length: 32 - }) - - // 存储密钥到数据库 - await UserSecurity.create({ - user_id: userId, - totp_secret: encrypt(secret.base32), - totp_enabled: false, - created_at: new Date() - }) - - // 生成二维码 - const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url) - - return { - secret: secret.base32, - qr_code: qrCodeUrl, - manual_entry_key: secret.base32 - } -} - -// 验证TOTP令牌 -async function verifyTOTPToken(userId, token) { - try { - const userSecurity = await UserSecurity.findOne({ - where: { user_id: userId, totp_enabled: true } - }) - - if (!userSecurity) { - throw new Error('TOTP未启用') - } - - const secret = decrypt(userSecurity.totp_secret) - - const verified = speakeasy.totp.verify({ - secret: secret, - encoding: 'base32', - token: token, - window: 2 // 允许时间窗口偏差 - }) - - if (!verified) { - // 记录失败尝试 - await logSecurityEvent({ - type: 'TOTP_VERIFICATION_FAILED', - user_id: userId, - timestamp: new Date() - }) - throw new Error('TOTP令牌无效') - } - - // 记录成功验证 - await logSecurityEvent({ - type: 'TOTP_VERIFICATION_SUCCESS', - user_id: userId, - timestamp: new Date() - }) - - return { success: true } - } catch (error) { - logger.error('TOTP验证失败:', error) - throw error - } -} -``` - -## 👥 权限管理系统 - -### RBAC权限模型 - -#### 权限数据模型 -```sql --- 角色表 -CREATE TABLE roles ( - id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(50) NOT NULL UNIQUE, - display_name VARCHAR(100) NOT NULL, - description TEXT, - is_system BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- 权限表 -CREATE TABLE permissions ( - id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(100) NOT NULL UNIQUE, - display_name VARCHAR(100) NOT NULL, - description TEXT, - resource VARCHAR(50) NOT NULL, - action VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- 角色权限关联表 -CREATE TABLE role_permissions ( - id INT PRIMARY KEY AUTO_INCREMENT, - role_id INT NOT NULL, - permission_id INT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, - FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE, - UNIQUE KEY unique_role_permission (role_id, permission_id) -); - --- 用户角色关联表 -CREATE TABLE user_roles ( - id INT PRIMARY KEY AUTO_INCREMENT, - user_id INT NOT NULL, - role_id INT NOT NULL, - assigned_by INT, - assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NULL, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, - FOREIGN KEY (assigned_by) REFERENCES users(id), - UNIQUE KEY unique_user_role (user_id, role_id) -); - --- 用户直接权限表(特殊权限) -CREATE TABLE user_permissions ( - id INT PRIMARY KEY AUTO_INCREMENT, - user_id INT NOT NULL, - permission_id INT NOT NULL, - granted_by INT, - granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NULL, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE, - FOREIGN KEY (granted_by) REFERENCES users(id), - UNIQUE KEY unique_user_permission (user_id, permission_id) -); -``` - -#### 权限检查中间件 -```javascript -// 权限检查中间件 -function requirePermission(resource, action) { - return async (req, res, next) => { - try { - const userId = req.user.id - const hasPermission = await checkUserPermission(userId, resource, action) - - if (!hasPermission) { - // 记录权限拒绝日志 - await logSecurityEvent({ - type: 'PERMISSION_DENIED', - user_id: userId, - resource: resource, - action: action, - ip_address: req.ip, - user_agent: req.get('User-Agent'), - timestamp: new Date() - }) - - return res.status(403).json({ - error: '权限不足', - message: `您没有执行 ${action} 操作 ${resource} 的权限` - }) - } - - next() - } catch (error) { - logger.error('权限检查失败:', error) - return res.status(500).json({ error: '权限检查失败' }) - } - } -} - -// 检查用户权限 -async function checkUserPermission(userId, resource, action) { - try { - // 1. 检查用户直接权限 - const directPermission = await UserPermission.findOne({ - include: [{ - model: Permission, - where: { resource, action } - }], - where: { - user_id: userId, - [Op.or]: [ - { expires_at: null }, - { expires_at: { [Op.gt]: new Date() } } - ] - } - }) - - if (directPermission) { - return true - } - - // 2. 检查角色权限 - const rolePermissions = await UserRole.findAll({ - include: [{ - model: Role, - include: [{ - model: Permission, - where: { resource, action }, - through: { attributes: [] } - }] - }], - where: { - user_id: userId, - [Op.or]: [ - { expires_at: null }, - { expires_at: { [Op.gt]: new Date() } } - ] - } - }) - - return rolePermissions.length > 0 - } catch (error) { - logger.error('权限检查错误:', error) - return false - } -} - -// 获取用户所有权限 -async function getUserPermissions(userId) { - try { - const permissions = new Set() - - // 1. 获取直接权限 - const directPermissions = await UserPermission.findAll({ - include: [Permission], - where: { - user_id: userId, - [Op.or]: [ - { expires_at: null }, - { expires_at: { [Op.gt]: new Date() } } - ] - } - }) - - directPermissions.forEach(up => { - permissions.add(`${up.Permission.resource}:${up.Permission.action}`) - }) - - // 2. 获取角色权限 - const rolePermissions = await UserRole.findAll({ - include: [{ - model: Role, - include: [{ - model: Permission, - through: { attributes: [] } - }] - }], - where: { - user_id: userId, - [Op.or]: [ - { expires_at: null }, - { expires_at: { [Op.gt]: new Date() } } - ] - } - }) - - rolePermissions.forEach(ur => { - ur.Role.Permissions.forEach(permission => { - permissions.add(`${permission.resource}:${permission.action}`) - }) - }) - - return Array.from(permissions) - } catch (error) { - logger.error('获取用户权限失败:', error) - return [] - } -} -``` - -### 角色管理 - -#### 预定义角色 -```javascript -// 系统预定义角色 -const SYSTEM_ROLES = { - SUPER_ADMIN: { - name: 'super_admin', - display_name: '超级管理员', - description: '拥有系统所有权限', - permissions: ['*:*'] // 通配符表示所有权限 - }, - - ADMIN: { - name: 'admin', - display_name: '管理员', - description: '系统管理员,可管理用户和内容', - permissions: [ - 'users:read', 'users:create', 'users:update', 'users:delete', - 'animals:read', 'animals:create', 'animals:update', 'animals:delete', - 'adoptions:read', 'adoptions:update', 'adoptions:approve', - 'content:read', 'content:create', 'content:update', 'content:delete', - 'reports:read', 'system:monitor' - ] - }, - - MODERATOR: { - name: 'moderator', - display_name: '内容审核员', - description: '负责内容审核和动物信息管理', - permissions: [ - 'animals:read', 'animals:create', 'animals:update', - 'adoptions:read', 'adoptions:update', - 'content:read', 'content:update', - 'reports:read' - ] - }, - - USER: { - name: 'user', - display_name: '普通用户', - description: '普通用户,可浏览和申请认领', - permissions: [ - 'animals:read', - 'adoptions:create', 'adoptions:read_own', - 'profile:read', 'profile:update' - ] - }, - - VOLUNTEER: { - name: 'volunteer', - display_name: '志愿者', - description: '志愿者,可协助动物信息维护', - permissions: [ - 'animals:read', 'animals:update', - 'adoptions:read', - 'content:read', - 'profile:read', 'profile:update' - ] - } -} - -// 初始化系统角色 -async function initializeSystemRoles() { - try { - for (const [key, roleData] of Object.entries(SYSTEM_ROLES)) { - // 创建或更新角色 - const [role] = await Role.findOrCreate({ - where: { name: roleData.name }, - defaults: { - display_name: roleData.display_name, - description: roleData.description, - is_system: true - } - }) - - // 处理权限 - if (roleData.permissions.includes('*:*')) { - // 超级管理员拥有所有权限 - const allPermissions = await Permission.findAll() - await role.setPermissions(allPermissions) - } else { - // 设置指定权限 - const permissions = await Permission.findAll({ - where: { - name: { [Op.in]: roleData.permissions } - } - }) - await role.setPermissions(permissions) - } - } - - logger.info('系统角色初始化完成') - } catch (error) { - logger.error('系统角色初始化失败:', error) - throw error - } -} -``` - -#### 动态权限管理 -```javascript -// 权限管理服务 -class PermissionService { - // 创建权限 - static async createPermission(permissionData) { - try { - const permission = await Permission.create({ - name: `${permissionData.resource}:${permissionData.action}`, - display_name: permissionData.display_name, - description: permissionData.description, - resource: permissionData.resource, - action: permissionData.action - }) - - logger.info(`权限创建成功: ${permission.name}`) - return permission - } catch (error) { - logger.error('权限创建失败:', error) - throw error - } - } - - // 分配角色给用户 - static async assignRoleToUser(userId, roleId, assignedBy, expiresAt = null) { - try { - const userRole = await UserRole.create({ - user_id: userId, - role_id: roleId, - assigned_by: assignedBy, - expires_at: expiresAt - }) - - // 记录权限变更日志 - await logSecurityEvent({ - type: 'ROLE_ASSIGNED', - user_id: userId, - role_id: roleId, - assigned_by: assignedBy, - expires_at: expiresAt, - timestamp: new Date() - }) - - // 清除用户权限缓存 - await this.clearUserPermissionCache(userId) - - return userRole - } catch (error) { - logger.error('角色分配失败:', error) - throw error - } - } - - // 撤销用户角色 - static async revokeRoleFromUser(userId, roleId, revokedBy) { - try { - const result = await UserRole.destroy({ - where: { user_id: userId, role_id: roleId } - }) - - if (result > 0) { - // 记录权限变更日志 - await logSecurityEvent({ - type: 'ROLE_REVOKED', - user_id: userId, - role_id: roleId, - revoked_by: revokedBy, - timestamp: new Date() - }) - - // 清除用户权限缓存 - await this.clearUserPermissionCache(userId) - } - - return result > 0 - } catch (error) { - logger.error('角色撤销失败:', error) - throw error - } - } - - // 授予用户直接权限 - static async grantPermissionToUser(userId, permissionId, grantedBy, expiresAt = null) { - try { - const userPermission = await UserPermission.create({ - user_id: userId, - permission_id: permissionId, - granted_by: grantedBy, - expires_at: expiresAt - }) - - // 记录权限变更日志 - await logSecurityEvent({ - type: 'PERMISSION_GRANTED', - user_id: userId, - permission_id: permissionId, - granted_by: grantedBy, - expires_at: expiresAt, - timestamp: new Date() - }) - - // 清除用户权限缓存 - await this.clearUserPermissionCache(userId) - - return userPermission - } catch (error) { - logger.error('权限授予失败:', error) - throw error - } - } - - // 清除用户权限缓存 - static async clearUserPermissionCache(userId) { - try { - await redis.del(`user_permissions:${userId}`) - logger.info(`用户权限缓存已清除: ${userId}`) - } catch (error) { - logger.error('清除权限缓存失败:', error) - } - } - - // 获取用户权限(带缓存) - static async getUserPermissionsWithCache(userId) { - try { - const cacheKey = `user_permissions:${userId}` - let permissions = await redis.get(cacheKey) - - if (permissions) { - return JSON.parse(permissions) - } - - permissions = await getUserPermissions(userId) - - // 缓存权限信息(5分钟) - await redis.setex(cacheKey, 300, JSON.stringify(permissions)) - - return permissions - } catch (error) { - logger.error('获取用户权限失败:', error) - return [] - } - } -} -``` - -## 🛡️ 安全防护措施 - -### 输入验证和过滤 - -#### SQL注入防护 -```javascript -// 使用参数化查询防止SQL注入 -const { QueryTypes } = require('sequelize') - -// 错误示例 - 容易受到SQL注入攻击 -async function searchAnimalsUnsafe(keyword) { - const query = `SELECT * FROM animals WHERE name LIKE '%${keyword}%'` - return await sequelize.query(query, { type: QueryTypes.SELECT }) -} - -// 正确示例 - 使用参数化查询 -async function searchAnimalsSafe(keyword) { - const query = ` - SELECT * FROM animals - WHERE name LIKE :keyword - OR description LIKE :keyword - ` - return await sequelize.query(query, { - replacements: { keyword: `%${keyword}%` }, - type: QueryTypes.SELECT - }) -} - -// 使用ORM的安全查询 -async function searchAnimalsORM(keyword) { - return await Animal.findAll({ - where: { - [Op.or]: [ - { name: { [Op.like]: `%${keyword}%` } }, - { description: { [Op.like]: `%${keyword}%` } } - ] - } - }) -} -``` - -#### XSS防护 -```javascript -const DOMPurify = require('isomorphic-dompurify') -const validator = require('validator') - -// XSS过滤中间件 -function xssProtection(req, res, next) { - // 递归清理对象中的所有字符串 - function sanitizeObject(obj) { - if (typeof obj === 'string') { - return DOMPurify.sanitize(obj) - } else if (Array.isArray(obj)) { - return obj.map(sanitizeObject) - } else if (obj && typeof obj === 'object') { - const sanitized = {} - for (const [key, value] of Object.entries(obj)) { - sanitized[key] = sanitizeObject(value) - } - return sanitized - } - return obj - } - - // 清理请求体 - if (req.body) { - req.body = sanitizeObject(req.body) - } - - // 清理查询参数 - if (req.query) { - req.query = sanitizeObject(req.query) - } - - next() -} - -// 输入验证函数 -function validateInput(data, rules) { - const errors = [] - - for (const [field, rule] of Object.entries(rules)) { - const value = data[field] - - // 必填验证 - if (rule.required && (!value || value.trim() === '')) { - errors.push(`${field} 是必填字段`) - continue - } - - if (value) { - // 长度验证 - if (rule.minLength && value.length < rule.minLength) { - errors.push(`${field} 长度不能少于 ${rule.minLength} 个字符`) - } - - if (rule.maxLength && value.length > rule.maxLength) { - errors.push(`${field} 长度不能超过 ${rule.maxLength} 个字符`) - } - - // 格式验证 - if (rule.type === 'email' && !validator.isEmail(value)) { - errors.push(`${field} 格式不正确`) - } - - if (rule.type === 'phone' && !validator.isMobilePhone(value, 'zh-CN')) { - errors.push(`${field} 手机号格式不正确`) - } - - if (rule.type === 'url' && !validator.isURL(value)) { - errors.push(`${field} URL格式不正确`) - } - - // 自定义正则验证 - if (rule.pattern && !rule.pattern.test(value)) { - errors.push(`${field} 格式不符合要求`) - } - - // 危险字符检测 - if (containsDangerousChars(value)) { - errors.push(`${field} 包含非法字符`) - } - } - } - - return errors -} - -// 检测危险字符 -function containsDangerousChars(input) { - const dangerousPatterns = [ - /)<[^<]*)*<\/script>/gi, - /javascript:/gi, - /on\w+\s*=/gi, - /eval\s*\(/gi, - /expression\s*\(/gi - ] - - return dangerousPatterns.some(pattern => pattern.test(input)) -} -``` - -#### CSRF防护 -```javascript -const csrf = require('csurf') -const cookieParser = require('cookie-parser') - -// CSRF保护中间件配置 -const csrfProtection = csrf({ - cookie: { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict' - } -}) - -// 为前端提供CSRF令牌 -app.get('/api/csrf-token', csrfProtection, (req, res) => { - res.json({ csrfToken: req.csrfToken() }) -}) - -// 应用CSRF保护到需要的路由 -app.use('/api/admin', csrfProtection) -app.use('/api/user/profile', csrfProtection) - -// 自定义CSRF错误处理 -app.use((err, req, res, next) => { - if (err.code === 'EBADCSRFTOKEN') { - return res.status(403).json({ - error: 'CSRF令牌无效', - message: '请刷新页面后重试' - }) - } - next(err) -}) -``` - -### 速率限制 - -#### API速率限制 -```javascript -const rateLimit = require('express-rate-limit') -const RedisStore = require('rate-limit-redis') -const redis = require('redis') - -// Redis客户端 -const redisClient = redis.createClient({ - host: process.env.REDIS_HOST, - port: process.env.REDIS_PORT -}) - -// 通用速率限制 -const generalLimiter = rateLimit({ - store: new RedisStore({ - client: redisClient, - prefix: 'rl:general:' - }), - windowMs: 15 * 60 * 1000, // 15分钟 - max: 100, // 每个IP最多100个请求 - message: { - error: '请求过于频繁', - message: '请稍后再试' - }, - standardHeaders: true, - legacyHeaders: false -}) - -// 登录速率限制 -const loginLimiter = rateLimit({ - store: new RedisStore({ - client: redisClient, - prefix: 'rl:login:' - }), - windowMs: 15 * 60 * 1000, // 15分钟 - max: 5, // 每个IP最多5次登录尝试 - skipSuccessfulRequests: true, - message: { - error: '登录尝试过于频繁', - message: '请15分钟后再试' - } -}) - -// 注册速率限制 -const registerLimiter = rateLimit({ - store: new RedisStore({ - client: redisClient, - prefix: 'rl:register:' - }), - windowMs: 60 * 60 * 1000, // 1小时 - max: 3, // 每个IP每小时最多3次注册 - message: { - error: '注册过于频繁', - message: '请1小时后再试' - } -}) - -// 短信验证码速率限制 -const smsLimiter = rateLimit({ - store: new RedisStore({ - client: redisClient, - prefix: 'rl:sms:' - }), - windowMs: 60 * 1000, // 1分钟 - max: 1, // 每分钟最多1条短信 - keyGenerator: (req) => { - return req.body.phone_number || req.ip - }, - message: { - error: '短信发送过于频繁', - message: '请1分钟后再试' - } -}) - -// 应用速率限制 -app.use('/api', generalLimiter) -app.use('/api/auth/login', loginLimiter) -app.use('/api/auth/register', registerLimiter) -app.use('/api/auth/send-sms', smsLimiter) - -// 自定义速率限制器 -class CustomRateLimiter { - constructor(options) { - this.windowMs = options.windowMs - this.max = options.max - this.keyGenerator = options.keyGenerator || ((req) => req.ip) - this.store = options.store || new Map() - } - - middleware() { - return async (req, res, next) => { - try { - const key = this.keyGenerator(req) - const now = Date.now() - const windowStart = now - this.windowMs - - // 获取当前窗口内的请求记录 - const requests = await this.getRequests(key, windowStart) - - if (requests.length >= this.max) { - return res.status(429).json({ - error: '请求过于频繁', - retryAfter: Math.ceil((requests[0].timestamp + this.windowMs - now) / 1000) - }) - } - - // 记录当前请求 - await this.recordRequest(key, now) - - next() - } catch (error) { - logger.error('速率限制检查失败:', error) - next() - } - } - } - - async getRequests(key, windowStart) { - // 从Redis获取请求记录 - const data = await redis.get(`rate_limit:${key}`) - if (!data) return [] - - const requests = JSON.parse(data) - return requests.filter(req => req.timestamp > windowStart) - } - - async recordRequest(key, timestamp) { - const requests = await this.getRequests(key, 0) - requests.push({ timestamp }) - - // 只保留窗口内的请求 - const windowStart = timestamp - this.windowMs - const validRequests = requests.filter(req => req.timestamp > windowStart) - - await redis.setex( - `rate_limit:${key}`, - Math.ceil(this.windowMs / 1000), - JSON.stringify(validRequests) - ) - } -} -``` - -### 数据加密 - -#### 敏感数据加密 -```javascript -const crypto = require('crypto') -const bcrypt = require('bcrypt') - -// 加密配置 -const ENCRYPTION_CONFIG = { - algorithm: 'aes-256-gcm', - keyLength: 32, - ivLength: 16, - tagLength: 16, - saltRounds: 12 -} - -// 生成加密密钥 -function generateEncryptionKey() { - return crypto.randomBytes(ENCRYPTION_CONFIG.keyLength) -} - -// 对称加密 -function encrypt(text, key = process.env.ENCRYPTION_KEY) { - try { - const iv = crypto.randomBytes(ENCRYPTION_CONFIG.ivLength) - const cipher = crypto.createCipher(ENCRYPTION_CONFIG.algorithm, key) - cipher.setAAD(Buffer.from('additional-data')) - - let encrypted = cipher.update(text, 'utf8', 'hex') - encrypted += cipher.final('hex') - - const tag = cipher.getAuthTag() - - return { - encrypted, - iv: iv.toString('hex'), - tag: tag.toString('hex') - } - } catch (error) { - logger.error('加密失败:', error) - throw new Error('数据加密失败') - } -} - -// 对称解密 -function decrypt(encryptedData, key = process.env.ENCRYPTION_KEY) { - try { - const { encrypted, iv, tag } = encryptedData - const decipher = crypto.createDecipher(ENCRYPTION_CONFIG.algorithm, key) - - decipher.setAuthTag(Buffer.from(tag, 'hex')) - decipher.setAAD(Buffer.from('additional-data')) - - let decrypted = decipher.update(encrypted, 'hex', 'utf8') - decrypted += decipher.final('utf8') - - return decrypted - } catch (error) { - logger.error('解密失败:', error) - throw new Error('数据解密失败') - } -} - -// 密码哈希 -async function hashPassword(password) { - try { - const salt = await bcrypt.genSalt(ENCRYPTION_CONFIG.saltRounds) - return await bcrypt.hash(password, salt) - } catch (error) { - logger.error('密码哈希失败:', error) - throw new Error('密码处理失败') - } -} - -// 密码验证 -async function verifyPassword(password, hashedPassword) { - try { - return await bcrypt.compare(password, hashedPassword) - } catch (error) { - logger.error('密码验证失败:', error) - return false - } -} - -// 敏感字段加密模型 -class EncryptedField { - constructor(value) { - this.value = value - } - - // 加密存储 - encrypt() { - if (!this.value) return null - return JSON.stringify(encrypt(this.value)) - } - - // 解密读取 - static decrypt(encryptedValue) { - if (!encryptedValue) return null - try { - const encryptedData = JSON.parse(encryptedValue) - return decrypt(encryptedData) - } catch (error) { - logger.error('字段解密失败:', error) - return null - } - } -} - -// 数据库模型中使用加密字段 -const User = sequelize.define('User', { - username: DataTypes.STRING, - email: DataTypes.STRING, - phone: { - type: DataTypes.TEXT, - set(value) { - if (value) { - const encrypted = new EncryptedField(value) - this.setDataValue('phone', encrypted.encrypt()) - } - }, - get() { - const encryptedValue = this.getDataValue('phone') - return EncryptedField.decrypt(encryptedValue) - } - }, - id_card: { - type: DataTypes.TEXT, - set(value) { - if (value) { - const encrypted = new EncryptedField(value) - this.setDataValue('id_card', encrypted.encrypt()) - } - }, - get() { - const encryptedValue = this.getDataValue('id_card') - return EncryptedField.decrypt(encryptedValue) - } - } -}) -``` - -## 📊 安全监控和审计 - -### 安全事件日志 - -#### 日志记录系统 -```javascript -// 安全事件类型 -const SECURITY_EVENT_TYPES = { - // 认证相关 - LOGIN_SUCCESS: 'login_success', - LOGIN_FAILED: 'login_failed', - LOGOUT: 'logout', - PASSWORD_CHANGED: 'password_changed', - - // 权限相关 - PERMISSION_DENIED: 'permission_denied', - ROLE_ASSIGNED: 'role_assigned', - ROLE_REVOKED: 'role_revoked', - - // 安全威胁 - SUSPICIOUS_ACTIVITY: 'suspicious_activity', - BRUTE_FORCE_ATTEMPT: 'brute_force_attempt', - SQL_INJECTION_ATTEMPT: 'sql_injection_attempt', - XSS_ATTEMPT: 'xss_attempt', - - // 数据操作 - DATA_ACCESS: 'data_access', - DATA_MODIFICATION: 'data_modification', - DATA_DELETION: 'data_deletion', - - // 系统事件 - SYSTEM_ERROR: 'system_error', - CONFIGURATION_CHANGED: 'configuration_changed' -} - -// 安全事件日志模型 -const SecurityLog = sequelize.define('SecurityLog', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - event_type: { - type: DataTypes.STRING, - allowNull: false - }, - user_id: { - type: DataTypes.INTEGER, - allowNull: true - }, - ip_address: { - type: DataTypes.STRING, - allowNull: true - }, - user_agent: { - type: DataTypes.TEXT, - allowNull: true - }, - resource: { - type: DataTypes.STRING, - allowNull: true - }, - action: { - type: DataTypes.STRING, - allowNull: true - }, - details: { - type: DataTypes.JSON, - allowNull: true - }, - risk_level: { - type: DataTypes.ENUM('low', 'medium', 'high', 'critical'), - defaultValue: 'low' - }, - timestamp: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - } -}) - -// 记录安全事件 -async function logSecurityEvent(eventData) { - try { - const logEntry = await SecurityLog.create({ - event_type: eventData.type, - user_id: eventData.user_id, - ip_address: eventData.ip_address, - user_agent: eventData.user_agent, - resource: eventData.resource, - action: eventData.action, - details: eventData.details, - risk_level: eventData.risk_level || 'low', - timestamp: eventData.timestamp || new Date() - }) - - // 高风险事件立即告警 - if (eventData.risk_level === 'high' || eventData.risk_level === 'critical') { - await sendSecurityAlert(logEntry) - } - - return logEntry - } catch (error) { - logger.error('安全事件记录失败:', error) - } -} - -// 安全事件分析 -class SecurityAnalyzer { - // 检测暴力破解攻击 - static async detectBruteForceAttack(ip, timeWindow = 15 * 60 * 1000) { - const since = new Date(Date.now() - timeWindow) - - const failedAttempts = await SecurityLog.count({ - where: { - event_type: SECURITY_EVENT_TYPES.LOGIN_FAILED, - ip_address: ip, - timestamp: { [Op.gte]: since } - } - }) - - if (failedAttempts >= 5) { - await logSecurityEvent({ - type: SECURITY_EVENT_TYPES.BRUTE_FORCE_ATTEMPT, - ip_address: ip, - details: { failed_attempts: failedAttempts }, - risk_level: 'high' - }) - - // 临时封禁IP - await this.blockIP(ip, 60 * 60 * 1000) // 1小时 - - return true - } - - return false - } - - // 检测异常登录 - static async detectAnomalousLogin(userId, currentIP, userAgent) { - // 获取用户历史登录记录 - const recentLogins = await SecurityLog.findAll({ - where: { - event_type: SECURITY_EVENT_TYPES.LOGIN_SUCCESS, - user_id: userId, - timestamp: { [Op.gte]: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } - }, - order: [['timestamp', 'DESC']], - limit: 10 - }) - - // 检查IP地址异常 - const knownIPs = recentLogins.map(log => log.ip_address) - const isNewIP = !knownIPs.includes(currentIP) - - // 检查设备异常 - const knownUserAgents = recentLogins.map(log => log.user_agent) - const isNewDevice = !knownUserAgents.includes(userAgent) - - if (isNewIP && isNewDevice) { - await logSecurityEvent({ - type: SECURITY_EVENT_TYPES.SUSPICIOUS_ACTIVITY, - user_id: userId, - ip_address: currentIP, - user_agent: userAgent, - details: { - reason: 'new_ip_and_device', - known_ips: knownIPs.slice(0, 3), - known_devices: knownUserAgents.slice(0, 3) - }, - risk_level: 'medium' - }) - - return true - } - - return false - } - - // 检测权限滥用 - static async detectPrivilegeAbuse(userId, timeWindow = 60 * 60 * 1000) { - const since = new Date(Date.now() - timeWindow) - - const privilegedActions = await SecurityLog.count({ - where: { - event_type: { - [Op.in]: [ - SECURITY_EVENT_TYPES.ROLE_ASSIGNED, - SECURITY_EVENT_TYPES.DATA_MODIFICATION, - SECURITY_EVENT_TYPES.DATA_DELETION - ] - }, - user_id: userId, - timestamp: { [Op.gte]: since } - } - }) - - // 如果1小时内特权操作超过阈值 - if (privilegedActions > 20) { - await logSecurityEvent({ - type: SECURITY_EVENT_TYPES.SUSPICIOUS_ACTIVITY, - user_id: userId, - details: { - reason: 'excessive_privileged_actions', - action_count: privilegedActions - }, - risk_level: 'high' - }) - - return true - } - - return false - } - - // IP封禁 - static async blockIP(ip, duration) { - const expiresAt = new Date(Date.now() + duration) - - await redis.setex( - `blocked_ip:${ip}`, - Math.ceil(duration / 1000), - JSON.stringify({ - blocked_at: new Date(), - expires_at: expiresAt, - reason: 'security_violation' - }) - ) - - logger.warn(`IP已被封禁: ${ip}, 到期时间: ${expiresAt}`) - } - - // 检查IP是否被封禁 - static async isIPBlocked(ip) { - const blockData = await redis.get(`blocked_ip:${ip}`) - return !!blockData - } -} - -// IP封禁检查中间件 -function checkIPBlock(req, res, next) { - return async (req, res, next) => { - try { - const isBlocked = await SecurityAnalyzer.isIPBlocked(req.ip) - - if (isBlocked) { - await logSecurityEvent({ - type: SECURITY_EVENT_TYPES.SUSPICIOUS_ACTIVITY, - ip_address: req.ip, - details: { reason: 'blocked_ip_access_attempt' }, - risk_level: 'medium' - }) - - return res.status(403).json({ - error: '访问被拒绝', - message: '您的IP地址已被临时封禁' - }) - } - - next() - } catch (error) { - logger.error('IP封禁检查失败:', error) - next() - } - } -} -``` - -### 实时监控和告警 - -#### 安全告警系统 -```javascript -// 告警配置 -const ALERT_CONFIG = { - channels: { - email: { - enabled: true, - recipients: ['security@jiebanke.com', 'admin@jiebanke.com'] - }, - sms: { - enabled: true, - recipients: ['+8613800138000'] - }, - webhook: { - enabled: true, - url: 'https://hooks.slack.com/services/xxx' - } - }, - thresholds: { - failed_logins: 10, - permission_denials: 20, - suspicious_activities: 5 - } -} - -// 告警服务 -class SecurityAlertService { - // 发送安全告警 - static async sendSecurityAlert(logEntry) { - try { - const alertData = { - title: `安全告警 - ${this.getEventTypeName(logEntry.event_type)}`, - message: this.formatAlertMessage(logEntry), - severity: logEntry.risk_level, - timestamp: logEntry.timestamp, - details: logEntry - } - - // 发送邮件告警 - if (ALERT_CONFIG.channels.email.enabled) { - await this.sendEmailAlert(alertData) - } - - // 发送短信告警(仅高危事件) - if (ALERT_CONFIG.channels.sms.enabled && - ['high', 'critical'].includes(logEntry.risk_level)) { - await this.sendSMSAlert(alertData) - } - - // 发送Webhook告警 - if (ALERT_CONFIG.channels.webhook.enabled) { - await this.sendWebhookAlert(alertData) - } - - logger.info(`安全告警已发送: ${logEntry.event_type}`) - } catch (error) { - logger.error('发送安全告警失败:', error) - } - } - - // 格式化告警消息 - static formatAlertMessage(logEntry) { - const messages = { - [SECURITY_EVENT_TYPES.BRUTE_FORCE_ATTEMPT]: - `检测到暴力破解攻击,IP: ${logEntry.ip_address}`, - [SECURITY_EVENT_TYPES.SUSPICIOUS_ACTIVITY]: - `检测到可疑活动,用户: ${logEntry.user_id}, IP: ${logEntry.ip_address}`, - [SECURITY_EVENT_TYPES.SQL_INJECTION_ATTEMPT]: - `检测到SQL注入攻击尝试,IP: ${logEntry.ip_address}`, - [SECURITY_EVENT_TYPES.XSS_ATTEMPT]: - `检测到XSS攻击尝试,IP: ${logEntry.ip_address}`, - [SECURITY_EVENT_TYPES.PERMISSION_DENIED]: - `权限拒绝事件,用户: ${logEntry.user_id}, 资源: ${logEntry.resource}` - } - - return messages[logEntry.event_type] || `安全事件: ${logEntry.event_type}` - } - - // 发送邮件告警 - static async sendEmailAlert(alertData) { - const emailContent = ` -

🚨 ${alertData.title}

-

时间: ${alertData.timestamp}

-

严重程度: ${alertData.severity}

-

描述: ${alertData.message}

- -

详细信息:

-
${JSON.stringify(alertData.details, null, 2)}
- -

请立即检查系统安全状况。

- ` - - await emailService.send({ - to: ALERT_CONFIG.channels.email.recipients, - subject: `[解班客安全告警] ${alertData.title}`, - html: emailContent - }) - } - - // 发送短信告警 - static async sendSMSAlert(alertData) { - const message = `【解班客安全告警】${alertData.message},请立即处理。时间:${alertData.timestamp}` - - for (const recipient of ALERT_CONFIG.channels.sms.recipients) { - await smsService.send({ - to: recipient, - message: message - }) - } - } - - // 发送Webhook告警 - static async sendWebhookAlert(alertData) { - const payload = { - text: `🚨 ${alertData.title}`, - attachments: [{ - color: this.getSeverityColor(alertData.severity), - fields: [ - { title: '时间', value: alertData.timestamp, short: true }, - { title: '严重程度', value: alertData.severity, short: true }, - { title: '描述', value: alertData.message, short: false } - ] - }] - } - - await axios.post(ALERT_CONFIG.channels.webhook.url, payload) - } - - // 获取严重程度颜色 - static getSeverityColor(severity) { - const colors = { - low: '#36a64f', - medium: '#ff9500', - high: '#ff0000', - critical: '#8b0000' - } - return colors[severity] || '#cccccc' - } - - // 批量告警检查 - static async checkBatchAlerts() { - const timeWindow = 5 * 60 * 1000 // 5分钟 - const since = new Date(Date.now() - timeWindow) - - // 检查失败登录次数 - const failedLogins = await SecurityLog.count({ - where: { - event_type: SECURITY_EVENT_TYPES.LOGIN_FAILED, - timestamp: { [Op.gte]: since } - } - }) - - if (failedLogins >= ALERT_CONFIG.thresholds.failed_logins) { - await this.sendBatchAlert('大量登录失败', { - count: failedLogins, - timeWindow: '5分钟', - threshold: ALERT_CONFIG.thresholds.failed_logins - }) - } - - // 检查权限拒绝次数 - const permissionDenials = await SecurityLog.count({ - where: { - event_type: SECURITY_EVENT_TYPES.PERMISSION_DENIED, - timestamp: { [Op.gte]: since } - } - }) - - if (permissionDenials >= ALERT_CONFIG.thresholds.permission_denials) { - await this.sendBatchAlert('大量权限拒绝', { - count: permissionDenials, - timeWindow: '5分钟', - threshold: ALERT_CONFIG.thresholds.permission_denials - }) - } - } - - // 发送批量告警 - static async sendBatchAlert(title, data) { - const alertData = { - title: `批量安全事件 - ${title}`, - message: `在${data.timeWindow}内检测到${data.count}次${title}事件,超过阈值${data.threshold}`, - severity: 'high', - timestamp: new Date(), - details: data - } - - await this.sendSecurityAlert({ ...alertData, risk_level: 'high' }) - } -} - -// 定时检查批量告警 -setInterval(async () => { - try { - await SecurityAlertService.checkBatchAlerts() - } catch (error) { - logger.error('批量告警检查失败:', error) - } -}, 5 * 60 * 1000) // 每5分钟检查一次 -``` - -## 🔒 数据隐私保护 - -### 数据脱敏 - -#### 敏感数据脱敏 -```javascript -// 数据脱敏工具 -class DataMasking { - // 手机号脱敏 - static maskPhone(phone) { - if (!phone || phone.length < 11) return phone - return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') - } - - // 身份证号脱敏 - static maskIDCard(idCard) { - if (!idCard || idCard.length < 15) return idCard - return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2') - } - - // 邮箱脱敏 - static maskEmail(email) { - if (!email || !email.includes('@')) return email - const [username, domain] = email.split('@') - if (username.length <= 2) return email - const maskedUsername = username.charAt(0) + '*'.repeat(username.length - 2) + username.charAt(username.length - 1) - return `${maskedUsername}@${domain}` - } - - // 姓名脱敏 - static maskName(name) { - if (!name || name.length <= 1) return name - if (name.length === 2) { - return name.charAt(0) + '*' - } - return name.charAt(0) + '*'.repeat(name.length - 2) + name.charAt(name.length - 1) - } - - // 地址脱敏 - static maskAddress(address) { - if (!address || address.length <= 10) return address - return address.substring(0, 6) + '****' + address.substring(address.length - 4) - } - - // 银行卡号脱敏 - static maskBankCard(cardNumber) { - if (!cardNumber || cardNumber.length < 16) return cardNumber - return cardNumber.replace(/(\d{4})\d{8}(\d{4})/, '$1********$2') - } - - // 通用脱敏方法 - static maskSensitiveData(data, fields) { - const masked = { ...data } - - for (const field of fields) { - if (masked[field]) { - switch (field) { - case 'phone': - case 'mobile': - masked[field] = this.maskPhone(masked[field]) - break - case 'id_card': - case 'idCard': - masked[field] = this.maskIDCard(masked[field]) - break - case 'email': - masked[field] = this.maskEmail(masked[field]) - break - case 'name': - case 'real_name': - masked[field] = this.maskName(masked[field]) - break - case 'address': - masked[field] = this.maskAddress(masked[field]) - break - case 'bank_card': - masked[field] = this.maskBankCard(masked[field]) - break - default: - // 默认脱敏:显示前2位和后2位 - if (typeof masked[field] === 'string' && masked[field].length > 4) { - masked[field] = masked[field].substring(0, 2) + - '*'.repeat(masked[field].length - 4) + - masked[field].substring(masked[field].length - 2) - } - } - } - } - - return masked - } -} - -// API响应脱敏中间件 -function maskSensitiveResponse(sensitiveFields = []) { - return (req, res, next) => { - const originalJson = res.json - - res.json = function(data) { - if (data && typeof data === 'object') { - // 递归脱敏处理 - const maskedData = maskDataRecursively(data, sensitiveFields) - return originalJson.call(this, maskedData) - } - return originalJson.call(this, data) - } - - next() - } -} - -// 递归脱敏处理 -function maskDataRecursively(data, sensitiveFields) { - if (Array.isArray(data)) { - return data.map(item => maskDataRecursively(item, sensitiveFields)) - } else if (data && typeof data === 'object') { - return DataMasking.maskSensitiveData(data, sensitiveFields) - } - return data -} -``` - -### 数据备份和恢复 - -#### 安全备份策略 -```javascript -// 备份配置 -const BACKUP_CONFIG = { - schedule: { - full: '0 2 * * 0', // 每周日凌晨2点全量备份 - incremental: '0 2 * * 1-6', // 周一到周六增量备份 - log: '0 */6 * * *' // 每6小时备份日志 - }, - retention: { - full: 30, // 保留30天 - incremental: 7, // 保留7天 - log: 3 // 保留3天 - }, - encryption: { - enabled: true, - algorithm: 'aes-256-cbc', - keyRotation: 90 // 90天轮换密钥 - }, - storage: { - local: '/backup/local', - remote: 's3://jiebanke-backup', - offsite: 'backup-server-2' - } -} - -// 备份服务 -class BackupService { - // 数据库全量备份 - static async createFullBackup() { - try { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-') - const backupName = `full-backup-${timestamp}` - - logger.info(`开始全量备份: ${backupName}`) - - // 1. 创建数据库备份 - const dbBackupPath = await this.backupDatabase(backupName) - - // 2. 备份文件存储 - const filesBackupPath = await this.backupFiles(backupName) - - // 3. 备份配置文件 - const configBackupPath = await this.backupConfigs(backupName) - - // 4. 创建备份清单 - const manifest = { - backup_name: backupName, - backup_type: 'full', - created_at: new Date(), - database: dbBackupPath, - files: filesBackupPath, - configs: configBackupPath, - checksum: await this.calculateChecksum([dbBackupPath, filesBackupPath, configBackupPath]) - } - - // 5. 加密备份 - if (BACKUP_CONFIG.encryption.enabled) { - await this.encryptBackup(manifest) - } - - // 6. 上传到远程存储 - await this.uploadToRemoteStorage(manifest) - - // 7. 记录备份日志 - await this.logBackupEvent('FULL_BACKUP_SUCCESS', manifest) - - logger.info(`全量备份完成: ${backupName}`) - return manifest - } catch (error) { - logger.error('全量备份失败:', error) - await this.logBackupEvent('FULL_BACKUP_FAILED', { error: error.message }) - throw error - } - } - - // 数据库备份 - static async backupDatabase(backupName) { - const backupPath = path.join(BACKUP_CONFIG.storage.local, `${backupName}-db.sql`) - - const command = `mysqldump -h ${process.env.DB_HOST} -u ${process.env.DB_USER} -p${process.env.DB_PASSWORD} ${process.env.DB_NAME} > ${backupPath}` - - await execAsync(command) - - // 验证备份文件 - const stats = await fs.stat(backupPath) - if (stats.size === 0) { - throw new Error('数据库备份文件为空') - } - - return backupPath - } - - // 文件备份 - static async backupFiles(backupName) { - const backupPath = path.join(BACKUP_CONFIG.storage.local, `${backupName}-files.tar.gz`) - - const command = `tar -czf ${backupPath} ${process.env.UPLOAD_DIR} ${process.env.STATIC_DIR}` - - await execAsync(command) - return backupPath - } - - // 配置文件备份 - static async backupConfigs(backupName) { - const backupPath = path.join(BACKUP_CONFIG.storage.local, `${backupName}-configs.tar.gz`) - - const configDirs = [ - './config', - './docker-compose.yml', - './package.json', - './.env.example' - ] - - const command = `tar -czf ${backupPath} ${configDirs.join(' ')}` - - await execAsync(command) - return backupPath - } - - // 增量备份 - static async createIncrementalBackup() { - try { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-') - const backupName = `incremental-backup-${timestamp}` - - logger.info(`开始增量备份: ${backupName}`) - - // 获取上次备份时间 - const lastBackup = await this.getLastBackupTime() - - // 1. 增量数据库备份 - const dbBackupPath = await this.backupDatabaseIncremental(backupName, lastBackup) - - // 2. 增量文件备份 - const filesBackupPath = await this.backupFilesIncremental(backupName, lastBackup) - - // 3. 创建备份清单 - const manifest = { - backup_name: backupName, - backup_type: 'incremental', - created_at: new Date(), - since: lastBackup, - database: dbBackupPath, - files: filesBackupPath, - checksum: await this.calculateChecksum([dbBackupPath, filesBackupPath]) - } - - // 4. 加密和上传 - if (BACKUP_CONFIG.encryption.enabled) { - await this.encryptBackup(manifest) - } - - await this.uploadToRemoteStorage(manifest) - await this.logBackupEvent('INCREMENTAL_BACKUP_SUCCESS', manifest) - - logger.info(`增量备份完成: ${backupName}`) - return manifest - } catch (error) { - logger.error('增量备份失败:', error) - await this.logBackupEvent('INCREMENTAL_BACKUP_FAILED', { error: error.message }) - throw error - } - } - - // 备份恢复 - static async restoreBackup(backupName, options = {}) { - try { - logger.info(`开始恢复备份: ${backupName}`) - - // 1. 下载备份文件 - const manifest = await this.downloadBackup(backupName) - - // 2. 解密备份 - if (BACKUP_CONFIG.encryption.enabled) { - await this.decryptBackup(manifest) - } - - // 3. 验证备份完整性 - const isValid = await this.verifyBackupIntegrity(manifest) - if (!isValid) { - throw new Error('备份文件完整性验证失败') - } - - // 4. 停止服务(如果需要) - if (options.stopServices) { - await this.stopServices() - } - - // 5. 恢复数据库 - if (options.restoreDatabase !== false) { - await this.restoreDatabase(manifest.database) - } - - // 6. 恢复文件 - if (options.restoreFiles !== false) { - await this.restoreFiles(manifest.files) - } - - // 7. 恢复配置 - if (options.restoreConfigs !== false && manifest.configs) { - await this.restoreConfigs(manifest.configs) - } - - // 8. 重启服务 - if (options.stopServices) { - await this.startServices() - } - - await this.logBackupEvent('RESTORE_SUCCESS', { backup_name: backupName }) - logger.info(`备份恢复完成: ${backupName}`) - - return true - } catch (error) { - logger.error('备份恢复失败:', error) - await this.logBackupEvent('RESTORE_FAILED', { - backup_name: backupName, - error: error.message - }) - throw error - } - } - - // 备份清理 - static async cleanupOldBackups() { - try { - const now = new Date() - - // 清理本地备份 - const localBackups = await this.listLocalBackups() - - for (const backup of localBackups) { - const age = (now - backup.created_at) / (1000 * 60 * 60 * 24) // 天数 - - let shouldDelete = false - - if (backup.type === 'full' && age > BACKUP_CONFIG.retention.full) { - shouldDelete = true - } else if (backup.type === 'incremental' && age > BACKUP_CONFIG.retention.incremental) { - shouldDelete = true - } else if (backup.type === 'log' && age > BACKUP_CONFIG.retention.log) { - shouldDelete = true - } - - if (shouldDelete) { - await this.deleteBackup(backup) - logger.info(`已删除过期备份: ${backup.name}`) - } - } - - // 清理远程备份 - await this.cleanupRemoteBackups() - - } catch (error) { - logger.error('备份清理失败:', error) - } - } -} -``` - -## 🔧 安全配置和部署 - -### 服务器安全配置 - -#### Nginx安全配置 -```nginx -# /etc/nginx/sites-available/jiebanke-security -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name api.jiebanke.com; - - # SSL配置 - ssl_certificate /etc/letsencrypt/live/api.jiebanke.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/api.jiebanke.com/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - # 安全头部 - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always; - - # 隐藏服务器信息 - server_tokens off; - - # 限制请求大小 - client_max_body_size 10M; - client_body_buffer_size 128k; - - # 限制请求速率 - limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; - limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s; - - # 防止缓冲区溢出 - client_body_timeout 12; - client_header_timeout 12; - keepalive_timeout 15; - send_timeout 10; - - # 主要API路由 - location /api/ { - limit_req zone=api burst=20 nodelay; - - # 代理到后端服务 - proxy_pass http://127.0.0.1:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - - # 超时设置 - proxy_connect_timeout 5s; - proxy_send_timeout 10s; - proxy_read_timeout 10s; - } - - # 登录接口特殊限制 - location /api/auth/login { - limit_req zone=login burst=5 nodelay; - proxy_pass http://127.0.0.1:3000; - # ... 其他代理设置 - } - - # 静态文件 - location /uploads/ { - alias /var/www/jiebanke/uploads/; - expires 30d; - add_header Cache-Control "public, immutable"; - - # 防止执行上传的脚本 - location ~* \.(php|jsp|asp|sh|py|pl|exe)$ { - deny all; - } - } - - # 禁止访问敏感文件 - location ~ /\. { - deny all; - } - - location ~ \.(sql|log|conf)$ { - deny all; - } - - # 错误页面 - error_page 404 /404.html; - error_page 500 502 503 504 /50x.html; -} - -# HTTP重定向到HTTPS -server { - listen 80; - listen [::]:80; - server_name api.jiebanke.com; - return 301 https://$server_name$request_uri; -} -``` - -#### 防火墙配置 -```bash -#!/bin/bash -# 防火墙安全配置脚本 - -# 清空现有规则 -iptables -F -iptables -X -iptables -t nat -F -iptables -t nat -X - -# 设置默认策略 -iptables -P INPUT DROP -iptables -P FORWARD DROP -iptables -P OUTPUT ACCEPT - -# 允许本地回环 -iptables -A INPUT -i lo -j ACCEPT -iptables -A OUTPUT -o lo -j ACCEPT - -# 允许已建立的连接 -iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT - -# 允许SSH(限制连接数) -iptables -A INPUT -p tcp --dport 22 -m connlimit --connlimit-above 3 -j DROP -iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT - -# 允许HTTP和HTTPS -iptables -A INPUT -p tcp --dport 80 -j ACCEPT -iptables -A INPUT -p tcp --dport 443 -j ACCEPT - -# 防止DDoS攻击 -iptables -A INPUT -p tcp --dport 80 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT -iptables -A INPUT -p tcp --dport 443 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT - -# 防止端口扫描 -iptables -A INPUT -m recent --name portscan --rcheck --seconds 86400 -j DROP -iptables -A INPUT -m recent --name portscan --remove -iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --name portscan --set -j LOG --log-prefix "Portscan:" -iptables -A INPUT -p tcp -m tcp --dport 139 -j DROP - -# 防止SYN洪水攻击 -iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j RETURN -iptables -A INPUT -p tcp --syn -j DROP - -# 防止ping洪水攻击 -iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT -iptables -A INPUT -p icmp --icmp-type echo-request -j DROP - -# 记录被丢弃的包 -iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7 - -# 保存规则 -iptables-save > /etc/iptables/rules.v4 -``` - -### 环境变量安全管理 - -#### 密钥管理 -```javascript -// 环境变量验证和管理 -class EnvironmentManager { - constructor() { - this.requiredVars = [ - 'NODE_ENV', - 'PORT', - 'DB_HOST', - 'DB_USER', - 'DB_PASSWORD', - 'DB_NAME', - 'JWT_SECRET', - 'ENCRYPTION_KEY', - 'REDIS_HOST', - 'REDIS_PASSWORD' - ] - - this.sensitiveVars = [ - 'DB_PASSWORD', - 'JWT_SECRET', - 'ENCRYPTION_KEY', - 'REDIS_PASSWORD', - 'SMS_API_KEY', - 'EMAIL_PASSWORD' - ] - } - - // 验证环境变量 - validateEnvironment() { - const missing = [] - const weak = [] - - for (const varName of this.requiredVars) { - const value = process.env[varName] - - if (!value) { - missing.push(varName) - continue - } - - // 检查敏感变量强度 - if (this.sensitiveVars.includes(varName)) { - if (!this.isStrongSecret(value)) { - weak.push(varName) - } - } - } - - if (missing.length > 0) { - throw new Error(`缺少必需的环境变量: ${missing.join(', ')}`) - } - - if (weak.length > 0) { - logger.warn(`以下环境变量强度不足: ${weak.join(', ')}`) - } - - // 生产环境额外检查 - if (process.env.NODE_ENV === 'production') { - this.validateProductionEnvironment() - } - } - - // 检查密钥强度 - isStrongSecret(secret) { - if (secret.length < 32) return false - - const hasUpper = /[A-Z]/.test(secret) - const hasLower = /[a-z]/.test(secret) - const hasNumber = /\d/.test(secret) - const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(secret) - - return hasUpper && hasLower && hasNumber && hasSpecial - } - - // 生产环境验证 - validateProductionEnvironment() { - const productionChecks = { - NODE_ENV: (val) => val === 'production', - JWT_SECRET: (val) => val.length >= 64, - ENCRYPTION_KEY: (val) => val.length >= 64, - DB_SSL: (val) => val === 'true', - REDIS_TLS: (val) => val === 'true' - } - - for (const [varName, validator] of Object.entries(productionChecks)) { - const value = process.env[varName] - if (value && !validator(value)) { - throw new Error(`生产环境配置错误: ${varName}`) - } - } - } - - // 密钥轮换 - async rotateSecrets() { - try { - logger.info('开始密钥轮换') - - // 生成新的JWT密钥 - const newJwtSecret = this.generateStrongSecret(64) - - // 生成新的加密密钥 - const newEncryptionKey = this.generateStrongSecret(64) - - // 更新密钥存储 - await this.updateSecretStore({ - JWT_SECRET: newJwtSecret, - ENCRYPTION_KEY: newEncryptionKey, - rotated_at: new Date().toISOString() - }) - - // 记录轮换事件 - await logSecurityEvent({ - type: 'SECRET_ROTATION', - details: { rotated_secrets: ['JWT_SECRET', 'ENCRYPTION_KEY'] }, - risk_level: 'low' - }) - - logger.info('密钥轮换完成') - } catch (error) { - logger.error('密钥轮换失败:', error) - throw error - } - } - - // 生成强密钥 - generateStrongSecret(length = 64) { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*' - let result = '' - - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)) - } - - return result - } - - // 更新密钥存储 - async updateSecretStore(secrets) { - // 这里可以集成AWS Secrets Manager、HashiCorp Vault等 - // 示例使用文件存储(生产环境不推荐) - const secretsPath = '/etc/jiebanke/secrets.json' - - const existingSecrets = await this.loadSecrets() - const updatedSecrets = { ...existingSecrets, ...secrets } - - await fs.writeFile(secretsPath, JSON.stringify(updatedSecrets, null, 2), { - mode: 0o600 // 仅所有者可读写 - }) - } -} - -// 初始化环境管理器 -const envManager = new EnvironmentManager() -envManager.validateEnvironment() - -// 定期密钥轮换(每90天) -if (process.env.NODE_ENV === 'production') { - setInterval(async () => { - try { - await envManager.rotateSecrets() - } catch (error) { - logger.error('自动密钥轮换失败:', error) - } - }, 90 * 24 * 60 * 60 * 1000) // 90天 -} -``` - -## 📚 总结 - -本安全和权限管理文档涵盖了解班客项目的完整安全体系,包括: - -### 🎯 核心安全特性 -- **多层安全防护**: 网络、应用、数据三层安全架构 -- **身份认证系统**: JWT + MFA多因素认证 -- **权限管理**: RBAC基于角色的访问控制 -- **数据保护**: 加密存储、传输和脱敏处理 -- **安全监控**: 实时威胁检测和告警系统 - -### 🛡️ 安全防护措施 -- **输入验证**: XSS、SQL注入、CSRF防护 -- **速率限制**: API访问频率控制 -- **数据加密**: 敏感信息加密存储 -- **安全备份**: 定期备份和恢复机制 -- **环境安全**: 密钥管理和轮换 - -### 📊 监控和审计 -- **安全日志**: 完整的操作审计跟踪 -- **威胁检测**: 自动化安全威胁识别 -- **告警系统**: 多渠道安全事件通知 -- **合规性**: 符合数据保护法规要求 - -通过实施这套完整的安全体系,解班客项目能够有效防范各类安全威胁,保护用户数据安全,确保系统稳定可靠运行。 \ No newline at end of file diff --git a/docs/安全文档.md b/docs/安全文档.md new file mode 100644 index 0000000..e70d4aa --- /dev/null +++ b/docs/安全文档.md @@ -0,0 +1,1996 @@ +# 解班客项目安全文档 + +## 1. 安全概述 + +### 1.1 安全目标 +- **数据保护**:确保用户数据和业务数据的机密性、完整性和可用性 +- **系统安全**:防范各类网络攻击和安全威胁 +- **合规要求**:满足相关法律法规和行业标准要求 +- **隐私保护**:保护用户隐私信息不被泄露或滥用 + +### 1.2 安全架构 + +```mermaid +graph TB + A[用户] --> B[CDN/WAF] + B --> C[负载均衡器] + C --> D[Web应用防火墙] + D --> E[反向代理] + E --> F[应用服务器] + + F --> G[数据库] + F --> H[缓存服务] + F --> I[文件存储] + + J[安全监控] --> K[入侵检测] + J --> L[日志审计] + J --> M[漏洞扫描] + + N[身份认证] --> O[JWT令牌] + N --> P[多因子认证] + N --> Q[权限控制] + + subgraph "安全防护层" + B + D + E + end + + subgraph "应用安全层" + F + N + end + + subgraph "数据安全层" + G + H + I + end + + subgraph "监控审计层" + J + K + L + M + end +``` + +### 1.3 安全原则 +- **最小权限原则**:用户和系统组件只获得完成任务所需的最小权限 +- **纵深防御**:多层安全防护,单点失效不影响整体安全 +- **零信任架构**:不信任任何用户或设备,始终验证身份和权限 +- **数据分类保护**:根据数据敏感性实施不同级别的保护措施 + +## 2. 威胁分析 + +### 2.1 威胁模型 + +```mermaid +graph LR + A[威胁来源] --> B[外部攻击者] + A --> C[内部威胁] + A --> D[第三方风险] + + B --> B1[黑客攻击] + B --> B2[恶意软件] + B --> B3[DDoS攻击] + + C --> C1[内部人员] + C --> C2[权限滥用] + C --> C3[数据泄露] + + D --> D1[供应商风险] + D --> D2[第三方服务] + D --> D3[开源组件] +``` + +### 2.2 风险评估矩阵 + +| 威胁类型 | 可能性 | 影响程度 | 风险等级 | 防护措施 | +|---------|--------|----------|----------|----------| +| SQL注入 | 中 | 高 | 高 | 参数化查询、输入验证 | +| XSS攻击 | 高 | 中 | 高 | 输出编码、CSP策略 | +| CSRF攻击 | 中 | 中 | 中 | CSRF令牌、同源检查 | +| 暴力破解 | 高 | 中 | 高 | 账户锁定、验证码 | +| 数据泄露 | 低 | 高 | 中 | 数据加密、访问控制 | +| DDoS攻击 | 中 | 高 | 高 | 流量清洗、限流 | +| 内部威胁 | 低 | 高 | 中 | 权限管理、审计日志 | + +### 2.3 攻击场景分析 + +#### 2.3.1 Web应用攻击 +```javascript +// 常见攻击示例和防护 +const securityExamples = { + // SQL注入攻击示例 + sqlInjection: { + vulnerable: "SELECT * FROM users WHERE id = " + userId, + secure: "SELECT * FROM users WHERE id = ?", // 使用参数化查询 + prevention: [ + "使用ORM框架", + "参数化查询", + "输入验证", + "最小权限数据库用户" + ] + }, + + // XSS攻击示例 + xssAttack: { + vulnerable: `
${userInput}
`, + secure: `
${escapeHtml(userInput)}
`, + prevention: [ + "输出编码", + "CSP策略", + "输入验证", + "使用安全的模板引擎" + ] + }, + + // CSRF攻击示例 + csrfAttack: { + vulnerable: "POST /api/transfer without token", + secure: "POST /api/transfer with CSRF token", + prevention: [ + "CSRF令牌", + "SameSite Cookie", + "双重提交Cookie", + "验证Referer头" + ] + } +}; +``` + +## 3. 身份认证与授权 + +### 3.1 认证架构 + +```mermaid +sequenceDiagram + participant U as 用户 + participant F as 前端 + participant A as 认证服务 + participant R as 资源服务 + participant D as 数据库 + + U->>F: 登录请求 + F->>A: 提交凭证 + A->>D: 验证用户 + D-->>A: 用户信息 + A->>A: 生成JWT令牌 + A-->>F: 返回令牌 + F-->>U: 登录成功 + + U->>F: 访问资源 + F->>R: 携带JWT令牌 + R->>A: 验证令牌 + A-->>R: 令牌有效 + R->>D: 查询数据 + D-->>R: 返回数据 + R-->>F: 返回结果 + F-->>U: 显示内容 +``` + +### 3.2 JWT令牌安全配置 + +```javascript +// JWT配置 +const jwtConfig = { + // 令牌配置 + secret: process.env.JWT_SECRET, // 256位随机密钥 + algorithm: 'HS256', + expiresIn: '2h', // 访问令牌2小时过期 + refreshExpiresIn: '7d', // 刷新令牌7天过期 + + // 安全选项 + issuer: 'jiebanke.com', + audience: 'jiebanke-api', + notBefore: 0, + + // 令牌载荷 + payload: { + userId: 'user.id', + username: 'user.username', + role: 'user.role', + permissions: 'user.permissions' + } +}; + +// JWT中间件 +const jwtMiddleware = (req, res, next) => { + const token = req.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ error: '缺少访问令牌' }); + } + + try { + const decoded = jwt.verify(token, jwtConfig.secret, { + issuer: jwtConfig.issuer, + audience: jwtConfig.audience, + algorithms: [jwtConfig.algorithm] + }); + + // 检查令牌黑名单 + if (await isTokenBlacklisted(token)) { + return res.status(401).json({ error: '令牌已失效' }); + } + + req.user = decoded; + next(); + } catch (error) { + return res.status(401).json({ error: '无效的访问令牌' }); + } +}; +``` + +### 3.3 权限控制系统 + +```javascript +// RBAC权限模型 +const rbacModel = { + roles: { + admin: { + name: '管理员', + permissions: ['*'] // 所有权限 + }, + moderator: { + name: '版主', + permissions: [ + 'trip:read', + 'trip:update', + 'trip:delete', + 'user:read', + 'comment:moderate' + ] + }, + user: { + name: '普通用户', + permissions: [ + 'trip:read', + 'trip:create', + 'trip:update:own', + 'comment:create', + 'profile:update:own' + ] + } + }, + + resources: { + trip: ['create', 'read', 'update', 'delete'], + user: ['create', 'read', 'update', 'delete'], + comment: ['create', 'read', 'update', 'delete', 'moderate'], + profile: ['read', 'update'] + } +}; + +// 权限检查中间件 +const checkPermission = (resource, action) => { + return (req, res, next) => { + const user = req.user; + const userRole = user.role; + const userPermissions = rbacModel.roles[userRole]?.permissions || []; + + // 检查是否有通配符权限 + if (userPermissions.includes('*')) { + return next(); + } + + // 检查具体权限 + const requiredPermission = `${resource}:${action}`; + const hasPermission = userPermissions.some(permission => { + if (permission === requiredPermission) return true; + if (permission.endsWith(':*') && requiredPermission.startsWith(permission.slice(0, -1))) return true; + return false; + }); + + // 检查资源所有权 + if (!hasPermission && action.endsWith(':own')) { + const basePermission = requiredPermission.replace(':own', ''); + if (userPermissions.includes(basePermission + ':own')) { + // 需要验证资源所有权 + return checkResourceOwnership(resource, req, res, next); + } + } + + if (!hasPermission) { + return res.status(403).json({ error: '权限不足' }); + } + + next(); + }; +}; +``` + +### 3.4 多因子认证 + +```javascript +// 多因子认证实现 +const mfaService = { + // 生成TOTP密钥 + generateSecret: (userId) => { + const secret = speakeasy.generateSecret({ + name: `解班客 (${userId})`, + issuer: '解班客', + length: 32 + }); + + return { + secret: secret.base32, + qrCode: qrcode.toDataURL(secret.otpauth_url) + }; + }, + + // 验证TOTP令牌 + verifyToken: (secret, token) => { + return speakeasy.totp.verify({ + secret: secret, + encoding: 'base32', + token: token, + window: 2 // 允许时间偏差 + }); + }, + + // 生成备用码 + generateBackupCodes: () => { + const codes = []; + for (let i = 0; i < 10; i++) { + codes.push(crypto.randomBytes(4).toString('hex').toUpperCase()); + } + return codes; + } +}; + +// MFA中间件 +const mfaMiddleware = async (req, res, next) => { + const user = req.user; + + // 检查用户是否启用了MFA + const userMfa = await getUserMfaSettings(user.userId); + if (!userMfa.enabled) { + return next(); + } + + const mfaToken = req.headers['x-mfa-token']; + if (!mfaToken) { + return res.status(401).json({ + error: '需要多因子认证', + mfaRequired: true + }); + } + + // 验证MFA令牌 + const isValid = mfaService.verifyToken(userMfa.secret, mfaToken); + if (!isValid) { + // 尝试备用码 + const backupCodeValid = await verifyBackupCode(user.userId, mfaToken); + if (!backupCodeValid) { + return res.status(401).json({ error: '多因子认证失败' }); + } + } + + next(); +}; +``` + +## 4. 数据安全 + +### 4.1 数据分类与保护 + +| 数据类型 | 敏感级别 | 保护措施 | 访问控制 | +|---------|----------|----------|----------| +| 用户密码 | 极高 | bcrypt加密、盐值 | 仅认证服务访问 | +| 身份证号 | 高 | AES-256加密 | 管理员+审计日志 | +| 手机号码 | 高 | AES-256加密 | 业务需要+脱敏显示 | +| 邮箱地址 | 中 | 明文存储 | 用户本人+管理员 | +| 旅行信息 | 中 | 明文存储 | 公开+隐私设置 | +| 系统日志 | 低 | 压缩存储 | 运维人员 | + +### 4.2 数据加密实现 + +```javascript +// 数据加密服务 +const encryptionService = { + // AES-256-GCM加密 + encrypt: (plaintext, key = process.env.ENCRYPTION_KEY) => { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipher('aes-256-gcm', key, iv); + + let encrypted = cipher.update(plaintext, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + const authTag = cipher.getAuthTag(); + + return { + encrypted, + iv: iv.toString('hex'), + authTag: authTag.toString('hex') + }; + }, + + // AES-256-GCM解密 + decrypt: (encryptedData, key = process.env.ENCRYPTION_KEY) => { + const decipher = crypto.createDecipher('aes-256-gcm', key, + Buffer.from(encryptedData.iv, 'hex')); + + decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex')); + + let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + }, + + // 密码哈希 + hashPassword: async (password) => { + const saltRounds = 12; + return await bcrypt.hash(password, saltRounds); + }, + + // 密码验证 + verifyPassword: async (password, hash) => { + return await bcrypt.compare(password, hash); + } +}; + +// 敏感数据处理中间件 +const sensitiveDataMiddleware = { + // 加密敏感字段 + encryptSensitiveFields: (data, fields) => { + const result = { ...data }; + + fields.forEach(field => { + if (result[field]) { + result[field] = encryptionService.encrypt(result[field]); + } + }); + + return result; + }, + + // 解密敏感字段 + decryptSensitiveFields: (data, fields) => { + const result = { ...data }; + + fields.forEach(field => { + if (result[field] && typeof result[field] === 'object') { + result[field] = encryptionService.decrypt(result[field]); + } + }); + + return result; + }, + + // 数据脱敏 + maskSensitiveData: (data, field) => { + if (!data[field]) return data; + + const value = data[field]; + let masked; + + switch (field) { + case 'phone': + masked = value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); + break; + case 'email': + masked = value.replace(/(.{2}).*(@.*)/, '$1***$2'); + break; + case 'idCard': + masked = value.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'); + break; + default: + masked = '***'; + } + + return { ...data, [field]: masked }; + } +}; +``` + +### 4.3 数据库安全配置 + +```sql +-- 数据库安全配置 +-- 1. 创建专用数据库用户 +CREATE USER 'jiebanke_app'@'%' IDENTIFIED BY 'strong_password_here'; +CREATE USER 'jiebanke_readonly'@'%' IDENTIFIED BY 'readonly_password_here'; + +-- 2. 授予最小权限 +GRANT SELECT, INSERT, UPDATE, DELETE ON jiebanke.* TO 'jiebanke_app'@'%'; +GRANT SELECT ON jiebanke.* TO 'jiebanke_readonly'@'%'; + +-- 3. 禁用危险权限 +REVOKE FILE ON *.* FROM 'jiebanke_app'@'%'; +REVOKE PROCESS ON *.* FROM 'jiebanke_app'@'%'; +REVOKE SUPER ON *.* FROM 'jiebanke_app'@'%'; + +-- 4. 启用SSL连接 +ALTER USER 'jiebanke_app'@'%' REQUIRE SSL; + +-- 5. 设置连接限制 +ALTER USER 'jiebanke_app'@'%' WITH MAX_CONNECTIONS_PER_HOUR 1000; +ALTER USER 'jiebanke_app'@'%' WITH MAX_QUERIES_PER_HOUR 10000; + +-- 6. 创建审计表 +CREATE TABLE security_audit_log ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + action VARCHAR(50), + resource VARCHAR(100), + ip_address VARCHAR(45), + user_agent TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_user_id (user_id), + INDEX idx_action (action), + INDEX idx_created_at (created_at) +); +``` + +## 5. 网络安全 + +### 5.1 网络架构安全 + +```mermaid +graph TB + A[Internet] --> B[CDN] + B --> C[WAF] + C --> D[Load Balancer] + D --> E[DMZ] + + E --> F[Web Server] + F --> G[Internal Network] + G --> H[App Server] + G --> I[Database Server] + + J[VPN] --> G + K[Bastion Host] --> G + + subgraph "Public Zone" + B + C + D + end + + subgraph "DMZ Zone" + E + F + end + + subgraph "Internal Zone" + G + H + I + end + + subgraph "Management Zone" + J + K + end +``` + +### 5.2 防火墙配置 + +```bash +#!/bin/bash +# firewall-config.sh - 防火墙配置脚本 + +# 清空现有规则 +iptables -F +iptables -X +iptables -t nat -F +iptables -t nat -X + +# 设置默认策略 +iptables -P INPUT DROP +iptables -P FORWARD DROP +iptables -P OUTPUT ACCEPT + +# 允许本地回环 +iptables -A INPUT -i lo -j ACCEPT +iptables -A OUTPUT -o lo -j ACCEPT + +# 允许已建立的连接 +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# 允许SSH (限制IP) +iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT + +# 允许HTTP/HTTPS +iptables -A INPUT -p tcp --dport 80 -j ACCEPT +iptables -A INPUT -p tcp --dport 443 -j ACCEPT + +# 允许应用端口 (仅内网) +iptables -A INPUT -p tcp --dport 3000 -s 192.168.1.0/24 -j ACCEPT + +# 防止DDoS攻击 +iptables -A INPUT -p tcp --dport 80 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT +iptables -A INPUT -p tcp --dport 443 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT + +# 防止端口扫描 +iptables -A INPUT -m recent --name portscan --rcheck --seconds 86400 -j DROP +iptables -A INPUT -m recent --name portscan --remove +iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --name portscan --set -j LOG --log-prefix "portscan:" +iptables -A INPUT -p tcp -m tcp --dport 139 -j DROP + +# 保存规则 +iptables-save > /etc/iptables/rules.v4 + +echo "防火墙配置完成" +``` + +### 5.3 WAF规则配置 + +```nginx +# WAF配置 - ModSecurity规则 +# /etc/nginx/modsec/main.conf + +# 基础配置 +SecRuleEngine On +SecRequestBodyAccess On +SecResponseBodyAccess Off +SecRequestBodyLimit 13107200 +SecRequestBodyNoFilesLimit 131072 + +# 日志配置 +SecAuditEngine RelevantOnly +SecAuditLog /var/log/nginx/modsec_audit.log +SecAuditLogFormat JSON + +# SQL注入防护 +SecRule ARGS "@detectSQLi" \ + "id:1001,\ + phase:2,\ + block,\ + msg:'SQL Injection Attack Detected',\ + logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',\ + tag:'application-multi',\ + tag:'language-multi',\ + tag:'platform-multi',\ + tag:'attack-sqli'" + +# XSS攻击防护 +SecRule ARGS "@detectXSS" \ + "id:1002,\ + phase:2,\ + block,\ + msg:'XSS Attack Detected',\ + logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',\ + tag:'application-multi',\ + tag:'language-multi',\ + tag:'platform-multi',\ + tag:'attack-xss'" + +# 文件上传限制 +SecRule FILES_TMPNAMES "@inspectFile /etc/nginx/modsec/file_inspection.lua" \ + "id:1003,\ + phase:2,\ + block,\ + msg:'Malicious File Upload Detected'" + +# 频率限制 +SecRule IP:REQUEST_COUNT "@gt 100" \ + "id:1004,\ + phase:1,\ + deny,\ + msg:'Rate Limiting - Too Many Requests',\ + setvar:ip.request_count=+1,\ + expirevar:ip.request_count=60" + +# 地理位置限制 +SecRule REMOTE_ADDR "@geoLookup" \ + "id:1005,\ + phase:1,\ + chain,\ + msg:'Request from blocked country'" +SecRule GEO:COUNTRY_CODE "@within CN US JP KR" \ + "t:none,\ + deny" +``` + +### 5.4 SSL/TLS配置 + +```nginx +# SSL/TLS安全配置 +server { + listen 443 ssl http2; + server_name api.jiebanke.com; + + # SSL证书配置 + ssl_certificate /etc/ssl/certs/jiebanke.crt; + ssl_certificate_key /etc/ssl/private/jiebanke.key; + + # SSL协议和加密套件 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers off; + + # SSL会话配置 + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_session_tickets off; + + # OCSP装订 + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + + # 安全头部 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';" always; + + # 隐藏服务器信息 + server_tokens off; + more_clear_headers Server; + + location / { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 安全头部 + proxy_hide_header X-Powered-By; + proxy_hide_header Server; + } +} +``` + +## 6. 应用安全 + +### 6.1 输入验证与过滤 + +```javascript +// 输入验证中间件 +const inputValidation = { + // 通用验证规则 + rules: { + username: { + type: 'string', + minLength: 3, + maxLength: 20, + pattern: /^[a-zA-Z0-9_]+$/, + sanitize: true + }, + email: { + type: 'email', + maxLength: 100, + sanitize: true + }, + password: { + type: 'string', + minLength: 8, + maxLength: 128, + pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/ + }, + phone: { + type: 'string', + pattern: /^1[3-9]\d{9}$/, + sanitize: true + } + }, + + // 验证函数 + validate: (data, rules) => { + const errors = []; + + for (const [field, rule] of Object.entries(rules)) { + const value = data[field]; + + // 必填检查 + if (rule.required && (!value || value.trim() === '')) { + errors.push(`${field} 是必填字段`); + continue; + } + + if (!value) continue; + + // 类型检查 + if (rule.type === 'email' && !validator.isEmail(value)) { + errors.push(`${field} 格式不正确`); + } + + // 长度检查 + if (rule.minLength && value.length < rule.minLength) { + errors.push(`${field} 长度不能少于 ${rule.minLength} 个字符`); + } + + if (rule.maxLength && value.length > rule.maxLength) { + errors.push(`${field} 长度不能超过 ${rule.maxLength} 个字符`); + } + + // 正则检查 + if (rule.pattern && !rule.pattern.test(value)) { + errors.push(`${field} 格式不正确`); + } + } + + return errors; + }, + + // 数据清理 + sanitize: (data, rules) => { + const sanitized = {}; + + for (const [field, rule] of Object.entries(rules)) { + let value = data[field]; + + if (!value) { + sanitized[field] = value; + continue; + } + + if (rule.sanitize) { + // HTML转义 + value = validator.escape(value); + // 去除首尾空格 + value = value.trim(); + // SQL注入防护 + value = value.replace(/['"\\]/g, '\\$&'); + } + + sanitized[field] = value; + } + + return sanitized; + } +}; + +// 验证中间件 +const validateInput = (rules) => { + return (req, res, next) => { + const errors = inputValidation.validate(req.body, rules); + + if (errors.length > 0) { + return res.status(400).json({ + error: '输入验证失败', + details: errors + }); + } + + // 清理输入数据 + req.body = inputValidation.sanitize(req.body, rules); + next(); + }; +}; +``` + +### 6.2 CSRF防护 + +```javascript +// CSRF防护中间件 +const csrfProtection = { + // 生成CSRF令牌 + generateToken: (req) => { + const token = crypto.randomBytes(32).toString('hex'); + req.session.csrfToken = token; + return token; + }, + + // 验证CSRF令牌 + verifyToken: (req) => { + const sessionToken = req.session.csrfToken; + const requestToken = req.headers['x-csrf-token'] || req.body._csrf; + + return sessionToken && requestToken && sessionToken === requestToken; + }, + + // CSRF中间件 + middleware: (req, res, next) => { + // GET请求不需要CSRF验证 + if (req.method === 'GET') { + return next(); + } + + // API请求使用JWT,不需要CSRF验证 + if (req.path.startsWith('/api/')) { + return next(); + } + + if (!csrfProtection.verifyToken(req)) { + return res.status(403).json({ error: 'CSRF令牌验证失败' }); + } + + next(); + } +}; + +// 双重提交Cookie CSRF防护 +const doubleSubmitCsrf = { + middleware: (req, res, next) => { + if (req.method === 'GET') { + // 设置CSRF Cookie + const token = crypto.randomBytes(32).toString('hex'); + res.cookie('csrf-token', token, { + httpOnly: false, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict' + }); + return next(); + } + + const cookieToken = req.cookies['csrf-token']; + const headerToken = req.headers['x-csrf-token']; + + if (!cookieToken || !headerToken || cookieToken !== headerToken) { + return res.status(403).json({ error: 'CSRF验证失败' }); + } + + next(); + } +}; +``` + +### 6.3 会话安全 + +```javascript +// 会话安全配置 +const sessionConfig = { + secret: process.env.SESSION_SECRET, + name: 'jiebanke.sid', + resave: false, + saveUninitialized: false, + rolling: true, + cookie: { + secure: process.env.NODE_ENV === 'production', + httpOnly: true, + maxAge: 2 * 60 * 60 * 1000, // 2小时 + sameSite: 'strict' + }, + store: new RedisStore({ + client: redisClient, + prefix: 'sess:', + ttl: 2 * 60 * 60 // 2小时 + }) +}; + +// 会话安全中间件 +const sessionSecurity = { + // 会话固定攻击防护 + regenerateSession: (req, res, next) => { + if (req.session && req.session.regenerate) { + req.session.regenerate((err) => { + if (err) { + return next(err); + } + next(); + }); + } else { + next(); + } + }, + + // 会话劫持防护 + validateSession: (req, res, next) => { + if (!req.session.userAgent) { + req.session.userAgent = req.headers['user-agent']; + req.session.ipAddress = req.ip; + } else { + // 检查User-Agent和IP是否一致 + if (req.session.userAgent !== req.headers['user-agent'] || + req.session.ipAddress !== req.ip) { + req.session.destroy(); + return res.status(401).json({ error: '会话已失效,请重新登录' }); + } + } + + next(); + }, + + // 并发会话控制 + limitConcurrentSessions: async (req, res, next) => { + if (!req.user) return next(); + + const userId = req.user.userId; + const currentSessionId = req.sessionID; + + // 获取用户的所有活跃会话 + const activeSessions = await redis.smembers(`user:${userId}:sessions`); + + // 限制最多3个并发会话 + if (activeSessions.length >= 3 && !activeSessions.includes(currentSessionId)) { + // 删除最旧的会话 + const oldestSession = activeSessions[0]; + await redis.srem(`user:${userId}:sessions`, oldestSession); + await redis.del(`sess:${oldestSession}`); + } + + // 添加当前会话 + await redis.sadd(`user:${userId}:sessions`, currentSessionId); + await redis.expire(`user:${userId}:sessions`, 2 * 60 * 60); + + next(); + } +}; +``` + +## 7. 安全监控与审计 + +### 7.1 安全事件监控 + +```javascript +// 安全事件监控系统 +const securityMonitor = { + // 事件类型定义 + eventTypes: { + LOGIN_SUCCESS: 'login_success', + LOGIN_FAILURE: 'login_failure', + PASSWORD_CHANGE: 'password_change', + PERMISSION_DENIED: 'permission_denied', + SUSPICIOUS_ACTIVITY: 'suspicious_activity', + DATA_ACCESS: 'data_access', + ADMIN_ACTION: 'admin_action' + }, + + // 记录安全事件 + logEvent: async (eventType, userId, details = {}) => { + const event = { + id: uuidv4(), + type: eventType, + userId: userId, + timestamp: new Date(), + ipAddress: details.ipAddress, + userAgent: details.userAgent, + resource: details.resource, + action: details.action, + result: details.result, + riskLevel: details.riskLevel || 'low', + metadata: details.metadata || {} + }; + + // 存储到数据库 + await SecurityAuditLog.create(event); + + // 发送到日志系统 + logger.info('Security Event', event); + + // 检查是否需要告警 + await this.checkAlerts(event); + }, + + // 告警检查 + checkAlerts: async (event) => { + const alerts = []; + + // 登录失败次数检查 + if (event.type === this.eventTypes.LOGIN_FAILURE) { + const recentFailures = await this.getRecentEvents( + event.userId, + this.eventTypes.LOGIN_FAILURE, + 15 * 60 * 1000 // 15分钟内 + ); + + if (recentFailures.length >= 5) { + alerts.push({ + type: 'BRUTE_FORCE_ATTACK', + severity: 'high', + message: `用户 ${event.userId} 15分钟内登录失败${recentFailures.length}次` + }); + } + } + + // 异常IP访问检查 + if (event.ipAddress) { + const userIPs = await this.getUserRecentIPs(event.userId, 24 * 60 * 60 * 1000); + if (userIPs.length > 1 && !userIPs.includes(event.ipAddress)) { + alerts.push({ + type: 'UNUSUAL_IP_ACCESS', + severity: 'medium', + message: `用户 ${event.userId} 从新IP地址 ${event.ipAddress} 访问` + }); + } + } + + // 权限提升检查 + if (event.type === this.eventTypes.ADMIN_ACTION && event.userId) { + alerts.push({ + type: 'ADMIN_ACTION', + severity: 'medium', + message: `管理员 ${event.userId} 执行了操作: ${event.action}` + }); + } + + // 发送告警 + for (const alert of alerts) { + await this.sendAlert(alert, event); + } + }, + + // 发送安全告警 + sendAlert: async (alert, event) => { + const message = { + title: `安全告警: ${alert.type}`, + severity: alert.severity, + message: alert.message, + timestamp: event.timestamp, + details: event + }; + + // 发送到钉钉 + await this.sendDingTalkAlert(message); + + // 发送邮件 + if (alert.severity === 'high') { + await this.sendEmailAlert(message); + } + + // 记录告警 + await SecurityAlert.create({ + type: alert.type, + severity: alert.severity, + message: alert.message, + eventId: event.id, + status: 'open' + }); + } +}; + +// 安全监控中间件 +const securityMonitorMiddleware = (req, res, next) => { + // 记录请求信息 + const originalSend = res.send; + res.send = function(data) { + const statusCode = res.statusCode; + + // 记录安全相关事件 + if (req.path.includes('/auth/login')) { + const eventType = statusCode === 200 ? + securityMonitor.eventTypes.LOGIN_SUCCESS : + securityMonitor.eventTypes.LOGIN_FAILURE; + + securityMonitor.logEvent(eventType, req.body.username, { + ipAddress: req.ip, + userAgent: req.headers['user-agent'], + result: statusCode === 200 ? 'success' : 'failure' + }); + } + + if (statusCode === 403) { + securityMonitor.logEvent(securityMonitor.eventTypes.PERMISSION_DENIED, req.user?.userId, { + ipAddress: req.ip, + userAgent: req.headers['user-agent'], + resource: req.path, + action: req.method + }); + } + + originalSend.call(this, data); + }; + + next(); +}; +``` + +### 7.2 入侵检测系统 + +```python +#!/usr/bin/env python3 +# intrusion_detection.py - 入侵检测系统 + +import re +import json +import time +from collections import defaultdict, deque +from datetime import datetime, timedelta + +class IntrusionDetectionSystem: + def __init__(self): + self.attack_patterns = { + 'sql_injection': [ + r"(\bunion\b.*\bselect\b)", + r"(\bselect\b.*\bfrom\b.*\bwhere\b)", + r"(\bdrop\b.*\btable\b)", + r"(\binsert\b.*\binto\b)", + r"(\bdelete\b.*\bfrom\b)" + ], + 'xss_attack': [ + r"]*>.*?", + r"javascript:", + r"on\w+\s*=", + r"]*>.*?" + ], + 'path_traversal': [ + r"\.\./", + r"\.\.\\", + r"/etc/passwd", + r"/etc/shadow" + ], + 'command_injection': [ + r";\s*(cat|ls|pwd|whoami)", + r"\|\s*(cat|ls|pwd|whoami)", + r"&&\s*(cat|ls|pwd|whoami)", + r"`.*`" + ] + } + + self.ip_requests = defaultdict(lambda: deque(maxlen=100)) + self.failed_logins = defaultdict(lambda: deque(maxlen=50)) + + def analyze_request(self, request_data): + """分析单个请求""" + alerts = [] + + # 检查攻击模式 + for attack_type, patterns in self.attack_patterns.items(): + for pattern in patterns: + if self._check_pattern(request_data, pattern): + alerts.append({ + 'type': attack_type, + 'severity': 'high', + 'pattern': pattern, + 'timestamp': datetime.now(), + 'source_ip': request_data.get('ip'), + 'request_uri': request_data.get('uri'), + 'user_agent': request_data.get('user_agent') + }) + + # 检查频率异常 + ip = request_data.get('ip') + if ip: + self.ip_requests[ip].append(time.time()) + if self._check_rate_limit(ip): + alerts.append({ + 'type': 'rate_limit_exceeded', + 'severity': 'medium', + 'source_ip': ip, + 'request_count': len(self.ip_requests[ip]), + 'timestamp': datetime.now() + }) + + # 检查登录失败 + if request_data.get('path') == '/auth/login' and request_data.get('status') >= 400: + user = request_data.get('username', ip) + self.failed_logins[user].append(time.time()) + if len(self.failed_logins[user]) >= 5: + alerts.append({ + 'type': 'brute_force_attack', + 'severity': 'high', + 'target_user': user, + 'source_ip': ip, + 'failed_attempts': len(self.failed_logins[user]), + 'timestamp': datetime.now() + }) + + return alerts + + def _check_pattern(self, request_data, pattern): + """检查请求是否匹配攻击模式""" + text_fields = [ + request_data.get('uri', ''), + request_data.get('query_string', ''), + request_data.get('post_data', ''), + request_data.get('headers', {}).get('user-agent', '') + ] + + for field in text_fields: + if re.search(pattern, field, re.IGNORECASE): + return True + return False + + def _check_rate_limit(self, ip): + """检查IP请求频率""" + now = time.time() + requests = self.ip_requests[ip] + + # 1分钟内超过100个请求 + recent_requests = [req for req in requests if now - req < 60] + return len(recent_requests) > 100 + + def generate_report(self, alerts): + """生成入侵检测报告""" + if not alerts: + return None + + report = { + 'timestamp': datetime.now().isoformat(), + 'total_alerts': len(alerts), + 'severity_breakdown': defaultdict(int), + 'attack_types': defaultdict(int), + 'top_source_ips': defaultdict(int), + 'alerts': alerts + } + + for alert in alerts: + report['severity_breakdown'][alert['severity']] += 1 + report['attack_types'][alert['type']] += 1 + if 'source_ip' in alert: + report['top_source_ips'][alert['source_ip']] += 1 + + return report + + def block_ip(self, ip, duration=3600): + """阻止IP访问""" + # 这里应该调用防火墙API或更新IP黑名单 + print(f"Blocking IP {ip} for {duration} seconds") + + # 示例:更新iptables规则 + import subprocess + try: + subprocess.run([ + 'iptables', '-A', 'INPUT', + '-s', ip, '-j', 'DROP' + ], check=True) + + # 设置定时解除阻止 + subprocess.run([ + 'at', f'now + {duration} seconds' + ], input=f'iptables -D INPUT -s {ip} -j DROP\n', + text=True, check=True) + + except subprocess.CalledProcessError as e: + print(f"Failed to block IP {ip}: {e}") + +# 使用示例 +if __name__ == "__main__": + ids = IntrusionDetectionSystem() + + # 模拟请求数据 + request_data = { + 'ip': '192.168.1.100', + 'uri': '/api/users?id=1 UNION SELECT * FROM users', + 'method': 'GET', + 'status': 200, + 'user_agent': 'Mozilla/5.0...' + } + + alerts = ids.analyze_request(request_data) + if alerts: + report = ids.generate_report(alerts) + print(json.dumps(report, indent=2, default=str)) + + # 自动阻止高危IP + for alert in alerts: + if alert['severity'] == 'high' and 'source_ip' in alert: + ids.block_ip(alert['source_ip']) +``` + +## 8. 安全测试 + +### 8.1 自动化安全测试 + +```python +#!/usr/bin/env python3 +# security_test.py - 自动化安全测试 + +import requests +import json +import time +from urllib.parse import urljoin + +class SecurityTester: + def __init__(self, base_url): + self.base_url = base_url + self.session = requests.Session() + self.vulnerabilities = [] + + def test_sql_injection(self): + """SQL注入测试""" + print("Testing SQL Injection...") + + payloads = [ + "' OR '1'='1", + "'; DROP TABLE users; --", + "' UNION SELECT * FROM users --", + "1' AND (SELECT COUNT(*) FROM users) > 0 --" + ] + + test_endpoints = [ + "/api/users", + "/api/trips", + "/api/auth/login" + ] + + for endpoint in test_endpoints: + for payload in payloads: + params = {'id': payload} + try: + response = self.session.get( + urljoin(self.base_url, endpoint), + params=params, + timeout=10 + ) + + # 检查是否存在SQL错误信息 + error_indicators = [ + 'mysql_fetch_array', + 'ORA-01756', + 'Microsoft OLE DB Provider', + 'SQLServer JDBC Driver', + 'PostgreSQL query failed' + ] + + for indicator in error_indicators: + if indicator.lower() in response.text.lower(): + self.vulnerabilities.append({ + 'type': 'SQL Injection', + 'severity': 'High', + 'endpoint': endpoint, + 'payload': payload, + 'evidence': indicator + }) + break + + except requests.RequestException as e: + print(f"Request failed: {e}") + + def test_xss(self): + """XSS测试""" + print("Testing XSS...") + + payloads = [ + "", + "", + "javascript:alert('XSS')", + "" + ] + + test_endpoints = [ + "/api/comments", + "/api/trips", + "/api/users/profile" + ] + + for endpoint in test_endpoints: + for payload in payloads: + data = {'content': payload, 'title': payload} + try: + response = self.session.post( + urljoin(self.base_url, endpoint), + json=data, + timeout=10 + ) + + # 检查响应中是否包含未转义的payload + if payload in response.text: + self.vulnerabilities.append({ + 'type': 'Cross-Site Scripting (XSS)', + 'severity': 'High', + 'endpoint': endpoint, + 'payload': payload, + 'evidence': 'Payload reflected in response' + }) + + except requests.RequestException as e: + print(f"Request failed: {e}") + + def test_authentication_bypass(self): + """认证绕过测试""" + print("Testing Authentication Bypass...") + + protected_endpoints = [ + "/api/admin/users", + "/api/admin/system", + "/api/users/profile" + ] + + for endpoint in protected_endpoints: + try: + # 不带认证头的请求 + response = self.session.get( + urljoin(self.base_url, endpoint), + timeout=10 + ) + + if response.status_code == 200: + self.vulnerabilities.append({ + 'type': 'Authentication Bypass', + 'severity': 'High', + 'endpoint': endpoint, + 'evidence': f'Status code: {response.status_code}' + }) + + # 测试无效token + headers = {'Authorization': 'Bearer invalid_token'} + response = self.session.get( + urljoin(self.base_url, endpoint), + headers=headers, + timeout=10 + ) + + if response.status_code == 200: + self.vulnerabilities.append({ + 'type': 'Invalid Token Accepted', + 'severity': 'High', + 'endpoint': endpoint, + 'evidence': 'Invalid token accepted' + }) + + except requests.RequestException as e: + print(f"Request failed: {e}") + + def test_brute_force_protection(self): + """暴力破解防护测试""" + print("Testing Brute Force Protection...") + + login_endpoint = "/api/auth/login" + credentials = { + 'username': 'testuser', + 'password': 'wrongpassword' + } + + attempt_count = 0 + max_attempts = 10 + + for i in range(max_attempts): + try: + response = self.session.post( + urljoin(self.base_url, login_endpoint), + json=credentials, + timeout=10 + ) + + attempt_count += 1 + + # 检查是否有速率限制 + if response.status_code == 429: + print(f"Rate limiting detected after {attempt_count} attempts") + break + elif response.status_code == 401: + continue + else: + time.sleep(1) # 避免过快请求 + + except requests.RequestException as e: + print(f"Request failed: {e}") + break + + if attempt_count >= max_attempts: + self.vulnerabilities.append({ + 'type': 'No Brute Force Protection', + 'severity': 'Medium', + 'endpoint': login_endpoint, + 'evidence': f'Allowed {max_attempts} failed login attempts' + }) + + def test_file_upload_security(self): + """文件上传安全测试""" + print("Testing File Upload Security...") + + upload_endpoint = "/api/upload" + + # 测试恶意文件上传 + malicious_files = [ + ('shell.php', '', 'application/x-php'), + ('script.js', 'alert("XSS")', 'application/javascript'), + ('test.exe', b'\x4d\x5a\x90\x00', 'application/x-executable') + ] + + for filename, content, content_type in malicious_files: + files = { + 'file': (filename, content, content_type) + } + + try: + response = self.session.post( + urljoin(self.base_url, upload_endpoint), + files=files, + timeout=10 + ) + + if response.status_code == 200: + self.vulnerabilities.append({ + 'type': 'Malicious File Upload', + 'severity': 'High', + 'endpoint': upload_endpoint, + 'payload': filename, + 'evidence': 'Malicious file accepted' + }) + + except requests.RequestException as e: + print(f"Request failed: {e}") + + def generate_report(self): + """生成安全测试报告""" + report = { + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), + 'target': self.base_url, + 'total_vulnerabilities': len(self.vulnerabilities), + 'severity_breakdown': { + 'High': 0, + 'Medium': 0, + 'Low': 0 + }, + 'vulnerabilities': self.vulnerabilities + } + + for vuln in self.vulnerabilities: + severity = vuln.get('severity', 'Low') + report['severity_breakdown'][severity] += 1 + + return report + + def run_all_tests(self): + """运行所有安全测试""" + print(f"Starting security tests for {self.base_url}") + + self.test_sql_injection() + self.test_xss() + self.test_authentication_bypass() + self.test_brute_force_protection() + self.test_file_upload_security() + + report = self.generate_report() + + print("\n" + "="*50) + print("SECURITY TEST REPORT") + print("="*50) + print(f"Target: {report['target']}") + print(f"Total Vulnerabilities: {report['total_vulnerabilities']}") + print(f"High: {report['severity_breakdown']['High']}") + print(f"Medium: {report['severity_breakdown']['Medium']}") + print(f"Low: {report['severity_breakdown']['Low']}") + + if self.vulnerabilities: + print("\nVulnerabilities Found:") + for i, vuln in enumerate(self.vulnerabilities, 1): + print(f"\n{i}. {vuln['type']} ({vuln['severity']})") + print(f" Endpoint: {vuln['endpoint']}") + if 'payload' in vuln: + print(f" Payload: {vuln['payload']}") + print(f" Evidence: {vuln['evidence']}") + else: + print("\nNo vulnerabilities found!") + + return report + +# 使用示例 +if __name__ == "__main__": + tester = SecurityTester("http://localhost:3000") + report = tester.run_all_tests() + + # 保存报告 + with open(f"security_report_{int(time.time())}.json", 'w') as f: + json.dump(report, f, indent=2) +``` + +## 9. 应急响应 + +### 9.1 安全事件响应流程 + +```mermaid +graph TD + A[安全事件发生] --> B[事件检测] + B --> C[事件分类] + C --> D[影响评估] + D --> E[响应级别确定] + + E --> F[一级响应] + E --> G[二级响应] + E --> H[三级响应] + + F --> I[立即隔离] + G --> J[限制访问] + H --> K[监控观察] + + I --> L[深入调查] + J --> L + K --> L + + L --> M[制定恢复计划] + M --> N[系统恢复] + N --> O[事后总结] +``` + +### 9.2 应急响应脚本 + +```bash +#!/bin/bash +# emergency-response.sh - 应急响应脚本 + +set -e + +LOG_FILE="/var/log/security/emergency-response.log" +BACKUP_DIR="/opt/backup/emergency" +QUARANTINE_DIR="/opt/quarantine" + +# 日志函数 +log_message() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# 发送告警 +send_alert() { + local message="$1" + local level="$2" + + # 发送钉钉告警 + curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"msgtype\": \"text\", + \"text\": { + \"content\": \"【安全应急】[$level] $message\" + }, + \"at\": { + \"isAtAll\": true + } + }" > /dev/null 2>&1 + + log_message "Alert sent: [$level] $message" +} + +# 隔离受感染系统 +isolate_system() { + log_message "开始系统隔离..." + + # 断开网络连接 + iptables -P INPUT DROP + iptables -P OUTPUT DROP + iptables -P FORWARD DROP + + # 保留SSH连接用于管理 + iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT + iptables -A OUTPUT -p tcp --sport 22 -d 192.168.1.0/24 -j ACCEPT + + # 停止非关键服务 + systemctl stop nginx + systemctl stop apache2 + systemctl stop mysql + + send_alert "系统已隔离,网络连接已断开" "CRITICAL" + log_message "系统隔离完成" +} + +# 备份关键数据 +backup_critical_data() { + log_message "开始备份关键数据..." + + mkdir -p "$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)" + CURRENT_BACKUP="$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)" + + # 备份数据库 + mysqldump -u root -p"$DB_PASSWORD" --all-databases > "$CURRENT_BACKUP/database_backup.sql" + + # 备份配置文件 + cp -r /etc/nginx "$CURRENT_BACKUP/" + cp -r /etc/mysql "$CURRENT_BACKUP/" + cp -r /opt/jiebanke/config "$CURRENT_BACKUP/" + + # 备份日志文件 + cp -r /var/log "$CURRENT_BACKUP/" + + # 创建备份清单 + find "$CURRENT_BACKUP" -type f -exec ls -la {} \; > "$CURRENT_BACKUP/backup_manifest.txt" + + log_message "数据备份完成: $CURRENT_BACKUP" +} + +# 收集证据 +collect_evidence() { + log_message "开始收集安全事件证据..." + + EVIDENCE_DIR="/opt/evidence/$(date +%Y%m%d_%H%M%S)" + mkdir -p "$EVIDENCE_DIR" + + # 系统信息 + uname -a > "$EVIDENCE_DIR/system_info.txt" + ps aux > "$EVIDENCE_DIR/processes.txt" + netstat -tulpn > "$EVIDENCE_DIR/network_connections.txt" + lsof > "$EVIDENCE_DIR/open_files.txt" + + # 用户信息 + who > "$EVIDENCE_DIR/logged_users.txt" + last -n 100 > "$EVIDENCE_DIR/login_history.txt" + + # 文件系统信息 + find /tmp -type f -mtime -1 -ls > "$EVIDENCE_DIR/recent_tmp_files.txt" + find /var/www -type f -mtime -1 -ls > "$EVIDENCE_DIR/recent_web_files.txt" + + # 网络流量 + tcpdump -i any -w "$EVIDENCE_DIR/network_traffic.pcap" -c 1000 & + TCPDUMP_PID=$! + sleep 30 + kill $TCPDUMP_PID 2>/dev/null || true + + log_message "证据收集完成: $EVIDENCE_DIR" +} + +# 恶意文件隔离 +quarantine_malicious_files() { + local file_path="$1" + + if [ ! -f "$file_path" ]; then + log_message "文件不存在: $file_path" + return 1 + fi + + log_message "隔离恶意文件: $file_path" + + mkdir -p "$QUARANTINE_DIR" + + # 计算文件哈希 + FILE_HASH=$(sha256sum "$file_path" | cut -d' ' -f1) + + # 移动文件到隔离区 + mv "$file_path" "$QUARANTINE_DIR/${FILE_HASH}_$(basename $file_path)" + + # 记录隔离信息 + echo "$(date '+%Y-%m-%d %H:%M:%S') - $file_path -> $QUARANTINE_DIR/${FILE_HASH}_$(basename $file_path)" >> "$QUARANTINE_DIR/quarantine.log" + + log_message "文件已隔离: $QUARANTINE_DIR/${FILE_HASH}_$(basename $file_path)" +} + +# 阻止恶意IP +block_malicious_ip() { + local ip="$1" + local duration="${2:-3600}" # 默认1小时 + + log_message "阻止恶意IP: $ip (持续时间: ${duration}秒)" + + # 添加iptables规则 + iptables -A INPUT -s "$ip" -j DROP + + # 记录到黑名单 + echo "$ip" >> /etc/security/ip_blacklist.txt + + # 设置定时解除阻止 + echo "iptables -D INPUT -s $ip -j DROP" | at now + ${duration} seconds + + send_alert "已阻止恶意IP: $ip" "HIGH" + log_message "IP阻止完成: $ip" +} + +# 系统恢复 +system_recovery() { + log_message "开始系统恢复..." + + # 恢复网络连接 + iptables -F + iptables -P INPUT ACCEPT + iptables -P OUTPUT ACCEPT + iptables -P FORWARD ACCEPT + + # 重启服务 + systemctl start mysql + systemctl start nginx + + # 验证服务状态 + if systemctl is-active --quiet mysql && systemctl is-active --quiet nginx; then + log_message "服务恢复成功" + send_alert "系统恢复完成,服务正常运行" "INFO" + else + log_message "服务恢复失败" + send_alert "系统恢复失败,需要人工干预" "CRITICAL" + fi +} + +# 主函数 +main() { + case "$1" in + "isolate") + isolate_system + ;; + "backup") + backup_critical_data + ;; + "evidence") + collect_evidence + ;; + "quarantine") + if [ -z "$2" ]; then + echo "用法: $0 quarantine " + exit 1 + fi + quarantine_malicious_files "$2" + ;; + "block-ip") + if [ -z "$2" ]; then + echo "用法: $0 block-ip [duration]" + exit 1 + fi + block_malicious_ip "$2" "$3" + ;; + "recover") + system_recovery + ;; + "full-response") + log_message "开始完整应急响应流程" + backup_critical_data + collect_evidence + isolate_system + send_alert "完整应急响应流程已执行" "CRITICAL" + ;; + *) + echo "用法: $0 {isolate|backup|evidence|quarantine|block-ip|recover|full-response}" + echo " isolate - 隔离系统" + echo " backup - 备份关键数据" + echo " evidence - 收集证据" + echo " quarantine - 隔离恶意文件" + echo " block-ip - 阻止恶意IP" + echo " recover - 系统恢复" + echo " full-response - 执行完整应急响应" + exit 1 + ;; + esac +} + +main "$@" +``` + +### 9.3 事件响应手册 + +#### 9.3.1 数据泄露响应 + +**响应步骤:** +1. **立即行动**(0-1小时) + - 确认泄露范围和影响 + - 停止数据泄露源 + - 保护剩余数据 + - 通知应急响应团队 + +2. **深入调查**(1-24小时) + - 分析泄露原因 + - 确定泄露的具体数据 + - 评估业务影响 + - 收集相关证据 + +3. **通知相关方**(24-72小时) + - 通知监管机构 + - 通知受影响用户 + - 发布公告说明 + - 配合调查工作 + +#### 9.3.2 恶意软件感染响应 + +**响应步骤:** +1. **隔离感染系统** + - 断开网络连接 + - 停止相关服务 + - 防止横向传播 + +2. **分析恶意软件** + - 识别恶意软件类型 + - 分析攻击向量 + - 评估损害程度 + +3. **清除和恢复** + - 清除恶意软件 + - 修复受损文件 + - 恢复系统功能 + - 加强防护措施 + +## 10. 总结 + +### 10.1 安全架构总结 + +解班客项目的安全架构采用了多层防护策略,包括: + +- **网络安全层**:WAF、防火墙、DDoS防护 +- **应用安全层**:身份认证、权限控制、输入验证 +- **数据安全层**:数据加密、访问控制、备份恢复 +- **监控审计层**:安全监控、日志审计、入侵检测 + +### 10.2 关键安全措施 + +1. **身份认证**:JWT令牌 + 多因子认证 +2. **权限控制**:基于角色的访问控制(RBAC) +3. **数据保护**:AES-256加密 + 数据分类保护 +4. **网络防护**:多层防火墙 + WAF规则 +5. **安全监控**:实时监控 + 自动告警 +6. **应急响应**:完整的事件响应流程 + +### 10.3 持续改进 + +安全是一个持续的过程,需要: + +- **定期安全评估**:每季度进行安全评估 +- **漏洞扫描**:每月进行自动化漏洞扫描 +- **安全培训**:定期对开发和运维人员进行安全培训 +- **威胁情报**:关注最新的安全威胁和漏洞信息 +- **安全更新**:及时更新系统和组件的安全补丁 + +### 10.4 合规要求 + +项目需要满足以下合规要求: + +- **《网络安全法》**:数据保护和网络安全要求 +- **《数据安全法》**:数据分类分级保护 +- **《个人信息保护法》**:个人信息处理规范 +- **等保2.0**:信息系统安全等级保护 + +通过实施本安全文档中的各项措施,可以有效保护解班客项目的安全,确保用户数据和业务系统的安全稳定运行。 \ No newline at end of file diff --git a/docs/官网需求文档.md b/docs/官网需求文档.md new file mode 100644 index 0000000..9f6497f --- /dev/null +++ b/docs/官网需求文档.md @@ -0,0 +1,277 @@ +# 解班客官网需求文档 + +## 1. 项目概述 + +### 1.1 官网定位 +解班客官网作为品牌展示和商家入驻的主要平台,承担着品牌宣传、用户引流、商家服务、信息展示等重要职能。 + +### 1.2 目标用户 +- **潜在用户**:了解平台服务,下载小程序 +- **商家用户**:了解入驻政策,提交入驻申请 +- **媒体记者**:获取品牌资讯和新闻素材 +- **投资者**:了解公司发展和商业模式 + +### 1.3 核心目标 +- 提升品牌知名度和影响力 +- 吸引更多用户下载使用小程序 +- 为商家提供便捷的入驻渠道 +- 建立权威的信息发布平台 + +## 2. 功能需求 + +### 2.1 首页 +#### 2.1.1 品牌展示区 +- **品牌Logo和Slogan**:突出显示解班客品牌标识 +- **核心价值主张**:简洁明了地传达平台价值 +- **视觉冲击力**:使用高质量的背景图片或视频 +- **行动召唤按钮**:引导用户下载小程序 + +#### 2.1.2 功能介绍区 +- **结伴旅行功能**:图文并茂展示结伴旅行特色 +- **动物认领功能**:突出创新的动物认领服务 +- **商家服务功能**:展示多元化的商家服务 +- **用户评价展示**:真实用户反馈和评价 + +#### 2.1.3 数据展示区 +- **用户数量统计**:实时显示注册用户数 +- **活动数量统计**:展示平台活动总数 +- **认领动物数量**:显示已认领动物数量 +- **商家数量统计**:展示入驻商家数量 + +#### 2.1.4 新闻资讯区 +- **最新动态**:平台最新功能和活动 +- **媒体报道**:权威媒体的相关报道 +- **用户故事**:精选用户使用体验分享 +- **行业资讯**:相关行业的新闻动态 + +### 2.2 关于我们页面 +#### 2.2.1 公司介绍 +- **发展历程**:公司成立和发展的重要节点 +- **企业文化**:公司价值观和使命愿景 +- **团队介绍**:核心团队成员介绍 +- **荣誉资质**:获得的奖项和认证 + +#### 2.2.2 联系方式 +- **公司地址**:详细的办公地址和地图 +- **联系电话**:客服电话和工作时间 +- **邮箱地址**:官方邮箱和各部门邮箱 +- **社交媒体**:微信公众号、微博等链接 + +### 2.3 服务介绍页面 +#### 2.3.1 结伴旅行服务 +- **服务特色**:详细介绍结伴旅行的优势 +- **使用流程**:图文展示使用步骤 +- **安全保障**:用户安全和权益保护措施 +- **成功案例**:精选的成功结伴案例 + +#### 2.3.2 动物认领服务 +- **认领流程**:详细的认领步骤说明 +- **动物种类**:可认领的动物类型介绍 +- **农场介绍**:合作农场的基本信息 +- **探访服务**:农场探访的安排和服务 + +#### 2.3.3 商家服务 +- **服务类型**:支持的商家类型和服务 +- **入驻优势**:平台为商家提供的价值 +- **成功案例**:优秀商家的成功故事 +- **支持政策**:平台对商家的扶持政策 + +### 2.4 商家入驻页面 +#### 2.4.1 入驻申请表单 +- **基本信息**:商家名称、联系人、联系方式 +- **业务信息**:经营范围、服务类型、营业执照 +- **资质证明**:相关行业资质和证书上传 +- **店铺信息**:店铺介绍、地址、营业时间 + +#### 2.4.2 入驻政策 +- **入驻条件**:明确的入驻门槛和要求 +- **费用说明**:入驻费用和分成政策 +- **服务支持**:平台提供的技术和运营支持 +- **合作流程**:从申请到正式入驻的流程 + +#### 2.4.3 商家权益 +- **流量支持**:平台流量倾斜和推广支持 +- **技术支持**:系统使用培训和技术服务 +- **运营支持**:营销活动和运营指导 +- **数据支持**:经营数据分析和报告 + +### 2.5 新闻资讯页面 +#### 2.5.1 新闻分类 +- **公司动态**:公司发展和重要事件 +- **产品更新**:新功能发布和产品优化 +- **行业资讯**:相关行业的新闻和趋势 +- **用户故事**:用户使用体验和成功案例 + +#### 2.5.2 内容管理 +- **文章发布**:支持富文本编辑和图片上传 +- **分类管理**:新闻分类和标签管理 +- **评论功能**:用户评论和互动功能 +- **分享功能**:社交媒体分享功能 + +### 2.6 帮助中心页面 +#### 2.6.1 常见问题 +- **使用指南**:平台使用的详细说明 +- **账户问题**:注册、登录、密码等问题 +- **支付问题**:支付方式、退款等问题 +- **技术问题**:常见技术故障和解决方案 + +#### 2.6.2 用户协议 +- **服务协议**:用户使用平台的协议条款 +- **隐私政策**:用户隐私保护政策 +- **免责声明**:平台免责条款 +- **投诉举报**:投诉举报渠道和处理流程 + +### 2.7 下载页面 +#### 2.7.1 小程序下载 +- **微信小程序码**:高清的小程序二维码 +- **使用说明**:扫码使用的详细步骤 +- **功能预览**:小程序主要功能截图 +- **用户评价**:小程序的用户评分和评价 + +#### 2.7.2 其他下载 +- **APP下载**:未来APP版本的下载链接 +- **宣传资料**:公司宣传册和产品介绍 +- **媒体资源**:高清Logo、产品图片等 + +## 3. 设计需求 + +### 3.1 视觉设计 +#### 3.1.1 设计风格 +- **现代简约**:简洁大方的设计风格 +- **温馨友好**:体现社交和温暖的品牌调性 +- **专业可信**:建立用户信任感 +- **响应式设计**:适配各种设备和屏幕 + +#### 3.1.2 色彩方案 +- **主色调**:品牌主色,体现活力和温暖 +- **辅助色**:搭配色彩,增强视觉层次 +- **中性色**:文字和背景色,保证可读性 +- **强调色**:按钮和重要信息的突出色彩 + +#### 3.1.3 字体规范 +- **标题字体**:醒目的标题字体 +- **正文字体**:易读的正文字体 +- **字号层级**:清晰的字号层级体系 +- **行间距**:合适的行间距和段落间距 + +### 3.2 交互设计 +#### 3.2.1 导航设计 +- **主导航**:清晰的主要页面导航 +- **面包屑导航**:页面层级导航 +- **搜索功能**:全站内容搜索 +- **快速链接**:常用功能的快速入口 + +#### 3.2.2 用户体验 +- **加载速度**:页面快速加载 +- **交互反馈**:清晰的操作反馈 +- **错误处理**:友好的错误提示 +- **无障碍设计**:支持无障碍访问 + +### 3.3 移动端适配 +#### 3.3.1 响应式布局 +- **断点设置**:合理的响应式断点 +- **布局调整**:不同屏幕的布局优化 +- **图片适配**:图片的响应式处理 +- **字体缩放**:移动端字体大小调整 + +#### 3.3.2 触控优化 +- **按钮大小**:适合触控的按钮尺寸 +- **间距设置**:合适的元素间距 +- **滑动操作**:支持触控滑动 +- **手势支持**:常用手势操作支持 + +## 4. 技术需求 + +### 4.1 前端技术 +- **开发框架**:Vue 3 + JavaScript +- **UI框架**:自定义CSS + 响应式设计 +- **路由管理**:Vue Router +- **构建工具**:Vite +- **SEO优化**:Vue Meta + +### 4.2 后端支持 +- **内容管理**:支持动态内容更新 +- **数据接口**:提供必要的数据API +- **文件上传**:支持图片和文档上传 +- **表单处理**:处理用户提交的表单 + +### 4.3 SEO优化 +#### 4.3.1 搜索引擎优化 +- **关键词优化**:页面关键词布局 +- **Meta标签**:完善的Meta信息 +- **结构化数据**:Schema.org标记 +- **网站地图**:XML和HTML网站地图 + +#### 4.3.2 性能优化 +- **页面速度**:优化页面加载速度 +- **图片优化**:图片压缩和懒加载 +- **代码优化**:CSS和JS代码优化 +- **CDN加速**:使用CDN加速资源加载 + +## 5. 内容需求 + +### 5.1 文案内容 +- **品牌文案**:体现品牌价值的文案 +- **功能介绍**:清晰的功能说明文案 +- **用户指南**:详细的使用指南 +- **法律条款**:完善的法律条款文本 + +### 5.2 图片素材 +- **品牌图片**:高质量的品牌形象图片 +- **功能截图**:产品功能的截图展示 +- **用户照片**:真实用户的使用照片 +- **背景图片**:页面背景和装饰图片 + +### 5.3 视频内容 +- **产品介绍视频**:产品功能演示视频 +- **用户故事视频**:用户使用体验视频 +- **企业宣传视频**:公司形象宣传视频 +- **教程视频**:使用教程和指导视频 + +## 6. 运营需求 + +### 6.1 内容更新 +- **新闻发布**:定期发布新闻和动态 +- **内容维护**:及时更新过期内容 +- **图片更新**:定期更新产品图片 +- **数据更新**:实时更新统计数据 + +### 6.2 用户互动 +- **在线客服**:提供在线客服支持 +- **留言反馈**:用户留言和反馈功能 +- **社交分享**:支持社交媒体分享 +- **邮件订阅**:新闻邮件订阅功能 + +### 6.3 数据分析 +- **访问统计**:网站访问数据统计 +- **用户行为**:用户行为数据分析 +- **转化率**:页面转化率统计 +- **SEO数据**:搜索引擎优化数据 + +## 7. 安全需求 + +### 7.1 数据安全 +- **HTTPS加密**:全站HTTPS加密 +- **数据备份**:定期数据备份 +- **访问控制**:后台访问权限控制 +- **安全监控**:安全事件监控和报警 + +### 7.2 内容安全 +- **内容审核**:用户提交内容审核 +- **垃圾信息过滤**:防止垃圾信息和广告 +- **恶意攻击防护**:防止恶意攻击和爬虫 +- **版权保护**:保护原创内容版权 + +## 8. 维护需求 + +### 8.1 技术维护 +- **系统更新**:定期系统和安全更新 +- **性能监控**:网站性能监控和优化 +- **故障处理**:快速响应和处理故障 +- **备份恢复**:数据备份和恢复机制 + +### 8.2 内容维护 +- **内容更新**:定期更新网站内容 +- **链接检查**:检查和修复失效链接 +- **图片优化**:优化和更新图片资源 +- **SEO维护**:持续的SEO优化工作 \ No newline at end of file diff --git a/docs/小程序app开发文档.md b/docs/小程序app开发文档.md new file mode 100644 index 0000000..a62094f --- /dev/null +++ b/docs/小程序app开发文档.md @@ -0,0 +1,1050 @@ +# 小程序app开发文档 + +## 1. 项目概述 + +### 1.1 项目简介 +解班客小程序是一个专注于旅行结伴和动物认领的微信小程序应用,为用户提供便捷的移动端服务体验。 + +### 1.2 技术栈 +- **开发框架**:uni-app 3.x +- **开发语言**:Vue 3 + TypeScript +- **UI组件库**:uni-ui + 自定义组件 +- **状态管理**:Pinia +- **网络请求**:uni.request + 封装 +- **地图服务**:腾讯地图API +- **支付功能**:微信支付 +- **图片处理**:uni.chooseImage + 压缩 +- **构建工具**:Vite +- **代码规范**:ESLint + Prettier + +### 1.3 项目结构 +``` +mini-program/ +├── src/ +│ ├── pages/ # 页面文件 +│ │ ├── index/ # 首页 +│ │ ├── travel/ # 旅行相关页面 +│ │ ├── animal/ # 动物认领页面 +│ │ ├── user/ # 用户相关页面 +│ │ └── common/ # 通用页面 +│ ├── components/ # 组件 +│ │ ├── common/ # 通用组件 +│ │ ├── travel/ # 旅行组件 +│ │ └── animal/ # 动物组件 +│ ├── store/ # 状态管理 +│ ├── utils/ # 工具函数 +│ ├── services/ # API服务 +│ ├── types/ # 类型定义 +│ ├── styles/ # 样式文件 +│ ├── static/ # 静态资源 +│ ├── App.vue # 应用入口 +│ ├── main.ts # 主文件 +│ └── manifest.json # 应用配置 +├── tests/ # 测试文件 +├── docs/ # 文档 +├── package.json +├── tsconfig.json +├── vite.config.ts +└── README.md +``` + +## 2. 开发环境搭建 + +### 2.1 环境要求 +- Node.js >= 16.0.0 +- npm >= 8.0.0 +- 微信开发者工具 +- HBuilderX(可选) + +### 2.2 环境搭建步骤 + +#### 2.2.1 安装开发工具 +```bash +# 安装HBuilderX或使用VSCode +# 下载微信开发者工具 +``` + +#### 2.2.2 创建项目 +```bash +# 使用uni-app CLI创建项目 +npx @dcloudio/uvm@latest create jiebanke-miniprogram +cd jiebanke-miniprogram +``` + +#### 2.2.3 安装依赖 +```bash +npm install +``` + +#### 2.2.4 配置开发环境 +```bash +# 复制环境配置文件 +cp .env.example .env.development +# 编辑配置文件 +``` + +#### 2.2.5 启动开发服务器 +```bash +npm run dev:mp-weixin +``` + +### 2.3 开发工具配置 + +#### 2.3.1 VSCode配置 +推荐安装以下插件: +- Vetur +- TypeScript Importer +- ESLint +- Prettier +- uni-app-schemas +- uni-app-snippets + +#### 2.3.2 微信开发者工具配置 +- 导入项目:选择dist/dev/mp-weixin目录 +- 配置AppID:在manifest.json中配置 +- 开启ES6转ES5 +- 开启增强编译 + +## 3. 开发计划与任务分解 + +### 3.1 开发阶段划分 + +#### 阶段一:基础框架搭建(预计8个工作日) +- 项目初始化和环境配置 +- 基础组件开发 +- 路由和状态管理配置 +- 通用工具类开发 + +#### 阶段二:核心页面开发(预计20个工作日) +- 首页和导航 +- 用户系统页面 +- 旅行结伴页面 +- 动物认领页面 + +#### 阶段三:功能完善(预计12个工作日) +- 支付功能集成 +- 消息通知功能 +- 搜索和筛选功能 +- 个人中心功能 + +#### 阶段四:优化和测试(预计8个工作日) +- 性能优化 +- 兼容性测试 +- 用户体验优化 +- 发布准备 + +### 3.2 详细任务分解 + +#### 3.2.1 阶段一:基础框架搭建 + +##### 任务1.1:项目初始化(2个工作日) +**负责人**:前端架构师 + 小程序开发工程师 +**工时估算**:16人时 +**任务描述**: +- 创建uni-app项目结构 +- 配置TypeScript和构建工具 +- 设置代码规范和Git hooks +- 配置开发环境 + +**具体子任务**: +1. 创建uni-app项目和目录结构(4人时) +2. 配置TypeScript和Vite(4人时) +3. 设置ESLint和Prettier(2人时) +4. 配置环境变量和构建脚本(3人时) +5. 设置Git hooks和提交规范(3人时) + +**验收标准**: +- 项目可以正常编译和运行 +- 代码规范检查通过 +- 开发环境配置完整 + +##### 任务1.2:基础组件开发(3个工作日) +**负责人**:小程序开发工程师 +**工时估算**:24人时 +**任务描述**: +- 开发通用UI组件 +- 创建布局组件 +- 开发表单组件 +- 创建反馈组件 + +**具体子任务**: +1. 按钮、输入框等基础组件(6人时) +2. 导航栏、标签栏组件(4人时) +3. 列表、卡片组件(6人时) +4. 弹窗、提示组件(4人时) +5. 加载、空状态组件(4人时) + +**验收标准**: +- 组件功能完整 +- 样式符合设计规范 +- 支持自定义配置 + +##### 任务1.3:路由和状态管理(2个工作日) +**负责人**:小程序开发工程师 +**工时估算**:16人时 +**任务描述**: +- 配置页面路由 +- 设置Pinia状态管理 +- 创建全局状态模块 +- 实现数据持久化 + +**具体子任务**: +1. 配置pages.json路由(4人时) +2. 设置Pinia store结构(4人时) +3. 创建用户状态模块(4人时) +4. 实现本地存储封装(4人时) + +**验收标准**: +- 路由跳转正常 +- 状态管理功能完整 +- 数据持久化正常 + +##### 任务1.4:通用工具类开发(1个工作日) +**负责人**:小程序开发工程师 +**工时估算**:8人时 +**任务描述**: +- 网络请求封装 +- 工具函数开发 +- 常量定义 +- 类型定义 + +**具体子任务**: +1. HTTP请求封装(3人时) +2. 日期、字符串工具函数(2人时) +3. 常量和枚举定义(2人时) +4. TypeScript类型定义(1人时) + +**验收标准**: +- 网络请求功能完整 +- 工具函数覆盖常用场景 +- 类型定义准确 + +#### 3.2.2 阶段二:核心页面开发 + +##### 任务2.1:首页和导航(3个工作日) +**负责人**:小程序开发工程师 +**工时估算**:24人时 +**任务描述**: +- 首页布局和功能 +- 底部导航栏 +- 搜索功能 +- 轮播图和推荐 + +**具体子任务**: +1. 首页整体布局(6人时) +2. 轮播图组件(4人时) +3. 推荐内容展示(6人时) +4. 搜索功能实现(4人时) +5. 底部导航栏(4人时) + +**验收标准**: +- 首页布局美观 +- 导航功能正常 +- 搜索结果准确 + +##### 任务2.2:用户系统页面(4个工作日) +**负责人**:小程序开发工程师 +**工时估算**:32人时 +**任务描述**: +- 登录授权页面 +- 个人信息页面 +- 设置页面 +- 实名认证页面 + +**具体子任务**: +1. 微信登录授权页面(6人时) +2. 个人信息展示和编辑(8人时) +3. 用户设置页面(6人时) +4. 实名认证流程页面(6人时) +5. 头像上传功能(6人时) + +**验收标准**: +- 登录流程顺畅 +- 个人信息管理完整 +- 实名认证功能正常 + +##### 任务2.3:旅行结伴页面(7个工作日) +**负责人**:小程序开发工程师 +**工时估算**:56人时 +**任务描述**: +- 旅行列表页面 +- 旅行详情页面 +- 发布旅行页面 +- 申请参与页面 + +**具体子任务**: +1. 旅行活动列表页面(10人时) +2. 旅行详情页面(12人时) +3. 发布旅行活动页面(12人时) +4. 申请参与页面(8人时) +5. 我的旅行页面(8人时) +6. 旅行搜索和筛选(6人时) + +**验收标准**: +- 列表展示美观 +- 详情信息完整 +- 发布流程顺畅 +- 申请功能正常 + +##### 任务2.4:动物认领页面(6个工作日) +**负责人**:小程序开发工程师 +**工时估算**:48人时 +**任务描述**: +- 动物列表页面 +- 动物详情页面 +- 认领申请页面 +- 认领记录页面 + +**具体子任务**: +1. 动物列表页面(10人时) +2. 动物详情页面(12人时) +3. 认领申请页面(10人时) +4. 我的认领页面(8人时) +5. 认领动态页面(8人时) + +**验收标准**: +- 动物信息展示完整 +- 认领流程清晰 +- 动态更新及时 + +#### 3.2.3 阶段三:功能完善 + +##### 任务3.1:支付功能集成(3个工作日) +**负责人**:小程序开发工程师 +**工时估算**:24人时 +**任务描述**: +- 订单确认页面 +- 微信支付集成 +- 支付结果页面 +- 订单管理页面 + +**具体子任务**: +1. 订单确认页面(6人时) +2. 微信支付功能集成(8人时) +3. 支付结果处理(4人时) +4. 订单列表和详情(6人时) + +**验收标准**: +- 支付流程完整 +- 支付结果准确 +- 订单管理功能正常 + +##### 任务3.2:消息通知功能(2个工作日) +**负责人**:小程序开发工程师 +**工时估算**:16人时 +**任务描述**: +- 消息列表页面 +- 消息详情页面 +- 消息推送处理 +- 消息状态管理 + +**具体子任务**: +1. 消息列表页面(6人时) +2. 消息详情页面(4人时) +3. 消息推送处理(4人时) +4. 消息状态管理(2人时) + +**验收标准**: +- 消息展示正常 +- 推送功能正常 +- 状态更新及时 + +##### 任务3.3:搜索和筛选功能(3个工作日) +**负责人**:小程序开发工程师 +**工时估算**:24人时 +**任务描述**: +- 搜索页面优化 +- 高级筛选功能 +- 搜索历史管理 +- 搜索建议功能 + +**具体子任务**: +1. 搜索页面优化(6人时) +2. 筛选条件组件(8人时) +3. 搜索历史功能(4人时) +4. 搜索建议实现(6人时) + +**验收标准**: +- 搜索功能完善 +- 筛选条件准确 +- 用户体验良好 + +##### 任务3.4:个人中心功能(4个工作日) +**负责人**:小程序开发工程师 +**工时估算**:32人时 +**任务描述**: +- 个人中心首页 +- 收藏和点赞管理 +- 关注和粉丝功能 +- 设置和帮助页面 + +**具体子任务**: +1. 个人中心首页(8人时) +2. 收藏和点赞页面(8人时) +3. 关注和粉丝页面(8人时) +4. 设置和帮助页面(8人时) + +**验收标准**: +- 个人中心功能完整 +- 社交功能正常 +- 设置项目齐全 + +#### 3.2.4 阶段四:优化和测试 + +##### 任务4.1:性能优化(3个工作日) +**负责人**:小程序开发工程师 +**工时估算**:24人时 +**任务描述**: +- 页面加载优化 +- 图片懒加载 +- 代码分包优化 +- 缓存策略优化 + +**具体子任务**: +1. 页面加载性能优化(8人时) +2. 图片懒加载实现(6人时) +3. 代码分包配置(4人时) +4. 缓存策略优化(6人时) + +**验收标准**: +- 页面加载速度提升30% +- 图片加载流畅 +- 包体积控制在合理范围 + +##### 任务4.2:兼容性测试(2个工作日) +**负责人**:小程序开发工程师 + 测试工程师 +**工时估算**:16人时 +**任务描述**: +- 不同机型测试 +- 不同版本测试 +- 网络环境测试 +- 边界情况测试 + +**具体子任务**: +1. iOS和Android兼容性测试(6人时) +2. 不同微信版本测试(4人时) +3. 弱网络环境测试(3人时) +4. 边界情况和异常测试(3人时) + +**验收标准**: +- 主流机型兼容性良好 +- 弱网络环境可用 +- 异常情况处理正确 + +##### 任务4.3:用户体验优化(2个工作日) +**负责人**:小程序开发工程师 + UI设计师 +**工时估算**:16人时 +**任务描述**: +- 交互体验优化 +- 视觉效果优化 +- 无障碍功能 +- 用户反馈收集 + +**具体子任务**: +1. 交互动画和反馈优化(6人时) +2. 视觉效果和样式调整(6人时) +3. 无障碍功能支持(2人时) +4. 用户反馈机制(2人时) + +**验收标准**: +- 交互体验流畅 +- 视觉效果美观 +- 支持无障碍访问 + +##### 任务4.4:发布准备(1个工作日) +**负责人**:小程序开发工程师 +**工时估算**:8人时 +**任务描述**: +- 代码审查和清理 +- 版本号管理 +- 发布配置 +- 文档整理 + +**具体子任务**: +1. 代码审查和优化(3人时) +2. 版本号和更新日志(2人时) +3. 发布配置检查(2人时) +4. 用户手册整理(1人时) + +**验收标准**: +- 代码质量达标 +- 发布配置正确 +- 文档完整 + +## 4. 开发规范 + +### 4.1 代码规范 + +#### 4.1.1 命名规范 +- **文件命名**:使用kebab-case,如`user-profile.vue` +- **组件命名**:使用PascalCase,如`UserProfile` +- **方法命名**:使用camelCase,如`getUserInfo` +- **常量命名**:使用UPPER_SNAKE_CASE,如`API_BASE_URL` + +#### 4.1.2 目录结构规范 +``` +src/ +├── pages/ +│ ├── index/ +│ │ ├── index.vue +│ │ └── index.scss +│ └── user/ +│ ├── profile/ +│ │ ├── profile.vue +│ │ └── profile.scss +│ └── settings/ +│ ├── settings.vue +│ └── settings.scss +├── components/ +│ ├── common/ +│ │ ├── Button/ +│ │ │ ├── Button.vue +│ │ │ └── Button.scss +│ │ └── Input/ +│ │ ├── Input.vue +│ │ └── Input.scss +│ └── business/ +│ ├── TravelCard/ +│ └── AnimalCard/ +``` + +#### 4.1.3 Vue组件规范 +```vue + + + + + +``` + +### 4.2 API调用规范 + +#### 4.2.1 服务层封装 +```typescript +// services/user.service.ts +import { http } from '@/utils/http' +import type { UserInfo, LoginParams } from '@/types/user' + +export class UserService { + /** + * 用户登录 + */ + static async login(params: LoginParams): Promise { + return http.post('/auth/wechat/login', params) + } + + /** + * 获取用户信息 + */ + static async getUserInfo(): Promise { + return http.get('/auth/me') + } + + /** + * 更新用户信息 + */ + static async updateUserInfo(data: Partial): Promise { + return http.put('/auth/profile', data) + } +} +``` + +#### 4.2.2 HTTP请求封装 +```typescript +// utils/http.ts +import type { RequestOptions } from '@/types/http' + +class HttpClient { + private baseURL = process.env.VUE_APP_API_BASE_URL + private timeout = 10000 + + async request(options: RequestOptions): Promise { + return new Promise((resolve, reject) => { + uni.request({ + url: this.baseURL + options.url, + method: options.method || 'GET', + data: options.data, + header: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${uni.getStorageSync('token')}`, + ...options.header + }, + timeout: this.timeout, + success: (res) => { + if (res.statusCode === 200) { + const data = res.data as any + if (data.code === 200) { + resolve(data.data) + } else { + reject(new Error(data.message)) + } + } else { + reject(new Error('网络请求失败')) + } + }, + fail: (err) => { + reject(err) + } + }) + }) + } + + get(url: string, params?: any): Promise { + return this.request({ url, method: 'GET', data: params }) + } + + post(url: string, data?: any): Promise { + return this.request({ url, method: 'POST', data }) + } + + put(url: string, data?: any): Promise { + return this.request({ url, method: 'PUT', data }) + } + + delete(url: string): Promise { + return this.request({ url, method: 'DELETE' }) + } +} + +export const http = new HttpClient() +``` + +### 4.3 状态管理规范 + +#### 4.3.1 Store结构 +```typescript +// store/user.ts +import { defineStore } from 'pinia' +import { UserService } from '@/services/user.service' +import type { UserInfo } from '@/types/user' + +export const useUserStore = defineStore('user', { + state: () => ({ + userInfo: {} as UserInfo, + token: '', + isLoggedIn: false + }), + + getters: { + isProfileCompleted: (state) => { + return !!(state.userInfo.nickname && state.userInfo.avatar_url) + } + }, + + actions: { + async login(code: string) { + try { + const result = await UserService.login({ code }) + this.token = result.access_token + this.userInfo = result.user_info + this.isLoggedIn = true + + // 保存到本地存储 + uni.setStorageSync('token', this.token) + uni.setStorageSync('userInfo', this.userInfo) + } catch (error) { + throw error + } + }, + + async getUserInfo() { + try { + this.userInfo = await UserService.getUserInfo() + uni.setStorageSync('userInfo', this.userInfo) + } catch (error) { + throw error + } + }, + + logout() { + this.token = '' + this.userInfo = {} as UserInfo + this.isLoggedIn = false + + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + } + } +}) +``` + +### 4.4 样式规范 + +#### 4.4.1 SCSS变量定义 +```scss +// styles/variables.scss +// 颜色变量 +$primary-color: #007aff; +$success-color: #4cd964; +$warning-color: #f0ad4e; +$error-color: #dd524d; + +// 字体大小 +$font-size-xs: 20rpx; +$font-size-sm: 24rpx; +$font-size-base: 28rpx; +$font-size-lg: 32rpx; +$font-size-xl: 36rpx; + +// 间距 +$spacing-xs: 10rpx; +$spacing-sm: 20rpx; +$spacing-base: 30rpx; +$spacing-lg: 40rpx; +$spacing-xl: 50rpx; + +// 圆角 +$border-radius-sm: 8rpx; +$border-radius-base: 12rpx; +$border-radius-lg: 16rpx; +``` + +#### 4.4.2 通用样式类 +```scss +// styles/common.scss +// 布局类 +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +// 文本类 +.text-center { + text-align: center; +} + +.text-primary { + color: $primary-color; +} + +.text-success { + color: $success-color; +} + +// 间距类 +.m-0 { margin: 0; } +.m-1 { margin: $spacing-xs; } +.m-2 { margin: $spacing-sm; } +.m-3 { margin: $spacing-base; } + +.p-0 { padding: 0; } +.p-1 { padding: $spacing-xs; } +.p-2 { padding: $spacing-sm; } +.p-3 { padding: $spacing-base; } +``` + +## 5. 质量保证 + +### 5.1 代码质量检查 + +#### 5.1.1 ESLint配置 +```javascript +// .eslintrc.js +module.exports = { + extends: [ + '@vue/typescript/recommended', + '@vue/prettier', + '@vue/prettier/@typescript-eslint' + ], + rules: { + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/explicit-function-return-type': 'warn', + 'vue/component-name-in-template-casing': ['error', 'kebab-case'], + 'vue/no-unused-components': 'error' + } +} +``` + +#### 5.1.2 代码审查流程 +1. 开发者提交Pull Request +2. 自动化检查(ESLint、TypeScript、构建) +3. 同行代码审查 +4. 技术负责人审查 +5. 合并到主分支 + +### 5.2 测试策略 + +#### 5.2.1 单元测试 +```typescript +// tests/components/Button.spec.ts +import { mount } from '@vue/test-utils' +import Button from '@/components/common/Button/Button.vue' + +describe('Button Component', () => { + it('renders correctly', () => { + const wrapper = mount(Button, { + props: { + text: 'Click me', + type: 'primary' + } + }) + + expect(wrapper.text()).toBe('Click me') + expect(wrapper.classes()).toContain('button--primary') + }) + + it('emits click event', async () => { + const wrapper = mount(Button) + await wrapper.trigger('click') + + expect(wrapper.emitted('click')).toBeTruthy() + }) +}) +``` + +#### 5.2.2 端到端测试 +```typescript +// tests/e2e/login.spec.ts +describe('Login Flow', () => { + it('should login successfully', () => { + // 模拟微信登录流程 + cy.visit('/pages/login/login') + cy.get('[data-testid="login-button"]').click() + cy.url().should('include', '/pages/index/index') + }) +}) +``` + +### 5.3 性能要求 + +#### 5.3.1 页面性能指标 +- **首屏加载时间**:<2s +- **页面切换时间**:<500ms +- **图片加载时间**:<1s +- **接口响应时间**:<1s + +#### 5.3.2 包体积要求 +- **主包大小**:<2MB +- **分包大小**:<2MB +- **总包大小**:<20MB +- **图片资源**:<5MB + +## 6. 部署和发布 + +### 6.1 构建配置 + +#### 6.1.1 生产环境构建 +```bash +# 构建微信小程序 +npm run build:mp-weixin + +# 构建其他平台 +npm run build:h5 +npm run build:app +``` + +#### 6.1.2 环境配置 +```typescript +// .env.production +VUE_APP_API_BASE_URL=https://api.jiebanke.com +VUE_APP_CDN_BASE_URL=https://cdn.jiebanke.com +VUE_APP_WECHAT_APP_ID=wx1234567890abcdef +``` + +### 6.2 发布流程 + +#### 6.2.1 版本管理 +```json +// package.json +{ + "version": "1.0.0", + "scripts": { + "version:patch": "npm version patch", + "version:minor": "npm version minor", + "version:major": "npm version major" + } +} +``` + +#### 6.2.2 发布步骤 +1. 代码审查通过 +2. 构建生产版本 +3. 上传到微信开发者工具 +4. 提交审核 +5. 发布上线 + +### 6.3 监控和分析 + +#### 6.3.1 性能监控 +```typescript +// utils/monitor.ts +export class PerformanceMonitor { + static trackPageLoad(pageName: string) { + const startTime = Date.now() + + return () => { + const loadTime = Date.now() - startTime + console.log(`Page ${pageName} loaded in ${loadTime}ms`) + + // 上报性能数据 + this.reportPerformance({ + page: pageName, + loadTime, + timestamp: Date.now() + }) + } + } + + static reportPerformance(data: any) { + // 上报到监控平台 + uni.request({ + url: 'https://monitor.jiebanke.com/performance', + method: 'POST', + data + }) + } +} +``` + +#### 6.3.2 错误监控 +```typescript +// utils/error-handler.ts +export class ErrorHandler { + static init() { + // 全局错误处理 + uni.onError((error) => { + console.error('Global error:', error) + this.reportError({ + type: 'javascript', + message: error.message, + stack: error.stack, + timestamp: Date.now() + }) + }) + + // 未处理的Promise错误 + uni.onUnhandledRejection((event) => { + console.error('Unhandled promise rejection:', event.reason) + this.reportError({ + type: 'promise', + message: event.reason, + timestamp: Date.now() + }) + }) + } + + static reportError(error: any) { + // 上报错误信息 + uni.request({ + url: 'https://monitor.jiebanke.com/error', + method: 'POST', + data: error + }) + } +} +``` + +## 7. 风险管理 + +### 7.1 技术风险 + +#### 7.1.1 兼容性风险 +- **风险**:不同机型和微信版本兼容性问题 +- **应对**:充分测试、降级方案、polyfill +- **监控**:用户反馈、错误日志 + +#### 7.1.2 性能风险 +- **风险**:页面加载慢、卡顿 +- **应对**:代码分包、图片优化、缓存策略 +- **监控**:性能指标、用户体验数据 + +### 7.2 业务风险 + +#### 7.2.1 审核风险 +- **风险**:小程序审核不通过 +- **应对**:遵循微信规范、提前沟通 +- **监控**:审核状态、反馈信息 + +#### 7.2.2 用户体验风险 +- **风险**:用户流失、满意度下降 +- **应对**:用户测试、快速迭代 +- **监控**:用户行为、反馈数据 + +## 8. 总结 + +本开发文档详细规划了解班客小程序的开发计划,包括: + +### 8.1 开发计划 +- **总工期**:48个工作日 +- **团队规模**:2-3名小程序开发工程师 +- **关键里程碑**:基础框架、核心页面、功能完善、优化测试 + +### 8.2 技术架构 +- **开发框架**:uni-app + Vue 3 + TypeScript +- **状态管理**:Pinia +- **UI组件**:uni-ui + 自定义组件 +- **构建工具**:Vite + +### 8.3 质量保证 +- **代码规范**:ESLint + Prettier +- **测试策略**:单元测试 + 端到端测试 +- **性能优化**:分包、懒加载、缓存 +- **监控体系**:性能监控 + 错误监控 + +### 8.4 风险控制 +- **兼容性测试**:多机型、多版本 +- **性能优化**:加载速度、响应时间 +- **审核合规**:遵循微信规范 +- **用户体验**:持续优化、快速响应 + +通过严格按照本开发文档执行,可以确保小程序的高质量交付和良好的用户体验。 \ No newline at end of file diff --git a/docs/小程序app接口设计文档.md b/docs/小程序app接口设计文档.md new file mode 100644 index 0000000..86d2d09 --- /dev/null +++ b/docs/小程序app接口设计文档.md @@ -0,0 +1,1813 @@ +# 小程序app接口设计文档 + +## 1. 概述 + +### 1.1 项目简介 +本文档详细描述了解班客小程序端的所有API接口设计,包括用户系统、旅行结伴、动物认领、支付系统、消息通知等模块的接口规范。 + +### 1.2 接口设计原则 +- **RESTful风格**:遵循REST架构风格 +- **统一响应格式**:所有接口采用统一的响应格式 +- **微信生态集成**:深度集成微信小程序能力 +- **安全认证**:基于微信授权和JWT Token +- **性能优化**:针对移动端网络环境优化 + +### 1.3 技术规范 +- **协议**:HTTPS +- **数据格式**:JSON +- **字符编码**:UTF-8 +- **认证方式**:微信授权 + JWT Token +- **API版本**:v1 + +## 2. 通用规范 + +### 2.1 请求格式 + +#### 2.1.1 请求头 +```http +Content-Type: application/json +Authorization: Bearer +Accept: application/json +User-Agent: JieBanKe-MiniProgram/1.0.0 +X-Platform: miniprogram +X-Version: 1.0.0 +``` + +#### 2.1.2 请求参数 +- **路径参数**:用于资源标识,如 `/api/v1/travels/{id}` +- **查询参数**:用于过滤、排序、分页等,如 `?page=1&size=10&sort=created_at:desc` +- **请求体**:JSON格式的数据 + +### 2.2 响应格式 + +#### 2.2.1 成功响应 +```json +{ + "code": 200, + "message": "操作成功", + "data": { + // 具体数据 + }, + "timestamp": "2024-01-15T10:30:00Z", + "request_id": "req_123456789" +} +``` + +#### 2.2.2 分页响应 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + // 数据列表 + ], + "pagination": { + "page": 1, + "size": 10, + "total": 50, + "pages": 5, + "has_next": true, + "has_prev": false + } + }, + "timestamp": "2024-01-15T10:30:00Z", + "request_id": "req_123456789" +} +``` + +#### 2.2.3 错误响应 +```json +{ + "code": 400, + "message": "请求参数错误", + "error": { + "type": "VALIDATION_ERROR", + "details": [ + { + "field": "phone", + "message": "手机号格式不正确" + } + ] + }, + "timestamp": "2024-01-15T10:30:00Z", + "request_id": "req_123456789" +} +``` + +### 2.3 状态码规范 + +| 状态码 | 说明 | 使用场景 | +|--------|------|----------| +| 200 | 成功 | 请求成功处理 | +| 201 | 创建成功 | 资源创建成功 | +| 204 | 无内容 | 删除成功或更新成功无返回内容 | +| 400 | 请求错误 | 参数错误、格式错误 | +| 401 | 未授权 | 未登录或token无效 | +| 403 | 禁止访问 | 权限不足 | +| 404 | 资源不存在 | 请求的资源不存在 | +| 422 | 实体错误 | 业务逻辑错误 | +| 500 | 服务器错误 | 内部服务器错误 | + +### 2.4 分页参数 + +| 参数名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| page | integer | 1 | 页码,从1开始 | +| size | integer | 10 | 每页数量,最大50 | +| sort | string | created_at:desc | 排序字段和方向 | + +## 3. 用户认证接口 + +### 3.1 微信登录 + +#### 接口信息 +- **接口路径**:`POST /api/v1/auth/wechat/login` +- **接口描述**:微信小程序登录 +- **是否需要认证**:否 + +#### 请求参数 +```json +{ + "code": "wx_code_from_login", + "encrypted_data": "encrypted_user_info", + "iv": "initialization_vector", + "raw_data": "raw_user_data", + "signature": "data_signature" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| code | string | 是 | 微信登录凭证 | +| encrypted_data | string | 否 | 加密的用户信息 | +| iv | string | 否 | 加密算法的初始向量 | +| raw_data | string | 否 | 原始数据字符串 | +| signature | string | 否 | 数据签名 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expires_in": 7200, + "token_type": "Bearer", + "user_info": { + "id": 1, + "uuid": "user_123456", + "openid": "wx_openid_123", + "unionid": "wx_unionid_456", + "nickname": "微信用户", + "avatar_url": "https://wx.qlogo.cn/avatar.jpg", + "gender": 1, + "city": "北京", + "province": "北京", + "country": "中国", + "is_new_user": false, + "profile_completed": true + } + } +} +``` + +### 3.2 刷新令牌 + +#### 接口信息 +- **接口路径**:`POST /api/v1/auth/refresh` +- **接口描述**:刷新访问令牌 +- **是否需要认证**:否 + +#### 请求参数 +```json +{ + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "令牌刷新成功", + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expires_in": 7200, + "token_type": "Bearer" + } +} +``` + +### 3.3 获取用户信息 + +#### 接口信息 +- **接口路径**:`GET /api/v1/auth/me` +- **接口描述**:获取当前用户信息 +- **是否需要认证**:是 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "id": 1, + "uuid": "user_123456", + "nickname": "旅行达人", + "avatar_url": "https://example.com/avatar.jpg", + "gender": 1, + "birthday": "1990-01-01", + "location": "北京市", + "bio": "热爱旅行和小动物", + "phone": "13800138000", + "email": "user@example.com", + "status": 1, + "profile_completed": true, + "real_name_verified": true, + "phone_verified": true, + "email_verified": false, + "created_at": "2024-01-01T10:00:00Z", + "statistics": { + "travel_count": 5, + "adoption_count": 2, + "following_count": 15, + "followers_count": 8, + "like_count": 23 + } + } +} +``` + +### 3.4 更新用户信息 + +#### 接口信息 +- **接口路径**:`PUT /api/v1/auth/profile` +- **接口描述**:更新用户个人信息 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "nickname": "新昵称", + "avatar_url": "https://example.com/new_avatar.jpg", + "gender": 2, + "birthday": "1995-05-15", + "location": "上海市", + "bio": "更新的个人简介", + "interests": ["旅行", "摄影", "美食"], + "occupation": "软件工程师" +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "更新成功", + "data": { + "id": 1, + "nickname": "新昵称", + "avatar_url": "https://example.com/new_avatar.jpg", + "updated_at": "2024-01-15T10:30:00Z" + } +} +``` + +## 4. 旅行结伴接口 + +### 4.1 获取旅行列表 + +#### 接口信息 +- **接口路径**:`GET /api/v1/travels` +- **接口描述**:分页获取旅行活动列表 +- **是否需要认证**:否 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | integer | 否 | 页码,默认1 | +| size | integer | 否 | 每页数量,默认10 | +| sort | string | 否 | 排序,默认created_at:desc | +| keyword | string | 否 | 搜索关键词 | +| destination | string | 否 | 目的地 | +| travel_type | string | 否 | 旅行类型 | +| start_date | string | 否 | 开始日期(YYYY-MM-DD) | +| end_date | string | 否 | 结束日期(YYYY-MM-DD) | +| budget_min | number | 否 | 预算下限 | +| budget_max | number | 否 | 预算上限 | +| status | integer | 否 | 状态:1-招募中,2-进行中,3-已完成 | +| is_featured | integer | 否 | 是否精选:1-是 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "uuid": "travel_123456", + "title": "云南大理古城深度游", + "description": "探索大理古城的历史文化...", + "destination": "云南大理", + "start_date": "2024-03-01", + "end_date": "2024-03-05", + "duration_days": 5, + "max_participants": 8, + "current_participants": 3, + "budget_min": 2000.00, + "budget_max": 3000.00, + "travel_type": "cultural", + "status": 1, + "is_featured": 1, + "view_count": 156, + "like_count": 23, + "comment_count": 8, + "creator": { + "id": 1, + "nickname": "旅行达人", + "avatar_url": "https://example.com/avatar.jpg" + }, + "cover_image": "https://example.com/cover.jpg", + "images": [ + "https://example.com/image1.jpg", + "https://example.com/image2.jpg" + ], + "tags": ["文化", "古城", "摄影"], + "created_at": "2024-01-15T10:00:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 50, + "pages": 5, + "has_next": true, + "has_prev": false + } + } +} +``` + +### 4.2 获取旅行详情 + +#### 接口信息 +- **接口路径**:`GET /api/v1/travels/{id}` +- **接口描述**:获取旅行活动详细信息 +- **是否需要认证**:否 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "id": 1, + "uuid": "travel_123456", + "title": "云南大理古城深度游", + "description": "探索大理古城的历史文化,体验白族风情,品尝当地美食...", + "destination": "云南大理", + "destination_detail": { + "province": "云南省", + "city": "大理市", + "district": "大理古城", + "address": "大理古城南门", + "latitude": 25.6916, + "longitude": 100.1669 + }, + "start_date": "2024-03-01", + "end_date": "2024-03-05", + "duration_days": 5, + "max_participants": 8, + "current_participants": 3, + "min_age": 18, + "max_age": 60, + "gender_limit": 0, + "budget_min": 2000.00, + "budget_max": 3000.00, + "travel_type": "cultural", + "transportation": "高铁+包车", + "accommodation": "客栈", + "itinerary": [ + { + "day": 1, + "title": "抵达大理", + "activities": ["接机", "入住客栈", "古城夜游"], + "meals": ["晚餐"], + "accommodation": "大理古城客栈" + }, + { + "day": 2, + "title": "大理古城游览", + "activities": ["洱海骑行", "三塔寺参观", "古城购物"], + "meals": ["早餐", "午餐", "晚餐"], + "accommodation": "大理古城客栈" + } + ], + "requirements": "身体健康,有一定体力,热爱文化旅行", + "included_services": ["住宿", "早餐", "景点门票", "导游服务"], + "excluded_services": ["往返交通", "午晚餐", "个人消费"], + "contact_info": { + "wechat": "traveler001", + "phone": "13800138000" + }, + "images": [ + "https://example.com/image1.jpg", + "https://example.com/image2.jpg" + ], + "tags": ["文化", "古城", "摄影"], + "status": 1, + "is_featured": 1, + "view_count": 156, + "like_count": 23, + "comment_count": 8, + "share_count": 5, + "creator": { + "id": 1, + "nickname": "旅行达人", + "avatar_url": "https://example.com/avatar.jpg", + "travel_count": 15, + "rating": 4.8, + "verified": true + }, + "participants": [ + { + "id": 1, + "user": { + "id": 2, + "nickname": "小明", + "avatar_url": "https://example.com/avatar2.jpg", + "age": 28, + "gender": 1 + }, + "status": 1, + "applied_at": "2024-01-10T15:30:00Z" + } + ], + "is_liked": false, + "is_applied": false, + "can_apply": true, + "created_at": "2024-01-15T10:00:00Z" + } +} +``` + +### 4.3 创建旅行活动 + +#### 接口信息 +- **接口路径**:`POST /api/v1/travels` +- **接口描述**:创建新的旅行活动 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "title": "西藏拉萨朝圣之旅", + "description": "深度体验西藏文化,朝圣布达拉宫...", + "destination": "西藏拉萨", + "destination_detail": { + "province": "西藏自治区", + "city": "拉萨市", + "address": "布达拉宫广场", + "latitude": 29.6544, + "longitude": 91.1175 + }, + "start_date": "2024-04-01", + "end_date": "2024-04-07", + "max_participants": 6, + "min_age": 20, + "max_age": 50, + "gender_limit": 0, + "budget_min": 5000.00, + "budget_max": 8000.00, + "travel_type": "cultural", + "transportation": "飞机+包车", + "accommodation": "酒店", + "itinerary": [ + { + "day": 1, + "title": "抵达拉萨", + "activities": ["接机", "入住酒店", "适应高原"], + "meals": ["晚餐"] + } + ], + "requirements": "身体健康,无高原反应病史", + "included_services": ["住宿", "早餐", "景点门票"], + "excluded_services": ["往返机票", "午晚餐"], + "contact_info": { + "wechat": "tibet_lover", + "phone": "13900139000" + }, + "images": [ + "https://example.com/tibet1.jpg", + "https://example.com/tibet2.jpg" + ], + "tags": ["西藏", "文化", "朝圣"] +} +``` + +#### 响应示例 +```json +{ + "code": 201, + "message": "创建成功", + "data": { + "id": 101, + "uuid": "travel_654321", + "title": "西藏拉萨朝圣之旅", + "status": 0, + "created_at": "2024-01-15T10:30:00Z" + } +} +``` + +### 4.4 申请参加旅行 + +#### 接口信息 +- **接口路径**:`POST /api/v1/travels/{id}/apply` +- **接口描述**:申请参加旅行活动 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "join_reason": "对大理文化很感兴趣,希望能参加这次深度游", + "contact_info": { + "wechat": "xiaoming123", + "phone": "13900139000" + }, + "emergency_contact": { + "name": "李四", + "phone": "13800138000", + "relationship": "朋友" + }, + "special_requirements": "素食主义者,需要素食安排", + "travel_experience": "去过云南多次,有丰富的旅行经验" +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "申请成功", + "data": { + "application_id": 1, + "status": 0, + "applied_at": "2024-01-15T10:30:00Z", + "message": "申请已提交,请等待组织者审核" + } +} +``` + +### 4.5 获取我的旅行 + +#### 接口信息 +- **接口路径**:`GET /api/v1/travels/mine` +- **接口描述**:获取我创建和参与的旅行活动 +- **是否需要认证**:是 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| type | string | 否 | 类型:created-我创建的,joined-我参与的,applied-我申请的 | +| status | integer | 否 | 状态筛选 | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "title": "云南大理古城深度游", + "destination": "云南大理", + "start_date": "2024-03-01", + "end_date": "2024-03-05", + "status": 1, + "my_role": "creator", + "my_status": 1, + "participants_count": 3, + "max_participants": 8, + "cover_image": "https://example.com/cover.jpg", + "created_at": "2024-01-15T10:00:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 5, + "pages": 1 + } + } +} +``` + +## 5. 动物认领接口 + +### 5.1 获取动物列表 + +#### 接口信息 +- **接口路径**:`GET /api/v1/animals` +- **接口描述**:分页获取可认领动物列表 +- **是否需要认证**:否 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | integer | 否 | 页码,默认1 | +| size | integer | 否 | 每页数量,默认10 | +| sort | string | 否 | 排序,默认created_at:desc | +| keyword | string | 否 | 搜索关键词(动物名称) | +| species | string | 否 | 物种:cat,dog,rabbit,other | +| breed | string | 否 | 品种 | +| gender | integer | 否 | 性别:1-雄性,2-雌性 | +| age_min | integer | 否 | 年龄下限(月) | +| age_max | integer | 否 | 年龄上限(月) | +| location | string | 否 | 所在地 | +| status | integer | 否 | 状态:1-可认领,2-已认领 | +| is_featured | integer | 否 | 是否精选:1-是 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "uuid": "animal_123456", + "name": "小花", + "species": "cat", + "breed": "英国短毛猫", + "gender": 2, + "age": 24, + "weight": 4.5, + "color": "银渐层", + "description": "性格温顺,喜欢晒太阳", + "personality": "温顺、亲人、活泼", + "health_status": "健康", + "location": "北京市朝阳区", + "adoption_fee": 500.00, + "monthly_cost": 200.00, + "status": 1, + "is_featured": 1, + "view_count": 89, + "like_count": 15, + "adoption_count": 0, + "cover_image": "https://example.com/cat_cover.jpg", + "images": [ + "https://example.com/cat1.jpg", + "https://example.com/cat2.jpg" + ], + "farm": { + "id": 1, + "name": "爱心动物农场", + "location": "北京市朝阳区", + "rating": 4.8 + }, + "tags": ["温顺", "亲人", "已绝育"], + "created_at": "2024-01-10T10:00:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 30, + "pages": 3 + } + } +} +``` + +### 5.2 获取动物详情 + +#### 接口信息 +- **接口路径**:`GET /api/v1/animals/{id}` +- **接口描述**:获取动物详细信息 +- **是否需要认证**:否 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "id": 1, + "uuid": "animal_123456", + "name": "小花", + "species": "cat", + "breed": "英国短毛猫", + "gender": 2, + "age": 24, + "weight": 4.5, + "color": "银渐层", + "description": "小花是一只非常温顺的英国短毛猫,喜欢晒太阳和玩毛线球...", + "personality": "温顺、亲人、活泼", + "health_status": "健康", + "vaccination_status": { + "rabies": { + "vaccinated": true, + "date": "2023-12-01", + "next_due": "2024-12-01" + }, + "feline_distemper": { + "vaccinated": true, + "date": "2023-12-01", + "next_due": "2024-12-01" + } + }, + "medical_history": "2023年11月进行了绝育手术,恢复良好", + "location": "北京市朝阳区", + "adoption_fee": 500.00, + "monthly_cost": 200.00, + "status": 1, + "is_featured": 1, + "view_count": 89, + "like_count": 15, + "adoption_count": 0, + "images": [ + "https://example.com/cat1.jpg", + "https://example.com/cat2.jpg" + ], + "videos": [ + "https://example.com/cat_video1.mp4" + ], + "farm": { + "id": 1, + "name": "爱心动物农场", + "location": "北京市朝阳区", + "contact_phone": "13800138000", + "description": "专业的动物救助机构", + "rating": 4.8, + "images": [ + "https://example.com/farm1.jpg" + ] + }, + "caretaker": { + "id": 1, + "name": "张三", + "avatar_url": "https://example.com/caretaker.jpg", + "experience": "5年动物护理经验", + "introduction": "热爱动物,专业护理" + }, + "adoption_types": [ + { + "type": 1, + "name": "长期认领", + "description": "12个月以上的长期认领", + "min_duration": 12, + "monthly_fee": 200.00 + }, + { + "type": 2, + "name": "短期认领", + "description": "1-6个月的短期认领", + "min_duration": 1, + "monthly_fee": 250.00 + } + ], + "tags": ["温顺", "亲人", "已绝育"], + "is_liked": false, + "can_adopt": true, + "created_at": "2024-01-10T10:00:00Z" + } +} +``` + +### 5.3 申请认领动物 + +#### 接口信息 +- **接口路径**:`POST /api/v1/animals/{id}/adopt` +- **接口描述**:申请认领动物 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "adoption_type": 1, + "duration_months": 12, + "start_date": "2024-02-01", + "adoption_reason": "非常喜欢小花,希望能给它一个温暖的家", + "experience": "养过猫咪,有丰富的养宠经验", + "living_condition": "独居公寓,环境安静,适合猫咪生活", + "contact_info": { + "wechat": "cat_lover", + "phone": "13900139000" + }, + "emergency_contact": { + "name": "李四", + "phone": "13800138000", + "relationship": "朋友" + }, + "special_requirements": "希望能定期收到小花的照片和视频" +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "申请成功", + "data": { + "adoption_id": 1, + "status": 0, + "total_fee": 2400.00, + "applied_at": "2024-01-15T10:30:00Z", + "message": "认领申请已提交,请等待农场审核" + } +} +``` + +### 5.4 获取我的认领 + +#### 接口信息 +- **接口路径**:`GET /api/v1/adoptions/mine` +- **接口描述**:获取我的动物认领记录 +- **是否需要认证**:是 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| status | integer | 否 | 状态:0-申请中,1-已通过,2-已拒绝,3-进行中,4-已完成 | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "uuid": "adoption_123456", + "animal": { + "id": 1, + "name": "小花", + "species": "cat", + "breed": "英国短毛猫", + "cover_image": "https://example.com/cat_cover.jpg" + }, + "adoption_type": 1, + "duration_months": 12, + "start_date": "2024-02-01", + "end_date": "2025-02-01", + "monthly_fee": 200.00, + "total_fee": 2400.00, + "status": 3, + "progress": { + "days_passed": 15, + "total_days": 365, + "percentage": 4.1 + }, + "next_update_date": "2024-02-15", + "applied_at": "2024-01-15T10:30:00Z", + "approved_at": "2024-01-16T09:00:00Z", + "started_at": "2024-02-01T00:00:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 2, + "pages": 1 + } + } +} +``` + +### 5.5 获取认领动态 + +#### 接口信息 +- **接口路径**:`GET /api/v1/adoptions/{id}/updates` +- **接口描述**:获取认领动物的成长动态 +- **是否需要认证**:是 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "type": "daily_care", + "title": "小花今日状况", + "content": "小花今天很活泼,吃了很多猫粮,还玩了新玩具", + "images": [ + "https://example.com/update1.jpg", + "https://example.com/update2.jpg" + ], + "videos": [ + "https://example.com/update_video.mp4" + ], + "caretaker": { + "name": "张三", + "avatar_url": "https://example.com/caretaker.jpg" + }, + "created_at": "2024-02-15T14:30:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 20, + "pages": 2 + } + } +} +``` + +## 6. 支付系统接口 + +### 6.1 创建订单 + +#### 接口信息 +- **接口路径**:`POST /api/v1/orders` +- **接口描述**:创建支付订单 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "order_type": "adoption", + "related_id": 1, + "items": [ + { + "name": "认领小花(英国短毛猫)", + "description": "12个月长期认领", + "quantity": 1, + "unit_price": 2400.00 + } + ], + "amount": 2400.00, + "currency": "CNY", + "payment_method": "wechat", + "metadata": { + "adoption_id": 1, + "source": "miniprogram" + } +} +``` + +#### 响应示例 +```json +{ + "code": 201, + "message": "订单创建成功", + "data": { + "order_id": 1, + "order_no": "ORD202401150001", + "amount": 2400.00, + "currency": "CNY", + "status": 0, + "expires_at": "2024-01-15T11:00:00Z", + "created_at": "2024-01-15T10:30:00Z" + } +} +``` + +### 6.2 发起支付 + +#### 接口信息 +- **接口路径**:`POST /api/v1/orders/{id}/pay` +- **接口描述**:发起微信支付 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "payment_method": "wechat", + "openid": "wx_openid_123456" +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "支付参数获取成功", + "data": { + "payment_id": 1, + "payment_no": "PAY202401150001", + "payment_params": { + "appId": "wx_app_id", + "timeStamp": "1705312200", + "nonceStr": "random_string", + "package": "prepay_id=wx_prepay_123456", + "signType": "RSA", + "paySign": "signature_string" + }, + "expires_at": "2024-01-15T11:00:00Z" + } +} +``` + +### 6.3 查询订单状态 + +#### 接口信息 +- **接口路径**:`GET /api/v1/orders/{id}` +- **接口描述**:查询订单详情和支付状态 +- **是否需要认证**:是 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "id": 1, + "order_no": "ORD202401150001", + "order_type": "adoption", + "related_id": 1, + "related_info": { + "animal": { + "id": 1, + "name": "小花", + "species": "cat", + "cover_image": "https://example.com/cat_cover.jpg" + } + }, + "title": "认领小花(英国短毛猫)", + "description": "12个月长期认领", + "amount": 2400.00, + "currency": "CNY", + "status": 2, + "payment_method": "wechat", + "payment_status": 2, + "paid_at": "2024-01-15T10:35:00Z", + "created_at": "2024-01-15T10:30:00Z" + } +} +``` + +### 6.4 获取我的订单 + +#### 接口信息 +- **接口路径**:`GET /api/v1/orders/mine` +- **接口描述**:获取我的订单列表 +- **是否需要认证**:是 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| status | integer | 否 | 订单状态 | +| order_type | string | 否 | 订单类型 | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "order_no": "ORD202401150001", + "order_type": "adoption", + "title": "认领小花(英国短毛猫)", + "amount": 2400.00, + "status": 2, + "payment_status": 2, + "cover_image": "https://example.com/cat_cover.jpg", + "paid_at": "2024-01-15T10:35:00Z", + "created_at": "2024-01-15T10:30:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 5, + "pages": 1 + } + } +} +``` + +## 7. 消息通知接口 + +### 7.1 获取消息列表 + +#### 接口信息 +- **接口路径**:`GET /api/v1/messages` +- **接口描述**:获取用户消息列表 +- **是否需要认证**:是 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| type | string | 否 | 消息类型:system,travel,adoption,order | +| status | integer | 否 | 状态:0-未读,1-已读 | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "type": "adoption", + "title": "认领申请通过", + "content": "您的动物认领申请已通过审核,请及时完成支付", + "avatar": "https://example.com/system_avatar.jpg", + "related_type": "adoption", + "related_id": 1, + "status": 0, + "created_at": "2024-01-16T09:00:00Z" + }, + { + "id": 2, + "type": "travel", + "title": "旅行申请通过", + "content": "您申请参加的"云南大理古城深度游"已通过审核", + "avatar": "https://example.com/travel_avatar.jpg", + "related_type": "travel", + "related_id": 1, + "status": 1, + "created_at": "2024-01-15T16:00:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 15, + "pages": 2 + }, + "unread_count": 3 + } +} +``` + +### 7.2 标记消息已读 + +#### 接口信息 +- **接口路径**:`PUT /api/v1/messages/{id}/read` +- **接口描述**:标记消息为已读 +- **是否需要认证**:是 + +#### 响应示例 +```json +{ + "code": 200, + "message": "标记成功" +} +``` + +### 7.3 批量标记已读 + +#### 接口信息 +- **接口路径**:`PUT /api/v1/messages/batch-read` +- **接口描述**:批量标记消息为已读 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "message_ids": [1, 2, 3, 4, 5] +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "批量标记成功", + "data": { + "updated_count": 5 + } +} +``` + +## 8. 文件上传接口 + +### 8.1 上传图片 + +#### 接口信息 +- **接口路径**:`POST /api/v1/upload/image` +- **接口描述**:上传图片文件 +- **是否需要认证**:是 + +#### 请求参数 +- **Content-Type**:`multipart/form-data` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| file | file | 是 | 图片文件 | +| category | string | 否 | 分类:avatar,travel,animal,other | + +#### 响应示例 +```json +{ + "code": 200, + "message": "上传成功", + "data": { + "file_id": 1, + "file_url": "https://cdn.example.com/uploads/images/2024/01/15/image_123456.jpg", + "file_size": 1024000, + "width": 1920, + "height": 1080, + "uploaded_at": "2024-01-15T10:30:00Z" + } +} +``` + +### 8.2 批量上传图片 + +#### 接口信息 +- **接口路径**:`POST /api/v1/upload/images` +- **接口描述**:批量上传多张图片 +- **是否需要认证**:是 + +#### 请求参数 +- **Content-Type**:`multipart/form-data` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| files[] | file[] | 是 | 图片文件数组 | +| category | string | 否 | 分类 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "批量上传完成", + "data": { + "success_count": 3, + "failed_count": 0, + "files": [ + { + "file_id": 1, + "file_url": "https://cdn.example.com/uploads/images/image1.jpg" + }, + { + "file_id": 2, + "file_url": "https://cdn.example.com/uploads/images/image2.jpg" + } + ] + } +} +``` + +## 9. 搜索接口 + +### 9.1 综合搜索 + +#### 接口信息 +- **接口路径**:`GET /api/v1/search` +- **接口描述**:综合搜索旅行和动物 +- **是否需要认证**:否 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| keyword | string | 是 | 搜索关键词 | +| type | string | 否 | 搜索类型:all,travel,animal,默认all | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "搜索成功", + "data": { + "travels": { + "items": [ + { + "id": 1, + "title": "云南大理古城深度游", + "destination": "云南大理", + "cover_image": "https://example.com/travel_cover.jpg", + "creator": { + "nickname": "旅行达人" + } + } + ], + "total": 5 + }, + "animals": { + "items": [ + { + "id": 1, + "name": "小花", + "species": "cat", + "breed": "英国短毛猫", + "cover_image": "https://example.com/cat_cover.jpg", + "farm": { + "name": "爱心动物农场" + } + } + ], + "total": 3 + }, + "total_count": 8 + } +} +``` + +### 9.2 搜索建议 + +#### 接口信息 +- **接口路径**:`GET /api/v1/search/suggestions` +- **接口描述**:获取搜索建议 +- **是否需要认证**:否 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| keyword | string | 是 | 搜索关键词 | +| type | string | 否 | 类型:travel,animal | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "suggestions": [ + "大理古城", + "大理洱海", + "大理三塔", + "大理旅游" + ] + } +} +``` + +## 10. 收藏点赞接口 + +### 10.1 点赞/取消点赞 + +#### 接口信息 +- **接口路径**:`POST /api/v1/likes` +- **接口描述**:点赞或取消点赞 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "target_type": "travel", + "target_id": 1, + "action": "like" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| target_type | string | 是 | 目标类型:travel,animal | +| target_id | integer | 是 | 目标ID | +| action | string | 是 | 操作:like-点赞,unlike-取消点赞 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "点赞成功", + "data": { + "is_liked": true, + "like_count": 24 + } +} +``` + +### 10.2 收藏/取消收藏 + +#### 接口信息 +- **接口路径**:`POST /api/v1/favorites` +- **接口描述**:收藏或取消收藏 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "target_type": "animal", + "target_id": 1, + "action": "favorite" +} +``` + +#### 响应示例 +```json +{ + "code": 200, + "message": "收藏成功", + "data": { + "is_favorited": true, + "favorite_count": 16 + } +} +``` + +### 10.3 获取我的收藏 + +#### 接口信息 +- **接口路径**:`GET /api/v1/favorites/mine` +- **接口描述**:获取我的收藏列表 +- **是否需要认证**:是 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| type | string | 否 | 类型:travel,animal | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "target_type": "travel", + "target_id": 1, + "target_info": { + "id": 1, + "title": "云南大理古城深度游", + "destination": "云南大理", + "cover_image": "https://example.com/travel_cover.jpg" + }, + "created_at": "2024-01-15T10:30:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 8, + "pages": 1 + } + } +} +``` + +## 11. 评论接口 + +### 11.1 获取评论列表 + +#### 接口信息 +- **接口路径**:`GET /api/v1/comments` +- **接口描述**:获取评论列表 +- **是否需要认证**:否 + +#### 查询参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| target_type | string | 是 | 目标类型:travel,animal | +| target_id | integer | 是 | 目标ID | +| page | integer | 否 | 页码 | +| size | integer | 否 | 每页数量 | + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "content": "这个旅行计划看起来很不错,很想参加!", + "user": { + "id": 2, + "nickname": "旅行爱好者", + "avatar_url": "https://example.com/avatar.jpg" + }, + "like_count": 3, + "is_liked": false, + "replies": [ + { + "id": 2, + "content": "欢迎加入我们!", + "user": { + "id": 1, + "nickname": "旅行达人", + "avatar_url": "https://example.com/avatar2.jpg" + }, + "created_at": "2024-01-15T11:00:00Z" + } + ], + "created_at": "2024-01-15T10:30:00Z" + } + ], + "pagination": { + "page": 1, + "size": 10, + "total": 8, + "pages": 1 + } + } +} +``` + +### 11.2 发表评论 + +#### 接口信息 +- **接口路径**:`POST /api/v1/comments` +- **接口描述**:发表评论 +- **是否需要认证**:是 + +#### 请求参数 +```json +{ + "target_type": "travel", + "target_id": 1, + "content": "这个旅行计划很棒,期待参加!", + "parent_id": null +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| target_type | string | 是 | 目标类型:travel,animal | +| target_id | integer | 是 | 目标ID | +| content | string | 是 | 评论内容 | +| parent_id | integer | 否 | 父评论ID(回复时填写) | + +#### 响应示例 +```json +{ + "code": 201, + "message": "评论成功", + "data": { + "id": 10, + "content": "这个旅行计划很棒,期待参加!", + "user": { + "id": 3, + "nickname": "我的昵称", + "avatar_url": "https://example.com/my_avatar.jpg" + }, + "like_count": 0, + "created_at": "2024-01-15T10:30:00Z" + } +} +``` + +## 12. 系统配置接口 + +### 12.1 获取小程序配置 + +#### 接口信息 +- **接口路径**:`GET /api/v1/config/miniprogram` +- **接口描述**:获取小程序相关配置 +- **是否需要认证**:否 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "app_info": { + "name": "解班客", + "version": "1.0.0", + "description": "专业的旅行结伴和动物认领平台" + }, + "features": { + "travel_enabled": true, + "adoption_enabled": true, + "payment_enabled": true, + "share_enabled": true + }, + "upload": { + "max_image_size": 5242880, + "max_images_count": 9, + "allowed_image_types": ["jpg", "jpeg", "png"] + }, + "payment": { + "min_amount": 0.01, + "max_amount": 50000.00, + "supported_methods": ["wechat"] + }, + "contact": { + "customer_service": "kefu001", + "phone": "400-123-4567", + "email": "support@jiebanke.com" + } + } +} +``` + +### 12.2 获取首页配置 + +#### 接口信息 +- **接口路径**:`GET /api/v1/config/home` +- **接口描述**:获取首页配置信息 +- **是否需要认证**:否 + +#### 响应示例 +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "banners": [ + { + "id": 1, + "title": "春节特惠旅行", + "image_url": "https://example.com/banner1.jpg", + "link_type": "travel", + "link_value": "1", + "sort_order": 1 + } + ], + "featured_travels": [ + { + "id": 1, + "title": "云南大理古城深度游", + "cover_image": "https://example.com/travel_cover.jpg", + "destination": "云南大理" + } + ], + "featured_animals": [ + { + "id": 1, + "name": "小花", + "species": "cat", + "cover_image": "https://example.com/cat_cover.jpg" + } + ], + "announcements": [ + { + "id": 1, + "title": "平台升级公告", + "content": "为了提供更好的服务...", + "created_at": "2024-01-15T10:00:00Z" + } + ] + } +} +``` + +## 13. 错误码说明 + +### 13.1 通用错误码 + +| 错误码 | HTTP状态码 | 说明 | +|--------|------------|------| +| 200 | 200 | 请求成功 | +| 400 | 400 | 请求参数错误 | +| 401 | 401 | 未授权,需要登录 | +| 403 | 403 | 权限不足 | +| 404 | 404 | 资源不存在 | +| 422 | 422 | 业务逻辑错误 | +| 429 | 429 | 请求频率限制 | +| 500 | 500 | 服务器内部错误 | + +### 13.2 业务错误码 + +| 错误码 | 说明 | +|--------|------| +| 10001 | 微信授权失败 | +| 10002 | 用户信息不完整 | +| 10003 | 手机号格式错误 | +| 10004 | 用户不存在 | +| 20001 | 旅行活动不存在 | +| 20002 | 旅行活动已满员 | +| 20003 | 旅行活动已结束 | +| 20004 | 重复申请旅行活动 | +| 20005 | 不能申请自己创建的活动 | +| 30001 | 动物不存在 | +| 30002 | 动物已被认领 | +| 30003 | 重复申请认领 | +| 30004 | 认领申请不存在 | +| 40001 | 订单不存在 | +| 40002 | 订单已支付 | +| 40003 | 订单已过期 | +| 40004 | 支付失败 | +| 40005 | 退款失败 | +| 50001 | 文件上传失败 | +| 50002 | 文件格式不支持 | +| 50003 | 文件大小超限 | +| 60001 | 评论不存在 | +| 60002 | 不能删除他人评论 | + +## 14. 接口测试 + +### 14.1 测试环境 + +- **测试域名**:`https://api-test.jiebanke.com` +- **文档地址**:`https://api-test.jiebanke.com/docs` +- **测试账号**: + - 测试用户1:openid_test_001 + - 测试用户2:openid_test_002 + +### 14.2 测试工具 + +推荐使用以下工具进行接口测试: +- **Postman**:图形化接口测试工具 +- **curl**:命令行测试工具 +- **微信开发者工具**:小程序调试工具 + +### 14.3 测试示例 + +#### 14.3.1 登录测试 +```bash +curl -X POST https://api-test.jiebanke.com/api/v1/auth/wechat/login \ + -H "Content-Type: application/json" \ + -d '{ + "code": "test_wx_code_123456" + }' +``` + +#### 14.3.2 获取旅行列表测试 +```bash +curl -X GET "https://api-test.jiebanke.com/api/v1/travels?page=1&size=10" \ + -H "Accept: application/json" +``` + +#### 14.3.3 创建旅行活动测试 +```bash +curl -X POST https://api-test.jiebanke.com/api/v1/travels \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{ + "title": "测试旅行活动", + "description": "这是一个测试活动", + "destination": "北京", + "start_date": "2024-03-01", + "end_date": "2024-03-03", + "max_participants": 5, + "budget_min": 1000.00, + "budget_max": 2000.00, + "travel_type": "leisure" + }' +``` + +### 14.4 Mock数据 + +为了方便前端开发,提供以下Mock数据接口: + +- **Mock服务地址**:`https://mock.jiebanke.com` +- **数据更新频率**:每日更新 +- **支持接口**:所有小程序端接口 + +## 15. 版本更新记录 + +### v1.0.0 (2024-01-15) +- 初始版本发布 +- 完成用户认证、旅行结伴、动物认领、支付系统等核心功能接口 +- 支持微信小程序登录和支付 +- 实现文件上传、消息通知等辅助功能 + +### 后续版本规划 + +#### v1.1.0 (计划2024-02-15) +- 增加社交功能接口(关注、私信) +- 优化搜索算法和推荐系统 +- 增加用户等级和积分系统 + +#### v1.2.0 (计划2024-03-15) +- 增加直播功能接口 +- 支持多语言国际化 +- 增加AI智能推荐接口 + +## 16. 总结 + +本文档详细描述了解班客小程序端的所有API接口设计,涵盖了: + +### 16.1 核心功能 +- **用户系统**:微信登录、用户信息管理 +- **旅行结伴**:活动发布、申请、管理 +- **动物认领**:动物展示、认领申请、动态更新 +- **支付系统**:订单创建、微信支付集成 +- **消息通知**:系统消息、业务通知 + +### 16.2 辅助功能 +- **文件上传**:图片上传、批量处理 +- **搜索功能**:综合搜索、智能建议 +- **社交功能**:点赞、收藏、评论 +- **系统配置**:动态配置、首页内容 + +### 16.3 技术特点 +- **RESTful设计**:遵循REST架构原则 +- **统一响应格式**:标准化的API响应 +- **微信生态集成**:深度集成微信小程序能力 +- **安全认证**:JWT Token + 微信授权 +- **性能优化**:分页、缓存、CDN加速 + +### 16.4 开发支持 +- **完整的错误码体系**:便于问题定位和处理 +- **详细的接口文档**:包含请求参数、响应示例 +- **测试环境支持**:提供测试环境和Mock数据 +- **版本管理**:清晰的版本规划和更新记录 + +本接口设计文档将作为小程序前端开发的重要参考,确保前后端协作的高效进行。 \ No newline at end of file diff --git a/docs/小程序app需求文档.md b/docs/小程序app需求文档.md new file mode 100644 index 0000000..c2c6969 --- /dev/null +++ b/docs/小程序app需求文档.md @@ -0,0 +1,659 @@ +# 解班客小程序需求文档 + +## 1. 项目概述 + +### 1.1 产品定位 +解班客微信小程序是面向C端用户的核心产品,专注于提供结伴旅行和动物认领服务。通过微信生态的便利性,为用户提供便捷的社交旅行体验和创新的动物认领服务。 + +### 1.2 目标用户 +- **主要用户群体**:18-35岁的年轻用户 +- **用户特征**:热爱旅行、喜欢社交、追求新鲜体验 +- **使用场景**:碎片化时间浏览、计划旅行、寻找旅伴、管理认领动物 + +### 1.3 核心价值 +- **便捷的结伴旅行**:快速找到志同道合的旅伴 +- **创新的动物认领**:体验农场生活,建立情感连接 +- **安全的社交环境**:实名认证,安全可靠的交友平台 +- **丰富的服务生态**:整合旅行、住宿、美食、购物等服务 + +## 2. 功能需求 + +### 2.1 用户系统 +#### 2.1.1 登录注册 +- **微信授权登录**: + - 一键微信登录,获取基本信息 + - 支持静默登录和授权登录 + - 登录状态保持和自动续期 + - 登录异常处理和重试机制 + +- **手机号绑定**: + - 微信手机号快速验证 + - 短信验证码验证 + - 手机号更换功能 + - 多设备登录管理 + +- **用户协议**: + - 用户服务协议展示 + - 隐私政策说明 + - 必须同意才能使用 + - 协议更新通知 + +#### 2.1.2 个人资料 +- **基本信息设置**: + - 头像上传和裁剪 + - 昵称设置(敏感词过滤) + - 性别选择 + - 生日设置 + - 所在城市选择 + - 个人简介编辑 + +- **兴趣标签**: + - 预设兴趣标签选择 + - 自定义标签添加 + - 标签权重设置 + - 标签推荐算法 + +- **认证信息**: + - 实名认证(可选) + - 身份证认证 + - 芝麻信用认证 + - 认证状态展示 + +#### 2.1.3 隐私设置 +- **个人信息可见性**: + - 手机号可见范围 + - 位置信息共享设置 + - 个人资料可见性 + - 活动历史可见性 + +- **消息通知设置**: + - 系统通知开关 + - 活动通知设置 + - 私信通知设置 + - 推送时间段设置 + +### 2.2 首页模块 +#### 2.2.1 首页布局 +- **顶部导航**: + - 位置信息显示 + - 搜索入口 + - 消息通知图标 + - 个人头像入口 + +- **轮播图区域**: + - 平台活动推广 + - 热门活动推荐 + - 新功能介绍 + - 商家广告展示 + +- **快捷功能区**: + - 发布活动入口 + - 我的认领入口 + - 附近活动入口 + - 更多功能入口 + +#### 2.2.2 内容推荐 +- **推荐算法**: + - 基于地理位置推荐 + - 基于兴趣标签推荐 + - 基于历史行为推荐 + - 基于社交关系推荐 + +- **内容类型**: + - 热门活动推荐 + - 附近活动展示 + - 新用户活动推荐 + - 个性化内容推荐 + +#### 2.2.3 搜索功能 +- **搜索类型**: + - 活动搜索 + - 用户搜索 + - 地点搜索 + - 综合搜索 + +- **搜索功能**: + - 关键词搜索 + - 语音搜索 + - 图片搜索 + - 搜索历史记录 + +### 2.3 结伴旅行模块 +#### 2.3.1 活动发布 +- **活动信息填写**: + - 活动标题(必填) + - 活动类型选择(旅行/聚餐/看电影/户外运动等) + - 活动描述(支持富文本) + - 活动时间选择(开始时间、结束时间) + - 活动地点选择(地图选点、地址搜索) + - 参与人数限制 + - 费用说明(AA制/免费/付费) + - 联系方式设置 + +- **活动图片**: + - 多图片上传(最多9张) + - 图片编辑和滤镜 + - 图片排序和删除 + - 封面图片设置 + +- **参与要求**: + - 性别要求 + - 年龄范围 + - 兴趣标签要求 + - 其他自定义要求 + +- **发布设置**: + - 立即发布/定时发布 + - 活动可见性设置 + - 报名审核开关 + - 活动分享设置 + +#### 2.3.2 活动浏览 +- **活动列表**: + - 瀑布流/列表切换 + - 活动卡片信息展示 + - 无限滚动加载 + - 下拉刷新 + +- **筛选功能**: + - 活动类型筛选 + - 时间范围筛选 + - 距离范围筛选 + - 价格范围筛选 + - 人数范围筛选 + +- **排序功能**: + - 按时间排序 + - 按距离排序 + - 按热度排序 + - 按价格排序 + +#### 2.3.3 活动详情 +- **活动信息展示**: + - 活动基本信息 + - 发起人信息 + - 参与者列表 + - 活动位置地图 + - 相关推荐活动 + +- **互动功能**: + - 点赞/收藏 + - 评论/回复 + - 分享功能 + - 举报功能 + +- **报名功能**: + - 报名按钮 + - 报名表单填写 + - 报名状态显示 + - 取消报名 + +#### 2.3.4 活动管理 +- **我发布的活动**: + - 活动列表展示 + - 活动状态管理 + - 参与者管理 + - 活动编辑/删除 + +- **我参与的活动**: + - 报名状态查看 + - 活动提醒设置 + - 活动评价 + - 活动分享 + +- **活动通知**: + - 报名通知 + - 活动提醒 + - 状态变更通知 + - 消息推送 + +### 2.4 动物认领模块 +#### 2.4.1 动物展示 +- **动物列表**: + - 动物卡片展示 + - 动物基本信息 + - 认领状态显示 + - 筛选和搜索 + +- **动物详情**: + - 动物详细信息 + - 成长记录展示 + - 农场信息 + - 认领价格和周期 + +- **动物分类**: + - 按动物类型分类 + - 按年龄分类 + - 按价格分类 + - 按农场分类 + +#### 2.4.2 认领流程 +- **认领申请**: + - 认领申请表单 + - 认领原因填写 + - 探访计划制定 + - 认领协议确认 + +- **支付流程**: + - 认领费用支付 + - 支付方式选择 + - 支付状态跟踪 + - 发票申请 + +- **认领确认**: + - 认领审核状态 + - 认领证书生成 + - 动物信息绑定 + - 认领成功通知 + +#### 2.4.3 认领管理 +- **我的动物**: + - 认领动物列表 + - 动物状态查看 + - 成长记录查看 + - 探访预约 + +- **互动功能**: + - 给动物起名 + - 上传互动照片 + - 记录成长日记 + - 分享动物动态 + +- **农场服务**: + - 农场探访预约 + - 探访路线导航 + - 农场活动参与 + - 农产品购买 + +### 2.5 社交功能模块 +#### 2.5.1 消息系统 +- **私信功能**: + - 一对一聊天 + - 文字/图片/语音消息 + - 表情包发送 + - 消息撤回/删除 + +- **群聊功能**: + - 活动群聊 + - 群成员管理 + - 群公告发布 + - 群文件共享 + +- **系统消息**: + - 系统通知 + - 活动通知 + - 认领通知 + - 安全提醒 + +#### 2.5.2 动态功能 +- **动态发布**: + - 图文动态发布 + - 视频动态发布 + - 位置标记 + - 话题标签 + +- **动态浏览**: + - 关注动态流 + - 推荐动态 + - 附近动态 + - 话题动态 + +- **互动功能**: + - 点赞/评论/转发 + - @好友功能 + - 动态收藏 + - 动态举报 + +#### 2.5.3 关注系统 +- **关注功能**: + - 关注/取消关注 + - 关注列表管理 + - 粉丝列表查看 + - 互相关注标识 + +- **推荐关注**: + - 基于兴趣推荐 + - 基于地理位置推荐 + - 基于共同好友推荐 + - 基于活动参与推荐 + +### 2.6 商家服务模块 +#### 2.6.1 商家展示 +- **商家列表**: + - 商家卡片展示 + - 商家基本信息 + - 评分和评价 + - 距离和导航 + +- **商家详情**: + - 商家详细信息 + - 服务项目展示 + - 用户评价 + - 联系方式 + +- **商家分类**: + - 按服务类型分类 + - 按地理位置分类 + - 按评分排序 + - 按距离排序 + +#### 2.6.2 商品服务 +- **商品浏览**: + - 商品列表展示 + - 商品详情查看 + - 商品图片展示 + - 价格和库存信息 + +- **购买流程**: + - 商品选择和规格 + - 购物车管理 + - 订单确认 + - 支付和配送 + +- **订单管理**: + - 订单状态跟踪 + - 物流信息查看 + - 订单评价 + - 售后服务 + +### 2.7 个人中心模块 +#### 2.7.1 个人信息 +- **基本信息展示**: + - 头像和昵称 + - 认证状态 + - 个人统计数据 + - 快捷功能入口 + +- **个人资料编辑**: + - 基本信息修改 + - 兴趣标签设置 + - 隐私设置 + - 账户安全设置 + +#### 2.7.2 我的活动 +- **活动记录**: + - 发布的活动 + - 参与的活动 + - 收藏的活动 + - 活动历史 + +- **活动统计**: + - 活动参与次数 + - 活动发布次数 + - 活动评分 + - 活动成就 + +#### 2.7.3 我的认领 +- **认领记录**: + - 当前认领动物 + - 历史认领记录 + - 认领证书 + - 探访记录 + +- **认领统计**: + - 认领总数 + - 认领时长 + - 探访次数 + - 认领成就 + +#### 2.7.4 钱包功能 +- **余额管理**: + - 余额查看 + - 充值功能 + - 提现功能 + - 交易记录 + +- **支付管理**: + - 支付方式设置 + - 支付密码设置 + - 自动支付设置 + - 支付安全 + +#### 2.7.5 设置中心 +- **账户设置**: + - 个人信息设置 + - 隐私设置 + - 安全设置 + - 通知设置 + +- **应用设置**: + - 语言设置 + - 字体大小设置 + - 夜间模式 + - 缓存清理 + +- **帮助支持**: + - 使用帮助 + - 常见问题 + - 意见反馈 + - 联系客服 + +## 3. 用户体验需求 + +### 3.1 界面设计 +#### 3.1.1 设计风格 +- **现代简约**:简洁清爽的界面设计 +- **温馨友好**:体现社交和温暖的品牌调性 +- **年轻活力**:符合年轻用户的审美偏好 +- **一致性**:保持整体设计风格的统一 + +#### 3.1.2 色彩方案 +- **主色调**:温暖的橙色系,体现活力和友好 +- **辅助色**:清新的绿色系,体现自然和生机 +- **中性色**:灰色系作为背景和文字色 +- **强调色**:红色用于重要提示和警告 + +#### 3.1.3 字体规范 +- **标题字体**:醒目清晰的标题字体 +- **正文字体**:易读的正文字体 +- **字号层级**:合理的字号层级体系 +- **行间距**:舒适的行间距设置 + +### 3.2 交互设计 +#### 3.2.1 导航设计 +- **底部导航**:主要功能模块的快速切换 +- **顶部导航**:页面标题和功能按钮 +- **面包屑导航**:页面层级关系展示 +- **手势导航**:支持滑动返回等手势操作 + +#### 3.2.2 操作反馈 +- **点击反馈**:按钮点击的视觉和触觉反馈 +- **加载状态**:数据加载的进度提示 +- **操作结果**:操作成功或失败的明确提示 +- **错误处理**:友好的错误提示和解决建议 + +#### 3.2.3 动画效果 +- **页面转场**:流畅的页面切换动画 +- **元素动画**:适度的元素动画效果 +- **加载动画**:有趣的加载动画设计 +- **微交互**:细节的微交互动画 + +### 3.3 性能优化 +#### 3.3.1 加载性能 +- **首屏加载**:首屏加载时间 < 2秒 +- **图片优化**:图片懒加载和压缩 +- **代码分包**:合理的代码分包策略 +- **缓存策略**:有效的缓存机制 + +#### 3.3.2 运行性能 +- **流畅度**:保持60fps的流畅体验 +- **内存管理**:合理的内存使用和释放 +- **电量优化**:减少不必要的电量消耗 +- **网络优化**:减少网络请求和数据传输 + +### 3.4 适配要求 +#### 3.4.1 设备适配 +- **iPhone适配**:iPhone 6及以上设备 +- **Android适配**:主流Android设备 +- **屏幕适配**:不同屏幕尺寸的适配 +- **系统适配**:iOS 10+、Android 6.0+ + +#### 3.4.2 微信版本适配 +- **微信版本**:微信7.0及以上版本 +- **基础库版本**:2.10.0及以上版本 +- **API兼容**:新旧API的兼容处理 +- **功能降级**:低版本的功能降级方案 + +## 4. 技术需求 + +### 4.1 开发技术 +#### 4.1.1 开发框架 +- **原生小程序**:使用微信原生小程序开发 +- **UI组件库**:Vant Weapp组件库 +- **状态管理**:原生状态管理或MobX +- **网络请求**:wx.request封装 + +#### 4.1.2 开发工具 +- **微信开发者工具**:官方开发调试工具 +- **代码编辑器**:VS Code等现代编辑器 +- **版本控制**:Git版本控制 +- **构建工具**:微信开发者工具内置构建 + +### 4.2 API集成 +#### 4.2.1 微信API +- **登录API**:wx.login、wx.getUserProfile +- **支付API**:wx.requestPayment +- **分享API**:wx.shareAppMessage、wx.shareTimeline +- **地图API**:wx.getLocation、wx.openLocation + +#### 4.2.2 第三方API +- **地图服务**:腾讯地图API +- **支付服务**:微信支付API +- **短信服务**:阿里云短信API +- **云存储**:腾讯云COS或阿里云OSS + +### 4.3 数据管理 +#### 4.3.1 本地存储 +- **用户数据**:用户信息和设置的本地缓存 +- **业务数据**:活动和动物信息的本地缓存 +- **图片缓存**:图片资源的本地缓存 +- **离线数据**:支持部分功能的离线使用 + +#### 4.3.2 数据同步 +- **实时同步**:关键数据的实时同步 +- **增量同步**:大量数据的增量同步 +- **冲突处理**:数据冲突的处理机制 +- **数据校验**:数据完整性和一致性校验 + +## 5. 安全需求 + +### 5.1 用户安全 +#### 5.1.1 身份验证 +- **微信授权**:安全的微信登录授权 +- **手机验证**:手机号验证码验证 +- **实名认证**:可选的实名认证功能 +- **设备绑定**:设备信息绑定和验证 + +#### 5.1.2 数据保护 +- **数据加密**:敏感数据的加密传输 +- **隐私保护**:用户隐私信息的保护 +- **权限控制**:细粒度的权限控制 +- **数据脱敏**:敏感信息的脱敏处理 + +### 5.2 交易安全 +#### 5.2.1 支付安全 +- **支付验证**:支付密码或指纹验证 +- **交易加密**:支付信息的加密传输 +- **风险控制**:异常交易的风险控制 +- **资金保护**:用户资金的安全保护 + +#### 5.2.2 信息安全 +- **内容审核**:用户发布内容的审核 +- **举报机制**:不当内容的举报处理 +- **黑名单**:恶意用户的黑名单机制 +- **安全提醒**:安全风险的及时提醒 + +### 5.3 系统安全 +#### 5.3.1 接口安全 +- **接口鉴权**:API接口的安全鉴权 +- **参数验证**:接口参数的严格验证 +- **频率限制**:接口调用频率的限制 +- **异常监控**:异常请求的监控和处理 + +#### 5.3.2 代码安全 +- **代码混淆**:小程序代码的混淆保护 +- **版本控制**:代码版本的安全管理 +- **漏洞扫描**:定期的安全漏洞扫描 +- **安全更新**:及时的安全补丁更新 + +## 6. 运营需求 + +### 6.1 数据统计 +#### 6.1.1 用户行为统计 +- **页面访问统计**:各页面的访问量和停留时间 +- **功能使用统计**:各功能模块的使用情况 +- **用户路径分析**:用户操作路径的分析 +- **转化率统计**:关键转化节点的转化率 + +#### 6.1.2 业务数据统计 +- **活动数据**:活动发布、参与、完成情况 +- **认领数据**:动物认领、探访、续费情况 +- **交易数据**:支付、退款、收入情况 +- **用户增长**:新用户注册、活跃用户统计 + +### 6.2 运营工具 +#### 6.2.1 内容管理 +- **活动推荐**:热门活动的推荐机制 +- **内容审核**:用户发布内容的审核工具 +- **标签管理**:兴趣标签的管理和维护 +- **推送管理**:消息推送的管理工具 + +#### 6.2.2 用户运营 +- **用户分群**:用户群体的细分和标记 +- **活动营销**:营销活动的策划和执行 +- **积分系统**:用户积分的获取和消费 +- **等级系统**:用户等级的升级和权益 + +### 6.3 客服支持 +#### 6.3.1 在线客服 +- **客服入口**:便捷的客服联系入口 +- **问题分类**:常见问题的分类和解答 +- **工单系统**:复杂问题的工单处理 +- **满意度评价**:客服服务的满意度评价 + +#### 6.3.2 帮助中心 +- **使用指南**:详细的功能使用指南 +- **常见问题**:FAQ的整理和更新 +- **视频教程**:功能操作的视频教程 +- **意见反馈**:用户意见和建议的收集 + +## 7. 测试需求 + +### 7.1 功能测试 +#### 7.1.1 基础功能测试 +- **登录注册**:各种登录注册场景的测试 +- **核心功能**:活动发布、认领等核心功能测试 +- **支付功能**:各种支付场景的测试 +- **消息功能**:消息发送接收的测试 + +#### 7.1.2 兼容性测试 +- **设备兼容**:不同设备的兼容性测试 +- **系统兼容**:不同操作系统的兼容性测试 +- **微信版本**:不同微信版本的兼容性测试 +- **网络环境**:不同网络环境的测试 + +### 7.2 性能测试 +#### 7.2.1 性能指标测试 +- **启动时间**:小程序启动时间测试 +- **页面加载**:各页面加载时间测试 +- **内存使用**:内存使用情况测试 +- **电量消耗**:电量消耗情况测试 + +#### 7.2.2 压力测试 +- **并发用户**:高并发用户的压力测试 +- **大数据量**:大数据量处理的测试 +- **网络异常**:网络异常情况的测试 +- **长时间使用**:长时间使用的稳定性测试 + +### 7.3 安全测试 +#### 7.3.1 数据安全测试 +- **数据传输**:数据传输安全性测试 +- **数据存储**:本地数据存储安全性测试 +- **权限验证**:用户权限验证测试 +- **隐私保护**:用户隐私保护测试 + +#### 7.3.2 业务安全测试 +- **支付安全**:支付流程安全性测试 +- **内容安全**:用户内容安全性测试 +- **账户安全**:用户账户安全性测试 +- **接口安全**:API接口安全性测试 \ No newline at end of file diff --git a/docs/小程序架构文档.md b/docs/小程序架构文档.md new file mode 100644 index 0000000..640796f --- /dev/null +++ b/docs/小程序架构文档.md @@ -0,0 +1,1655 @@ +# 解班客小程序架构文档 + +## 1. 项目概述 + +### 1.1 项目简介 +解班客小程序是一个基于微信生态的社交旅行平台,融合了结伴旅行、动物认领、商家服务等核心功能。采用微信小程序原生开发框架,提供流畅的用户体验和丰富的社交功能。 + +### 1.2 业务目标 +- **社交旅行**:为用户提供结伴旅行的平台,增强旅行体验 +- **动物认领**:创新的动物认领功能,增加用户粘性 +- **商家服务**:为商家提供服务展示和预订平台 +- **用户增长**:通过微信生态实现用户快速增长 + +### 1.3 技术目标 +- **性能优化**:快速加载,流畅交互 +- **用户体验**:符合微信设计规范,操作简单直观 +- **功能完整**:覆盖核心业务场景 +- **扩展性强**:支持功能快速迭代和扩展 + +## 2. 技术选型 + +### 2.1 开发框架 + +#### 2.1.1 微信小程序原生框架 +```javascript +// 选型理由 +{ + "框架": "微信小程序原生", + "版本": "最新稳定版", + "优势": [ + "官方支持,稳定性高", + "性能最优,启动速度快", + "API完整,功能丰富", + "调试工具完善" + ], + "适用场景": [ + "复杂业务逻辑", + "高性能要求", + "深度集成微信能力" + ] +} +``` + +#### 2.1.2 状态管理 +```javascript +// Mobx-miniprogram +{ + "库": "mobx-miniprogram", + "版本": "^4.13.2", + "优势": [ + "响应式状态管理", + "简单易用", + "性能优秀", + "支持计算属性" + ] +} +``` + +### 2.2 UI组件库 + +#### 2.2.1 Vant Weapp +```javascript +{ + "组件库": "Vant Weapp", + "版本": "^1.11.2", + "优势": [ + "组件丰富", + "设计规范", + "文档完善", + "社区活跃" + ], + "使用组件": [ + "Button", "Cell", "Form", + "Popup", "Dialog", "Toast", + "Tab", "NavBar", "Search" + ] +} +``` + +### 2.3 工具库 + +#### 2.3.1 网络请求 +```javascript +// 自定义HTTP库 +{ + "库": "自研HTTP库", + "特性": [ + "请求拦截器", + "响应拦截器", + "错误处理", + "Loading管理", + "Token自动刷新" + ] +} +``` + +#### 2.3.2 工具函数 +```javascript +{ + "日期处理": "dayjs", + "数据验证": "async-validator", + "图片处理": "自研工具", + "地理位置": "微信API", + "支付": "微信支付API" +} +``` + +## 3. 架构设计 + +### 3.1 整体架构 + +```mermaid +graph TB + subgraph "小程序架构" + A[用户界面层 UI Layer] + B[业务逻辑层 Business Layer] + C[数据管理层 Data Layer] + D[服务层 Service Layer] + E[工具层 Utils Layer] + end + + subgraph "外部服务" + F[后端API] + G[微信API] + H[第三方服务] + end + + A --> B + B --> C + B --> D + D --> F + D --> G + D --> H + B --> E + C --> E +``` + +### 3.2 分层架构详解 + +#### 3.2.1 用户界面层 (UI Layer) +```javascript +// 页面组件结构 +pages/ +├── index/ // 首页 +├── travel/ // 结伴旅行 +│ ├── list/ // 旅行列表 +│ ├── detail/ // 旅行详情 +│ └── create/ // 创建旅行 +├── animal/ // 动物认领 +│ ├── list/ // 动物列表 +│ ├── detail/ // 动物详情 +│ └── adopt/ // 认领页面 +├── merchant/ // 商家服务 +├── user/ // 用户中心 +└── common/ // 通用页面 +``` + +#### 3.2.2 业务逻辑层 (Business Layer) +```javascript +// 业务模块结构 +business/ +├── user/ // 用户业务 +│ ├── auth.js // 认证逻辑 +│ ├── profile.js // 用户资料 +│ └── settings.js // 用户设置 +├── travel/ // 旅行业务 +│ ├── list.js // 列表逻辑 +│ ├── detail.js // 详情逻辑 +│ └── booking.js // 预订逻辑 +├── animal/ // 动物业务 +└── merchant/ // 商家业务 +``` + +#### 3.2.3 数据管理层 (Data Layer) +```javascript +// 状态管理结构 +store/ +├── index.js // Store入口 +├── user.js // 用户状态 +├── travel.js // 旅行状态 +├── animal.js // 动物状态 +└── common.js // 通用状态 + +// 本地存储管理 +storage/ +├── index.js // 存储管理器 +├── user.js // 用户数据 +├── cache.js // 缓存管理 +└── config.js // 配置数据 +``` + +#### 3.2.4 服务层 (Service Layer) +```javascript +// API服务结构 +services/ +├── http.js // HTTP客户端 +├── user.js // 用户API +├── travel.js // 旅行API +├── animal.js // 动物API +├── merchant.js // 商家API +├── payment.js // 支付API +└── upload.js // 文件上传 +``` + +#### 3.2.5 工具层 (Utils Layer) +```javascript +// 工具函数结构 +utils/ +├── index.js // 工具入口 +├── date.js // 日期工具 +├── format.js // 格式化工具 +├── validate.js // 验证工具 +├── location.js // 位置工具 +├── image.js // 图片工具 +└── wechat.js // 微信API封装 +``` + +## 4. 核心模块设计 + +### 4.1 用户模块 + +#### 4.1.1 用户认证 +```javascript +// 用户认证流程 +class AuthService { + // 微信登录 + async wxLogin() { + try { + // 1. 获取微信授权码 + const { code } = await wx.login(); + + // 2. 获取用户信息 + const userInfo = await this.getUserProfile(); + + // 3. 后端验证登录 + const result = await api.user.login({ + code, + userInfo + }); + + // 4. 保存用户信息 + await this.saveUserInfo(result); + + return result; + } catch (error) { + throw new Error('登录失败'); + } + } + + // 获取用户资料 + async getUserProfile() { + return new Promise((resolve, reject) => { + wx.getUserProfile({ + desc: '用于完善用户资料', + success: resolve, + fail: reject + }); + }); + } +} +``` + +#### 4.1.2 用户状态管理 +```javascript +// 用户Store +import { observable, action, computed } from 'mobx-miniprogram'; + +export const userStore = observable({ + // 用户信息 + userInfo: null, + token: '', + isLogin: false, + + // 用户设置 + settings: { + notifications: true, + location: true, + privacy: 'public' + }, + + // 计算属性 + get isVip() { + return this.userInfo?.vipLevel > 0; + }, + + // 动作 + setUserInfo: action(function(userInfo) { + this.userInfo = userInfo; + this.isLogin = true; + }), + + setToken: action(function(token) { + this.token = token; + }), + + logout: action(function() { + this.userInfo = null; + this.token = ''; + this.isLogin = false; + }) +}); +``` + +### 4.2 旅行模块 + +#### 4.2.1 旅行列表 +```javascript +// 旅行列表组件 +Component({ + data: { + travelList: [], + loading: false, + hasMore: true, + page: 1, + filters: { + city: '', + date: '', + type: '' + } + }, + + lifetimes: { + attached() { + this.loadTravelList(); + } + }, + + methods: { + // 加载旅行列表 + async loadTravelList(refresh = false) { + if (this.data.loading) return; + + this.setData({ loading: true }); + + try { + const page = refresh ? 1 : this.data.page; + const result = await api.travel.getList({ + page, + ...this.data.filters + }); + + const travelList = refresh + ? result.list + : [...this.data.travelList, ...result.list]; + + this.setData({ + travelList, + hasMore: result.hasMore, + page: page + 1, + loading: false + }); + } catch (error) { + this.setData({ loading: false }); + wx.showToast({ + title: '加载失败', + icon: 'error' + }); + } + }, + + // 筛选 + onFilter(e) { + const filters = e.detail; + this.setData({ filters }); + this.loadTravelList(true); + }, + + // 下拉刷新 + onRefresh() { + this.loadTravelList(true); + }, + + // 上拉加载 + onLoadMore() { + if (this.data.hasMore) { + this.loadTravelList(); + } + } + } +}); +``` + +#### 4.2.2 旅行详情 +```javascript +// 旅行详情页面 +Page({ + data: { + travelId: '', + travelDetail: null, + loading: true, + joined: false, + participants: [] + }, + + onLoad(options) { + this.setData({ travelId: options.id }); + this.loadTravelDetail(); + }, + + // 加载旅行详情 + async loadTravelDetail() { + try { + const result = await api.travel.getDetail(this.data.travelId); + + this.setData({ + travelDetail: result.travel, + participants: result.participants, + joined: result.joined, + loading: false + }); + } catch (error) { + this.setData({ loading: false }); + wx.showToast({ + title: '加载失败', + icon: 'error' + }); + } + }, + + // 加入旅行 + async joinTravel() { + try { + await api.travel.join(this.data.travelId); + + this.setData({ joined: true }); + + wx.showToast({ + title: '加入成功', + icon: 'success' + }); + + // 刷新参与者列表 + this.loadTravelDetail(); + } catch (error) { + wx.showToast({ + title: error.message || '加入失败', + icon: 'error' + }); + } + } +}); +``` + +### 4.3 动物认领模块 + +#### 4.3.1 动物列表 +```javascript +// 动物列表组件 +Component({ + data: { + animalList: [], + categories: [], + selectedCategory: '', + loading: false + }, + + lifetimes: { + attached() { + this.loadCategories(); + this.loadAnimalList(); + } + }, + + methods: { + // 加载动物分类 + async loadCategories() { + try { + const categories = await api.animal.getCategories(); + this.setData({ categories }); + } catch (error) { + console.error('加载分类失败', error); + } + }, + + // 加载动物列表 + async loadAnimalList() { + this.setData({ loading: true }); + + try { + const result = await api.animal.getList({ + category: this.data.selectedCategory + }); + + this.setData({ + animalList: result.list, + loading: false + }); + } catch (error) { + this.setData({ loading: false }); + wx.showToast({ + title: '加载失败', + icon: 'error' + }); + } + }, + + // 切换分类 + onCategoryChange(e) { + const category = e.detail; + this.setData({ selectedCategory: category }); + this.loadAnimalList(); + }, + + // 认领动物 + async adoptAnimal(e) { + const animalId = e.currentTarget.dataset.id; + + try { + await api.animal.adopt(animalId); + + wx.showToast({ + title: '认领成功', + icon: 'success' + }); + + // 刷新列表 + this.loadAnimalList(); + } catch (error) { + wx.showToast({ + title: error.message || '认领失败', + icon: 'error' + }); + } + } + } +}); +``` + +### 4.4 支付模块 + +#### 4.4.1 支付服务 +```javascript +// 支付服务 +class PaymentService { + // 微信支付 + async wxPay(orderInfo) { + try { + // 1. 创建支付订单 + const paymentData = await api.payment.createOrder(orderInfo); + + // 2. 调用微信支付 + const result = await this.requestPayment(paymentData); + + // 3. 支付成功处理 + await this.handlePaymentSuccess(result); + + return result; + } catch (error) { + throw new Error('支付失败'); + } + } + + // 调用微信支付API + requestPayment(paymentData) { + return new Promise((resolve, reject) => { + wx.requestPayment({ + timeStamp: paymentData.timeStamp, + nonceStr: paymentData.nonceStr, + package: paymentData.package, + signType: paymentData.signType, + paySign: paymentData.paySign, + success: resolve, + fail: reject + }); + }); + } + + // 支付成功处理 + async handlePaymentSuccess(result) { + // 更新订单状态 + await api.payment.confirmPayment(result); + + // 更新本地状态 + // ... + } +} +``` + +## 5. 数据架构 + +### 5.1 状态管理架构 + +```mermaid +graph TB + subgraph "Store架构" + A[RootStore] + B[UserStore] + C[TravelStore] + D[AnimalStore] + E[CommonStore] + end + + subgraph "页面组件" + F[Page Components] + G[Custom Components] + end + + subgraph "本地存储" + H[Storage Manager] + I[Cache Manager] + end + + A --> B + A --> C + A --> D + A --> E + + F --> A + G --> A + + A --> H + A --> I +``` + +### 5.2 数据流设计 + +#### 5.2.1 数据流向 +```javascript +// 数据流管理 +class DataFlow { + // 数据获取流程 + async fetchData(type, params) { + // 1. 检查缓存 + const cached = await this.checkCache(type, params); + if (cached && !this.isExpired(cached)) { + return cached.data; + } + + // 2. 请求API + const data = await this.requestAPI(type, params); + + // 3. 更新缓存 + await this.updateCache(type, params, data); + + // 4. 更新Store + this.updateStore(type, data); + + return data; + } + + // 缓存管理 + async checkCache(type, params) { + const key = this.generateCacheKey(type, params); + return await storage.get(key); + } + + async updateCache(type, params, data) { + const key = this.generateCacheKey(type, params); + await storage.set(key, { + data, + timestamp: Date.now(), + expiry: this.getCacheExpiry(type) + }); + } +} +``` + +### 5.3 本地存储设计 + +#### 5.3.1 存储结构 +```javascript +// 存储管理器 +class StorageManager { + constructor() { + this.prefix = 'jiebanke_'; + this.version = '1.0.0'; + } + + // 用户数据存储 + async setUserData(data) { + await this.set('user_data', { + ...data, + version: this.version, + timestamp: Date.now() + }); + } + + // 缓存数据存储 + async setCacheData(key, data, expiry = 3600000) { + await this.set(`cache_${key}`, { + data, + expiry: Date.now() + expiry, + version: this.version + }); + } + + // 基础存储方法 + async set(key, value) { + try { + await wx.setStorage({ + key: this.prefix + key, + data: value + }); + } catch (error) { + console.error('存储失败', error); + } + } + + async get(key) { + try { + const result = await wx.getStorage({ + key: this.prefix + key + }); + return result.data; + } catch (error) { + return null; + } + } +} +``` + +## 6. 网络架构 + +### 6.1 HTTP客户端设计 + +```javascript +// HTTP客户端 +class HttpClient { + constructor() { + this.baseURL = 'https://api.jiebanke.com'; + this.timeout = 10000; + this.interceptors = { + request: [], + response: [] + }; + } + + // 请求拦截器 + addRequestInterceptor(interceptor) { + this.interceptors.request.push(interceptor); + } + + // 响应拦截器 + addResponseInterceptor(interceptor) { + this.interceptors.response.push(interceptor); + } + + // 发送请求 + async request(config) { + // 应用请求拦截器 + for (const interceptor of this.interceptors.request) { + config = await interceptor(config); + } + + try { + const response = await this.wxRequest(config); + + // 应用响应拦截器 + for (const interceptor of this.interceptors.response) { + response = await interceptor(response); + } + + return response; + } catch (error) { + throw this.handleError(error); + } + } + + // 微信请求封装 + wxRequest(config) { + return new Promise((resolve, reject) => { + wx.request({ + url: this.baseURL + config.url, + method: config.method || 'GET', + data: config.data, + header: { + 'Content-Type': 'application/json', + ...config.headers + }, + timeout: this.timeout, + success: resolve, + fail: reject + }); + }); + } +} +``` + +### 6.2 API服务设计 + +#### 6.2.1 用户API +```javascript +// 用户API服务 +class UserAPI { + constructor(http) { + this.http = http; + } + + // 用户登录 + async login(data) { + return await this.http.request({ + url: '/user/login', + method: 'POST', + data + }); + } + + // 获取用户信息 + async getProfile() { + return await this.http.request({ + url: '/user/profile', + method: 'GET' + }); + } + + // 更新用户信息 + async updateProfile(data) { + return await this.http.request({ + url: '/user/profile', + method: 'PUT', + data + }); + } +} +``` + +#### 6.2.2 旅行API +```javascript +// 旅行API服务 +class TravelAPI { + constructor(http) { + this.http = http; + } + + // 获取旅行列表 + async getList(params) { + return await this.http.request({ + url: '/travel/list', + method: 'GET', + data: params + }); + } + + // 获取旅行详情 + async getDetail(id) { + return await this.http.request({ + url: `/travel/${id}`, + method: 'GET' + }); + } + + // 创建旅行 + async create(data) { + return await this.http.request({ + url: '/travel', + method: 'POST', + data + }); + } + + // 加入旅行 + async join(id) { + return await this.http.request({ + url: `/travel/${id}/join`, + method: 'POST' + }); + } +} +``` + +## 7. 性能优化 + +### 7.1 启动性能优化 + +#### 7.1.1 代码分包 +```javascript +// app.json 分包配置 +{ + "pages": [ + "pages/index/index", + "pages/user/index" + ], + "subPackages": [ + { + "root": "packages/travel", + "name": "travel", + "pages": [ + "list/index", + "detail/index", + "create/index" + ] + }, + { + "root": "packages/animal", + "name": "animal", + "pages": [ + "list/index", + "detail/index", + "adopt/index" + ] + } + ], + "preloadRule": { + "pages/index/index": { + "network": "all", + "packages": ["travel"] + } + } +} +``` + +#### 7.1.2 资源优化 +```javascript +// 图片懒加载组件 +Component({ + properties: { + src: String, + placeholder: String + }, + + data: { + loaded: false, + error: false + }, + + lifetimes: { + attached() { + this.observer = wx.createIntersectionObserver(this); + this.observer.relativeToViewport().observe('.lazy-image', (res) => { + if (res.intersectionRatio > 0) { + this.loadImage(); + this.observer.disconnect(); + } + }); + }, + + detached() { + if (this.observer) { + this.observer.disconnect(); + } + } + }, + + methods: { + loadImage() { + const img = wx.createImage(); + img.onload = () => { + this.setData({ loaded: true }); + }; + img.onerror = () => { + this.setData({ error: true }); + }; + img.src = this.properties.src; + } + } +}); +``` + +### 7.2 运行时性能优化 + +#### 7.2.1 数据缓存策略 +```javascript +// 缓存策略管理 +class CacheStrategy { + constructor() { + this.strategies = { + // 用户数据 - 长期缓存 + user: { + expiry: 24 * 60 * 60 * 1000, // 24小时 + storage: 'local' + }, + // 旅行列表 - 短期缓存 + travelList: { + expiry: 5 * 60 * 1000, // 5分钟 + storage: 'memory' + }, + // 动物列表 - 中期缓存 + animalList: { + expiry: 30 * 60 * 1000, // 30分钟 + storage: 'local' + } + }; + } + + // 获取缓存策略 + getStrategy(type) { + return this.strategies[type] || { + expiry: 5 * 60 * 1000, + storage: 'memory' + }; + } + + // 检查缓存是否过期 + isExpired(cacheData, type) { + const strategy = this.getStrategy(type); + return Date.now() - cacheData.timestamp > strategy.expiry; + } +} +``` + +#### 7.2.2 列表虚拟化 +```javascript +// 虚拟列表组件 +Component({ + properties: { + items: Array, + itemHeight: Number, + containerHeight: Number + }, + + data: { + visibleItems: [], + scrollTop: 0, + startIndex: 0, + endIndex: 0 + }, + + observers: { + 'items, containerHeight, itemHeight': function() { + this.updateVisibleItems(); + } + }, + + methods: { + // 更新可见项目 + updateVisibleItems() { + const { items, itemHeight, containerHeight } = this.properties; + const { scrollTop } = this.data; + + const visibleCount = Math.ceil(containerHeight / itemHeight); + const startIndex = Math.floor(scrollTop / itemHeight); + const endIndex = Math.min(startIndex + visibleCount + 1, items.length); + + const visibleItems = items.slice(startIndex, endIndex).map((item, index) => ({ + ...item, + index: startIndex + index, + top: (startIndex + index) * itemHeight + })); + + this.setData({ + visibleItems, + startIndex, + endIndex + }); + }, + + // 滚动事件 + onScroll(e) { + const scrollTop = e.detail.scrollTop; + this.setData({ scrollTop }); + this.updateVisibleItems(); + } + } +}); +``` + +## 8. 安全架构 + +### 8.1 数据安全 + +#### 8.1.1 敏感数据加密 +```javascript +// 数据加密工具 +class CryptoUtil { + constructor() { + this.algorithm = 'AES-256-GCM'; + this.keyLength = 32; + } + + // 生成密钥 + generateKey() { + const array = new Uint8Array(this.keyLength); + wx.getRandomValues(array); + return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join(''); + } + + // 加密数据 + encrypt(data, key) { + try { + const jsonString = JSON.stringify(data); + const encrypted = this.aesEncrypt(jsonString, key); + return encrypted; + } catch (error) { + throw new Error('加密失败'); + } + } + + // 解密数据 + decrypt(encryptedData, key) { + try { + const decrypted = this.aesDecrypt(encryptedData, key); + return JSON.parse(decrypted); + } catch (error) { + throw new Error('解密失败'); + } + } +} +``` + +#### 8.1.2 Token管理 +```javascript +// Token管理器 +class TokenManager { + constructor() { + this.tokenKey = 'access_token'; + this.refreshTokenKey = 'refresh_token'; + } + + // 保存Token + async saveToken(tokenData) { + await storage.set(this.tokenKey, { + token: tokenData.accessToken, + expiry: Date.now() + tokenData.expiresIn * 1000 + }); + + await storage.set(this.refreshTokenKey, tokenData.refreshToken); + } + + // 获取Token + async getToken() { + const tokenData = await storage.get(this.tokenKey); + + if (!tokenData) { + return null; + } + + // 检查是否过期 + if (Date.now() > tokenData.expiry) { + return await this.refreshToken(); + } + + return tokenData.token; + } + + // 刷新Token + async refreshToken() { + try { + const refreshToken = await storage.get(this.refreshTokenKey); + + if (!refreshToken) { + throw new Error('Refresh token not found'); + } + + const result = await api.user.refreshToken(refreshToken); + await this.saveToken(result); + + return result.accessToken; + } catch (error) { + // 刷新失败,清除所有Token + await this.clearTokens(); + throw error; + } + } + + // 清除Token + async clearTokens() { + await storage.remove(this.tokenKey); + await storage.remove(this.refreshTokenKey); + } +} +``` + +### 8.2 接口安全 + +#### 8.2.1 请求签名 +```javascript +// 请求签名工具 +class RequestSigner { + constructor(secretKey) { + this.secretKey = secretKey; + } + + // 生成签名 + generateSignature(params, timestamp, nonce) { + // 1. 参数排序 + const sortedParams = this.sortParams(params); + + // 2. 构建签名字符串 + const signString = this.buildSignString(sortedParams, timestamp, nonce); + + // 3. 生成签名 + const signature = this.hmacSha256(signString, this.secretKey); + + return signature; + } + + // 参数排序 + sortParams(params) { + return Object.keys(params) + .sort() + .reduce((result, key) => { + result[key] = params[key]; + return result; + }, {}); + } + + // 构建签名字符串 + buildSignString(params, timestamp, nonce) { + const paramString = Object.keys(params) + .map(key => `${key}=${params[key]}`) + .join('&'); + + return `${paramString}×tamp=${timestamp}&nonce=${nonce}&secret=${this.secretKey}`; + } +} +``` + +## 9. 测试架构 + +### 9.1 单元测试 + +#### 9.1.1 工具函数测试 +```javascript +// 工具函数测试 +describe('DateUtil', () => { + test('formatDate should format date correctly', () => { + const date = new Date('2023-12-25'); + const formatted = DateUtil.formatDate(date, 'YYYY-MM-DD'); + expect(formatted).toBe('2023-12-25'); + }); + + test('isValidDate should validate date correctly', () => { + expect(DateUtil.isValidDate('2023-12-25')).toBe(true); + expect(DateUtil.isValidDate('invalid-date')).toBe(false); + }); +}); + +// API服务测试 +describe('UserAPI', () => { + let userAPI; + let mockHttp; + + beforeEach(() => { + mockHttp = { + request: jest.fn() + }; + userAPI = new UserAPI(mockHttp); + }); + + test('login should call correct endpoint', async () => { + const loginData = { code: 'test-code' }; + const expectedResponse = { token: 'test-token' }; + + mockHttp.request.mockResolvedValue(expectedResponse); + + const result = await userAPI.login(loginData); + + expect(mockHttp.request).toHaveBeenCalledWith({ + url: '/user/login', + method: 'POST', + data: loginData + }); + expect(result).toEqual(expectedResponse); + }); +}); +``` + +### 9.2 集成测试 + +#### 9.2.1 页面测试 +```javascript +// 页面集成测试 +describe('Travel List Page', () => { + let page; + + beforeEach(() => { + page = new TravelListPage(); + // Mock API responses + jest.spyOn(api.travel, 'getList').mockResolvedValue({ + list: [ + { id: 1, title: 'Test Travel 1' }, + { id: 2, title: 'Test Travel 2' } + ], + hasMore: false + }); + }); + + test('should load travel list on page load', async () => { + await page.onLoad(); + + expect(api.travel.getList).toHaveBeenCalled(); + expect(page.data.travelList).toHaveLength(2); + expect(page.data.loading).toBe(false); + }); + + test('should handle filter changes', async () => { + const filters = { city: 'Beijing', date: '2023-12-25' }; + + await page.onFilter({ detail: filters }); + + expect(api.travel.getList).toHaveBeenCalledWith( + expect.objectContaining(filters) + ); + }); +}); +``` + +### 9.3 端到端测试 + +#### 9.3.1 用户流程测试 +```javascript +// E2E测试 +describe('User Journey', () => { + test('complete travel booking flow', async () => { + // 1. 用户登录 + await page.goto('/pages/user/login'); + await page.tap('.login-btn'); + await page.waitFor('.user-info'); + + // 2. 浏览旅行列表 + await page.goto('/pages/travel/list'); + await page.waitFor('.travel-item'); + + // 3. 查看旅行详情 + await page.tap('.travel-item:first-child'); + await page.waitFor('.travel-detail'); + + // 4. 加入旅行 + await page.tap('.join-btn'); + await page.waitFor('.success-toast'); + + // 5. 验证结果 + const joinedStatus = await page.$('.joined-status'); + expect(joinedStatus).toBeTruthy(); + }); +}); +``` + +## 10. 部署架构 + +### 10.1 构建配置 + +#### 10.1.1 环境配置 +```javascript +// 环境配置 +const config = { + development: { + apiBaseURL: 'https://dev-api.jiebanke.com', + debug: true, + logLevel: 'debug' + }, + testing: { + apiBaseURL: 'https://test-api.jiebanke.com', + debug: true, + logLevel: 'info' + }, + production: { + apiBaseURL: 'https://api.jiebanke.com', + debug: false, + logLevel: 'error' + } +}; + +// 获取当前环境配置 +function getConfig() { + const env = process.env.NODE_ENV || 'development'; + return config[env]; +} + +module.exports = getConfig(); +``` + +#### 10.1.2 构建脚本 +```json +{ + "scripts": { + "dev": "cross-env NODE_ENV=development miniprogram-cli dev", + "build:test": "cross-env NODE_ENV=testing miniprogram-cli build", + "build:prod": "cross-env NODE_ENV=production miniprogram-cli build", + "preview": "miniprogram-cli preview", + "upload": "miniprogram-cli upload", + "test": "jest", + "lint": "eslint . --ext .js", + "lint:fix": "eslint . --ext .js --fix" + } +} +``` + +### 10.2 CI/CD流程 + +#### 10.2.1 GitHub Actions配置 +```yaml +# .github/workflows/miniprogram.yml +name: MiniProgram CI/CD + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Run linting + run: npm run lint + + build-and-deploy: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build for production + run: npm run build:prod + + - name: Upload to WeChat + run: npm run upload + env: + WECHAT_APPID: ${{ secrets.WECHAT_APPID }} + WECHAT_PRIVATE_KEY: ${{ secrets.WECHAT_PRIVATE_KEY }} +``` + +## 11. 监控与分析 + +### 11.1 性能监控 + +#### 11.1.1 性能指标收集 +```javascript +// 性能监控工具 +class PerformanceMonitor { + constructor() { + this.metrics = {}; + this.init(); + } + + init() { + // 监听页面性能 + wx.onAppRoute((res) => { + this.trackPagePerformance(res); + }); + + // 监听网络请求 + this.interceptNetworkRequests(); + } + + // 页面性能追踪 + trackPagePerformance(route) { + const startTime = Date.now(); + + // 页面加载完成后记录 + setTimeout(() => { + const loadTime = Date.now() - startTime; + this.recordMetric('page_load_time', { + route: route.path, + loadTime, + timestamp: Date.now() + }); + }, 0); + } + + // 网络请求拦截 + interceptNetworkRequests() { + const originalRequest = wx.request; + + wx.request = (options) => { + const startTime = Date.now(); + + const originalSuccess = options.success; + const originalFail = options.fail; + + options.success = (res) => { + const duration = Date.now() - startTime; + this.recordMetric('api_request', { + url: options.url, + method: options.method, + duration, + status: res.statusCode, + success: true + }); + + if (originalSuccess) { + originalSuccess(res); + } + }; + + options.fail = (err) => { + const duration = Date.now() - startTime; + this.recordMetric('api_request', { + url: options.url, + method: options.method, + duration, + success: false, + error: err.errMsg + }); + + if (originalFail) { + originalFail(err); + } + }; + + return originalRequest(options); + }; + } + + // 记录指标 + recordMetric(type, data) { + if (!this.metrics[type]) { + this.metrics[type] = []; + } + + this.metrics[type].push(data); + + // 定期上报 + this.reportMetrics(); + } + + // 上报指标 + async reportMetrics() { + if (Object.keys(this.metrics).length === 0) { + return; + } + + try { + await api.analytics.reportMetrics(this.metrics); + this.metrics = {}; // 清空已上报的指标 + } catch (error) { + console.error('指标上报失败', error); + } + } +} +``` + +### 11.2 错误监控 + +#### 11.2.1 错误捕获和上报 +```javascript +// 错误监控工具 +class ErrorMonitor { + constructor() { + this.errors = []; + this.init(); + } + + init() { + // 全局错误监听 + wx.onError((error) => { + this.captureError('global_error', error); + }); + + // 未处理的Promise拒绝 + wx.onUnhandledRejection((res) => { + this.captureError('unhandled_rejection', res.reason); + }); + + // HTTP错误监听 + this.interceptHttpErrors(); + } + + // 捕获错误 + captureError(type, error) { + const errorInfo = { + type, + message: error.message || error, + stack: error.stack, + timestamp: Date.now(), + userAgent: wx.getSystemInfoSync(), + route: getCurrentPages().pop()?.route + }; + + this.errors.push(errorInfo); + + // 立即上报严重错误 + if (this.isCriticalError(error)) { + this.reportErrors(); + } + } + + // 判断是否为严重错误 + isCriticalError(error) { + const criticalKeywords = ['network', 'payment', 'auth']; + const message = (error.message || error).toLowerCase(); + + return criticalKeywords.some(keyword => + message.includes(keyword) + ); + } + + // 上报错误 + async reportErrors() { + if (this.errors.length === 0) { + return; + } + + try { + await api.analytics.reportErrors(this.errors); + this.errors = []; // 清空已上报的错误 + } catch (error) { + console.error('错误上报失败', error); + } + } +} +``` + +## 12. 总结 + +### 12.1 架构优势 + +#### 12.1.1 技术优势 +- **原生性能**:使用微信小程序原生框架,性能最优 +- **开发效率**:组件化开发,代码复用率高 +- **用户体验**:符合微信设计规范,用户学习成本低 +- **生态集成**:深度集成微信生态,功能丰富 + +#### 12.1.2 业务优势 +- **快速迭代**:模块化架构,支持功能快速开发和上线 +- **数据驱动**:完善的数据收集和分析体系 +- **用户增长**:利用微信社交关系链,促进用户增长 +- **商业变现**:多样化的商业模式支持 + +### 12.2 扩展性设计 + +#### 12.2.1 功能扩展 +- **插件化架构**:支持功能模块插件化扩展 +- **配置化管理**:业务规则配置化,灵活调整 +- **API版本管理**:支持API平滑升级 +- **多端适配**:架构支持多端扩展 + +#### 12.2.2 性能扩展 +- **分包加载**:支持功能分包,按需加载 +- **缓存策略**:多级缓存,提高响应速度 +- **虚拟化列表**:支持大数据量列表展示 +- **图片优化**:懒加载和压缩优化 + +### 12.3 运维保障 + +#### 12.3.1 监控体系 +- **性能监控**:全方位性能指标监控 +- **错误监控**:实时错误捕获和告警 +- **用户行为分析**:用户行为数据收集和分析 +- **业务指标监控**:关键业务指标实时监控 + +#### 12.3.2 质量保障 +- **自动化测试**:单元测试、集成测试、E2E测试 +- **代码质量**:ESLint代码规范检查 +- **CI/CD流程**:自动化构建和部署 +- **版本管理**:规范的版本发布流程 + +本小程序架构文档为解班客项目提供了完整的前端架构指导,通过合理的架构设计和技术选型,确保小程序的高性能、高可用性和良好的用户体验,为业务发展提供坚实的技术基础。 \ No newline at end of file diff --git a/docs/常见问题.md b/docs/常见问题.md deleted file mode 100644 index 1d6741a..0000000 --- a/docs/常见问题.md +++ /dev/null @@ -1,469 +0,0 @@ -# 常见问题 (FAQ) - -本文档收集了结伴客项目开发和使用过程中的常见问题及解决方案。 - -## 📋 目录 - -- [环境搭建问题](#环境搭建问题) -- [开发相关问题](#开发相关问题) -- [部署相关问题](#部署相关问题) -- [数据库问题](#数据库问题) -- [API接口问题](#api接口问题) -- [前端问题](#前端问题) -- [小程序问题](#小程序问题) -- [性能优化问题](#性能优化问题) - -## 🛠️ 环境搭建问题 - -### Q: Node.js版本要求是什么? -**A:** 项目要求 Node.js 16.x 或更高版本。推荐使用 Node.js 18.x LTS 版本。 - -```bash -# 检查Node.js版本 -node --version - -# 使用nvm管理Node.js版本 -nvm install 18 -nvm use 18 -``` - -### Q: 安装依赖时出现权限错误怎么办? -**A:** 避免使用 `sudo` 安装npm包,建议配置npm全局目录: - -```bash -# 创建全局目录 -mkdir ~/.npm-global - -# 配置npm使用新目录 -npm config set prefix '~/.npm-global' - -# 添加到环境变量 -echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc -source ~/.bashrc -``` - -### Q: MySQL连接失败怎么办? -**A:** 检查以下几个方面: - -1. **确认MySQL服务运行** - ```bash - # macOS - brew services start mysql - - # Linux - sudo systemctl start mysql - - # Windows - net start mysql - ``` - -2. **检查连接配置** - ```javascript - // backend/.env - DB_HOST=localhost - DB_PORT=3306 - DB_NAME=jiebanke - DB_USER=root - DB_PASSWORD=your_password - ``` - -3. **创建数据库** - ```sql - CREATE DATABASE jiebanke CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - ``` - -### Q: Redis连接问题如何解决? -**A:** 确保Redis服务正常运行: - -```bash -# 启动Redis服务 -redis-server - -# 测试连接 -redis-cli ping -# 应该返回 PONG -``` - -## 💻 开发相关问题 - -### Q: 如何重置数据库? -**A:** 使用项目提供的重置脚本: - -```bash -# 方法1:使用npm脚本 -cd backend -npm run db:reset - -# 方法2:手动执行SQL -mysql -u root -p jiebanke < scripts/init-database.sql -``` - -### Q: 热重载不工作怎么办? -**A:** 检查以下配置: - -1. **后端热重载** - ```bash - # 确保使用nodemon - npm run dev - - # 检查nodemon配置 - cat nodemon.json - ``` - -2. **前端热重载** - ```bash - # 确保Vite配置正确 - npm run dev - - # 检查vite.config.ts中的server配置 - ``` - -### Q: ESLint报错如何解决? -**A:** 常见解决方案: - -```bash -# 自动修复可修复的问题 -npm run lint:fix - -# 检查ESLint配置 -cat .eslintrc.js - -# 重新安装ESLint依赖 -npm install --save-dev eslint @typescript-eslint/parser -``` - -### Q: 如何调试API接口? -**A:** 推荐使用以下工具: - -1. **使用内置测试脚本** - ```bash - cd backend - npm run test:api - ``` - -2. **使用Postman或Insomnia** - - 导入API文档中的接口定义 - - 配置环境变量 - -3. **使用curl命令** - ```bash - # 测试登录接口 - curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"test@example.com","password":"123456"}' - ``` - -## 🚀 部署相关问题 - -### Q: Docker部署失败怎么办? -**A:** 检查以下几个方面: - -1. **确认Docker版本** - ```bash - docker --version - docker-compose --version - ``` - -2. **检查端口占用** - ```bash - # 检查端口是否被占用 - lsof -i :3000 - lsof -i :8080 - ``` - -3. **查看容器日志** - ```bash - docker-compose logs backend - docker-compose logs admin-system - ``` - -### Q: Nginx配置问题如何解决? -**A:** 常见配置问题: - -1. **检查配置文件语法** - ```bash - nginx -t - ``` - -2. **重新加载配置** - ```bash - nginx -s reload - ``` - -3. **查看错误日志** - ```bash - tail -f /var/log/nginx/error.log - ``` - -### Q: SSL证书配置问题? -**A:** 使用Let's Encrypt免费证书: - -```bash -# 安装certbot -sudo apt install certbot python3-certbot-nginx - -# 获取证书 -sudo certbot --nginx -d yourdomain.com - -# 自动续期 -sudo crontab -e -# 添加:0 12 * * * /usr/bin/certbot renew --quiet -``` - -## 🗄️ 数据库问题 - -### Q: 数据库迁移失败怎么办? -**A:** 按以下步骤排查: - -1. **检查迁移文件** - ```bash - # 查看迁移状态 - npx sequelize-cli db:migrate:status - - # 回滚迁移 - npx sequelize-cli db:migrate:undo - ``` - -2. **手动执行SQL** - ```sql - -- 查看表结构 - DESCRIBE users; - - -- 检查约束 - SHOW CREATE TABLE users; - ``` - -### Q: 数据库性能问题如何优化? -**A:** 常见优化方案: - -1. **添加索引** - ```sql - -- 为常用查询字段添加索引 - CREATE INDEX idx_user_email ON users(email); - CREATE INDEX idx_created_at ON travel_plans(created_at); - ``` - -2. **查询优化** - ```sql - -- 使用EXPLAIN分析查询 - EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'; - ``` - -3. **配置优化** - ```ini - # my.cnf - [mysqld] - innodb_buffer_pool_size = 1G - query_cache_size = 256M - ``` - -## 🔌 API接口问题 - -### Q: 接口返回401未授权错误? -**A:** 检查JWT token配置: - -1. **确认token格式** - ```javascript - // 请求头格式 - Authorization: Bearer - ``` - -2. **检查token有效期** - ```javascript - // 解码token查看过期时间 - const jwt = require('jsonwebtoken'); - const decoded = jwt.decode(token); - console.log(decoded.exp); - ``` - -### Q: 跨域问题如何解决? -**A:** 配置CORS中间件: - -```javascript -// backend/src/app.js -const cors = require('cors'); - -app.use(cors({ - origin: ['http://localhost:8080', 'https://admin.jiebanke.com'], - credentials: true -})); -``` - -### Q: 文件上传失败怎么办? -**A:** 检查以下配置: - -1. **文件大小限制** - ```javascript - // 增加文件大小限制 - app.use(express.json({ limit: '50mb' })); - app.use(express.urlencoded({ limit: '50mb', extended: true })); - ``` - -2. **存储路径权限** - ```bash - # 确保上传目录有写权限 - chmod 755 uploads/ - ``` - -## 🎨 前端问题 - -### Q: 组件样式不生效怎么办? -**A:** 检查以下几个方面: - -1. **CSS作用域** - ```vue - - - - ``` - -2. **样式优先级** - ```css - /* 使用!important提高优先级 */ - .my-class { - color: red !important; - } - ``` - -### Q: 路由跳转问题? -**A:** 常见解决方案: - -```javascript -// 编程式导航 -this.$router.push('/path'); - -// 声明式导航 -链接 - -// 带参数跳转 -this.$router.push({ - name: 'user', - params: { id: 123 } -}); -``` - -### Q: 状态管理问题? -**A:** 使用Pinia进行状态管理: - -```javascript -// stores/user.js -import { defineStore } from 'pinia'; - -export const useUserStore = defineStore('user', { - state: () => ({ - userInfo: null - }), - actions: { - setUserInfo(info) { - this.userInfo = info; - } - } -}); -``` - -## 📱 小程序问题 - -### Q: 小程序真机调试问题? -**A:** 常见解决方案: - -1. **网络请求问题** - ```javascript - // 确保域名在小程序后台配置 - wx.request({ - url: 'https://api.jiebanke.com/api/v1/users', - method: 'GET', - success: (res) => { - console.log(res.data); - } - }); - ``` - -2. **权限问题** - ```json - // app.json - { - "permission": { - "scope.userLocation": { - "desc": "你的位置信息将用于小程序位置接口的效果展示" - } - } - } - ``` - -### Q: 小程序分包加载问题? -**A:** 配置分包策略: - -```json -// app.json -{ - "pages": ["pages/index/index"], - "subPackages": [ - { - "root": "pages/travel", - "pages": ["list/list", "detail/detail"] - } - ] -} -``` - -## ⚡ 性能优化问题 - -### Q: 页面加载慢怎么优化? -**A:** 常见优化策略: - -1. **代码分割** - ```javascript - // 路由懒加载 - const UserProfile = () => import('./components/UserProfile.vue'); - ``` - -2. **图片优化** - ```html - - description - ``` - -3. **缓存策略** - ```javascript - // 设置HTTP缓存 - app.use(express.static('public', { - maxAge: '1d' - })); - ``` - -### Q: 数据库查询慢如何优化? -**A:** 优化建议: - -1. **使用索引** -2. **避免N+1查询** -3. **使用连接查询替代子查询** -4. **分页查询大数据集** - -## 📞 获取帮助 - -如果以上FAQ没有解决您的问题,可以通过以下方式获取帮助: - -### 技术支持 -- **GitHub Issues**: [提交问题](https://github.com/jiebanke/jiebanke/issues) -- **开发者邮箱**: dev@jiebanke.com -- **技术QQ群**: 123456789 - -### 文档资源 -- **开发指南**: [docs/开发指南.md](开发指南.md) -- **API文档**: [docs/API接口文档.md](API接口文档.md) -- **部署指南**: [docs/部署指南.md](部署指南.md) - -### 社区资源 -- **官方论坛**: https://forum.jiebanke.com -- **技术博客**: https://blog.jiebanke.com -- **视频教程**: https://video.jiebanke.com - ---- - -*本文档持续更新中,如有新问题请及时反馈。* - -*最后更新:2024年1月* \ No newline at end of file diff --git a/docs/开发指南.md b/docs/开发指南.md deleted file mode 100644 index 7450481..0000000 --- a/docs/开发指南.md +++ /dev/null @@ -1,1022 +0,0 @@ -# 🚀 结伴客开发指南 - -## 📋 项目概述 - -结伴客是一个创新的社交旅行平台,融合了结伴旅行和动物认领功能。本开发指南为开发者提供完整的开发环境搭建、代码规范、最佳实践等指导,帮助团队高效协作开发。 - -## 🎯 开发目标 - -### 代码质量目标 -- **可维护性**:代码结构清晰,易于理解和修改 -- **可扩展性**:支持功能快速迭代和模块化扩展 -- **性能优化**:响应时间 < 200ms,并发支持 1000+ -- **安全性**:数据安全,防止常见安全漏洞 - -### 团队协作目标 -- **统一规范**:代码风格、命名规范、提交规范统一 -- **文档完善**:API文档、技术文档实时更新 -- **测试覆盖**:单元测试覆盖率 > 80% -- **持续集成**:自动化构建、测试、部署 - -## 🛠️ 开发环境搭建 - -### 系统要求 - -#### 前端开发环境 -- **Node.js**: 16.0+ (推荐使用 18.x LTS) -- **包管理器**: npm 8.0+ 或 yarn 1.22+ 或 pnpm 7.0+ -- **微信开发者工具**: 最新稳定版 (1.06.x) -- **IDE**: VS Code (推荐) / WebStorm -- **Git**: 2.30+ -- **浏览器**: Chrome 100+ (开发调试) - -#### 后端开发环境 -- **Node.js**: 16.0+ (主要后端) -- **JDK**: Java 17 (微服务版本,推荐使用 OpenJDK) -- **Maven**: 3.6+ (Java项目构建) -- **IDE**: IntelliJ IDEA (推荐) / Eclipse / VS Code -- **数据库**: MySQL 8.0+ -- **缓存**: Redis 6.0+ -- **消息队列**: RabbitMQ 3.8+ (可选) -- **容器化**: Docker 20.10+ 和 Docker Compose 1.29+ - -### 快速环境安装 - -#### macOS 环境安装 -```bash -# 使用 Homebrew 安装开发工具 -# 1. 安装 Homebrew (如果未安装) -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - -# 2. 安装 Node.js 和 npm -brew install node - -# 3. 安装 Java 17 -brew install openjdk@17 -echo 'export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"' >> ~/.zshrc - -# 4. 安装 Maven -brew install maven - -# 5. 安装 Docker -brew install docker docker-compose - -# 6. 安装数据库 -brew install mysql redis - -# 7. 安装开发工具 -brew install git wget curl - -# 8. 验证安装 -node --version # 应显示 v16.x 或更高 -java --version # 应显示 Java 17 -mvn --version # 应显示 Maven 3.6+ -docker --version # 应显示 Docker 20.10+ -``` - -#### Windows 环境安装 -1. **Node.js**: 从 [官网](https://nodejs.org/) 下载安装 -2. **JDK 17**: 从 [Adoptium](https://adoptium.net/) 下载安装 -3. **Maven**: 从 [官网](https://maven.apache.org/) 下载配置 -4. **Docker Desktop**: 从 [官网](https://www.docker.com/products/docker-desktop) 下载安装 -5. **微信开发者工具**: 从 [官网](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) 下载安装 - -#### Ubuntu/Debian 环境安装 -```bash -# 更新包管理器 -sudo apt update - -# 安装 Node.js -curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - -sudo apt-get install -y nodejs - -# 安装 Java 17 -sudo apt install openjdk-17-jdk - -# 安装 Maven -sudo apt install maven - -# 安装 Docker -sudo apt install docker.io docker-compose - -# 安装 MySQL -sudo apt install mysql-server - -# 安装 Redis -sudo apt install redis-server -``` - -### 环境验证 -```bash -# 验证 Node.js 和 npm -node --version -npm --version - -# 验证 Java 和 Maven -java -version -mvn -version - -# 验证 Docker -docker --version -docker-compose --version - -# 验证数据库连接 -mysql --version -redis-cli --version -``` - -## 🏗️ 项目架构 - -### 整体架构 -```mermaid -graph TB - A[微信小程序前端] --> B[API网关] - C[管理后台前端] --> B - D[官网前端] --> B - - B --> E[认证服务] - B --> F[用户服务] - B --> G[旅行服务] - B --> H[动物服务] - B --> I[订单服务] - B --> J[商家服务] - - E --> K[MySQL数据库] - F --> K - G --> K - H --> K - I --> K - J --> K - - E --> L[Redis缓存] - F --> L - G --> L - H --> L - - subgraph "服务注册与发现" - M[Eureka Server] - end - - E --> M - F --> M - G --> M - H --> M - I --> M - J --> M -``` - -### 目录结构 -``` -jiebanke/ -├── frontend/ # 前端项目 -│ ├── miniprogram/ # 微信小程序 -│ ├── admin-web/ # 管理后台 -│ └── website/ # 官方网站 -├── backend-java/ # Java后端服务 -│ ├── eureka-server/ # 服务注册中心 -│ ├── gateway-service/ # API网关 -│ ├── auth-service/ # 认证服务 -│ ├── user-service/ # 用户服务 -│ ├── travel-service/ # 旅行服务 -│ ├── animal-service/ # 动物服务 -│ ├── order-service/ # 订单服务 -│ ├── merchant-service/ # 商家服务 -│ └── common/ # 公共模块 -├── docs/ # 项目文档 -├── scripts/ # 部署脚本 -└── docker-compose.yml # Docker编排文件 -``` - -## 📝 开发规范 - -### 1. 代码规范 - -#### 前端代码规范 - -##### JavaScript/TypeScript 规范 -```javascript -// ✅ 推荐写法 -const getUserInfo = async (userId) => { - try { - const response = await api.get(`/users/${userId}`); - return response.data; - } catch (error) { - console.error('获取用户信息失败:', error); - throw error; - } -}; - -// ❌ 不推荐写法 -function getUserInfo(userId) { - return new Promise((resolve, reject) => { - api.get('/users/' + userId).then(res => { - resolve(res.data); - }).catch(err => { - reject(err); - }); - }); -} -``` - -##### Vue 组件规范 -```vue - - - - - -``` - -#### 后端代码规范 - -##### Java 命名规范 -```java -// ✅ 推荐写法 -public class UserService { - private static final int MAX_RETRY_COUNT = 3; - private final UserMapper userMapper; - - /** - * 根据用户ID获取用户信息 - * - * @param userId 用户ID - * @return 用户信息 - * @throws BusinessException 当用户不存在时抛出 - */ - public UserDTO getUserById(Long userId) { - if (userId == null || userId <= 0) { - throw new IllegalArgumentException("用户ID不能为空或小于等于0"); - } - - User user = userMapper.selectById(userId); - if (user == null) { - throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户不存在"); - } - - return UserConverter.toDTO(user); - } -} - -// ❌ 不推荐写法 -public class userservice { - public Object getuser(Object id) { - return userMapper.selectById((Long)id); - } -} -``` - -##### Spring Boot 控制器规范 -```java -@RestController -@RequestMapping("/api/v1/users") -@Validated -@Slf4j -public class UserController { - - private final UserService userService; - - public UserController(UserService userService) { - this.userService = userService; - } - - @GetMapping("/{id}") - @Operation(summary = "获取用户信息", description = "根据用户ID获取用户详细信息") - public ApiResponse getUserById( - @PathVariable @Min(1) Long id) { - - log.info("获取用户信息, userId: {}", id); - - try { - UserDTO user = userService.getUserById(id); - return ApiResponse.success(user); - } catch (BusinessException e) { - log.warn("获取用户信息失败, userId: {}, error: {}", id, e.getMessage()); - return ApiResponse.error(e.getCode(), e.getMessage()); - } catch (Exception e) { - log.error("获取用户信息系统错误, userId: {}", id, e); - return ApiResponse.error(ErrorCode.SYSTEM_ERROR, "系统错误"); - } - } -} -``` - -### 2. 数据库规范 - -#### 表设计规范 -```sql --- ✅ 推荐的表结构设计 -CREATE TABLE users ( - id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', - openid VARCHAR(100) UNIQUE NOT NULL COMMENT '微信openid', - nickname VARCHAR(50) NOT NULL COMMENT '用户昵称', - avatar VARCHAR(255) COMMENT '头像URL', - gender ENUM('male', 'female', 'unknown') DEFAULT 'unknown' COMMENT '性别', - phone VARCHAR(20) UNIQUE COMMENT '手机号码', - status ENUM('active', 'inactive', 'banned') DEFAULT 'active' COMMENT '用户状态', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - - INDEX idx_openid (openid), - INDEX idx_phone (phone), - INDEX idx_status (status), - INDEX idx_created_at (created_at) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户基础信息表'; -``` - -#### SQL 查询规范 -```sql --- ✅ 推荐的查询写法 -SELECT - u.id, - u.nickname, - u.avatar, - u.created_at -FROM users u -WHERE u.status = 'active' - AND u.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) -ORDER BY u.created_at DESC -LIMIT 20; - --- ❌ 不推荐的查询写法 -select * from users where status='active' order by created_at desc; -``` - -### 3. API 接口规范 - -#### RESTful API 设计 -```javascript -// ✅ 推荐的API设计 -GET /api/v1/users // 获取用户列表 -GET /api/v1/users/{id} // 获取单个用户 -POST /api/v1/users // 创建用户 -PUT /api/v1/users/{id} // 更新用户 -DELETE /api/v1/users/{id} // 删除用户 - -GET /api/v1/users/{id}/travels // 获取用户的旅行计划 -POST /api/v1/travels // 创建旅行计划 - -// ❌ 不推荐的API设计 -GET /api/getUserList -POST /api/createUser -GET /api/user_travels?userId=123 -``` - -#### 统一响应格式 -```json -{ - "code": 200, - "message": "success", - "data": { - "id": 1, - "nickname": "张三", - "avatar": "https://example.com/avatar.jpg" - }, - "timestamp": "2025-01-15T10:30:00Z" -} -``` - -#### 错误响应格式 -```json -{ - "code": 400, - "message": "参数错误", - "data": null, - "errors": [ - { - "field": "phone", - "message": "手机号格式不正确" - } - ], - "timestamp": "2025-01-15T10:30:00Z" -} -``` - -### 4. Git 工作流规范 - -#### 分支管理 -```bash -# 主要分支 -main # 生产环境分支 -develop # 开发环境分支 -release/* # 发布分支 -hotfix/* # 热修复分支 - -# 功能分支 -feature/user-management # 用户管理功能 -feature/travel-booking # 旅行预订功能 -feature/animal-adoption # 动物认领功能 - -# 修复分支 -bugfix/login-issue # 登录问题修复 -bugfix/payment-error # 支付错误修复 -``` - -#### 提交信息规范 -```bash -# 提交信息格式 -(): - - - -