# 解班客项目开发规范和最佳实践 ## 📋 文档概述 本文档制定了解班客项目的开发规范、编码标准和最佳实践,旨在提高代码质量、团队协作效率和项目可维护性。 ### 文档目标 - 建立统一的代码规范和编码标准 - 规范开发流程和团队协作方式 - 提高代码质量和可维护性 - 确保项目的长期稳定发展 ## 🎯 开发原则 ### 核心原则 1. **可读性优先**:代码应该易于理解和维护 2. **一致性**:遵循统一的编码风格和命名规范 3. **简洁性**:避免过度设计,保持代码简洁 4. **可测试性**:编写易于测试的代码 5. **安全性**:始终考虑安全因素 6. **性能意识**:在保证功能的前提下优化性能 ### SOLID原则 - **S** - 单一职责原则(Single Responsibility Principle) - **O** - 开闭原则(Open/Closed Principle) - **L** - 里氏替换原则(Liskov Substitution Principle) - **I** - 接口隔离原则(Interface Segregation Principle) - **D** - 依赖倒置原则(Dependency Inversion Principle) ## 📁 项目结构规范 ### 后端项目结构 ``` backend/ ├── src/ │ ├── controllers/ # 控制器层 │ │ ├── admin/ # 管理员控制器 │ │ └── user/ # 用户控制器 │ ├── models/ # 数据模型层 │ ├── routes/ # 路由层 │ │ ├── admin/ # 管理员路由 │ │ └── user/ # 用户路由 │ ├── middleware/ # 中间件 │ ├── services/ # 业务逻辑层 │ ├── utils/ # 工具函数 │ ├── config/ # 配置文件 │ └── validators/ # 数据验证 ├── tests/ # 测试文件 │ ├── unit/ # 单元测试 │ ├── integration/ # 集成测试 │ └── fixtures/ # 测试数据 ├── docs/ # API文档 ├── scripts/ # 脚本文件 └── package.json ``` ### 前端项目结构 ``` frontend/ ├── src/ │ ├── components/ # 公共组件 │ │ ├── common/ # 通用组件 │ │ └── business/ # 业务组件 │ ├── views/ # 页面组件 │ │ ├── admin/ # 管理员页面 │ │ └── user/ # 用户页面 │ ├── stores/ # Pinia状态管理 │ ├── composables/ # 组合式函数 │ ├── utils/ # 工具函数 │ ├── api/ # API接口 │ ├── router/ # 路由配置 │ ├── assets/ # 静态资源 │ └── styles/ # 样式文件 ├── public/ # 公共资源 ├── tests/ # 测试文件 └── package.json ``` frontend/ ├── src/ │ ├── components/ # 公共组件 │ │ ├── common/ # 通用组件 │ │ └── business/ # 业务组件 │ ├── views/ # 页面视图 │ │ ├── user/ # 用户相关页面 │ │ ├── animal/ # 动物相关页面 │ │ └── admin/ # 管理页面 │ ├── stores/ # 状态管理 │ ├── router/ # 路由配置 │ ├── utils/ # 工具函数 │ ├── api/ # API接口 │ ├── assets/ # 静态资源 │ │ ├── images/ # 图片资源 │ │ ├── styles/ # 样式文件 │ │ └── icons/ # 图标资源 │ └── composables/ # 组合式函数 ├── public/ # 公共文件 ├── tests/ # 测试文件 └── package.json ``` ## 🔤 命名规范 ### 文件和目录命名 - **文件名**: 使用小写字母和连字符 (`kebab-case`) ``` ✅ user-management.js ✅ animal-list.vue ❌ UserManagement.js ❌ animalList.vue ``` - **目录名**: 使用小写字母和连字符 ``` ✅ user-management/ ✅ api-docs/ ❌ UserManagement/ ❌ apiDocs/ ``` ### 变量和函数命名 #### JavaScript/Node.js - **变量**: 使用驼峰命名法 (`camelCase`) - **常量**: 使用大写字母和下划线 (`UPPER_SNAKE_CASE`) - **函数**: 使用驼峰命名法,动词开头 - **类**: 使用帕斯卡命名法 (`PascalCase`) ```javascript // ✅ 正确示例 const userName = 'john'; const MAX_RETRY_COUNT = 3; const API_BASE_URL = 'https://api.example.com'; function getUserById(id) { } function createAnimalRecord(data) { } class UserService { } class AnimalController { } // ❌ 错误示例 const user_name = 'john'; const maxretrycount = 3; function GetUserById(id) { } class userService { } ``` #### Vue.js组件 - **组件名**: 使用帕斯卡命名法 - **Props**: 使用驼峰命名法 - **事件**: 使用kebab-case ```vue ``` ### 数据库命名 - **表名**: 使用复数形式,下划线分隔 - **字段名**: 使用下划线分隔 - **索引名**: 使用 `idx_` 前缀 - **外键名**: 使用 `fk_` 前缀 ```sql -- ✅ 正确示例 CREATE TABLE users ( id INT PRIMARY KEY, user_name VARCHAR(50), email_address VARCHAR(100), created_at TIMESTAMP, updated_at TIMESTAMP ); CREATE INDEX idx_users_email ON users(email_address); ALTER TABLE adoptions ADD CONSTRAINT fk_adoptions_user_id FOREIGN KEY (user_id) REFERENCES users(id); ``` ## 💻 代码风格规范 ### JavaScript/Node.js代码规范 #### 基本格式 ```javascript // ✅ 使用2个空格缩进 if (condition) { doSomething(); } // ✅ 使用单引号 const message = 'Hello World'; // ✅ 对象和数组的格式 const user = { name: 'John', age: 30, email: 'john@example.com' }; const animals = [ 'dog', 'cat', 'bird' ]; // ✅ 函数声明 function calculateAge(birthDate) { const today = new Date(); const birth = new Date(birthDate); return today.getFullYear() - birth.getFullYear(); } // ✅ 箭头函数 const getFullName = (firstName, lastName) => `${firstName} ${lastName}`; ``` #### 注释规范 ```javascript /** * 获取用户信息 * @param {number} userId - 用户ID * @param {Object} options - 查询选项 * @param {boolean} options.includeProfile - 是否包含个人资料 * @returns {Promise} 用户信息对象 * @throws {Error} 当用户不存在时抛出错误 */ async function getUserInfo(userId, options = {}) { // 验证用户ID if (!userId || typeof userId !== 'number') { throw new Error('Invalid user ID'); } // 查询用户基本信息 const user = await User.findById(userId); if (!user) { throw new Error('User not found'); } // 如果需要包含个人资料 if (options.includeProfile) { user.profile = await UserProfile.findByUserId(userId); } return user; } ``` #### 错误处理 ```javascript // ✅ 使用try-catch处理异步错误 async function createUser(userData) { try { // 验证输入数据 const validatedData = validateUserData(userData); // 创建用户 const user = await User.create(validatedData); // 记录日志 logger.info('User created successfully', { userId: user.id }); return user; } catch (error) { // 记录错误日志 logger.error('Failed to create user', { error: error.message, userData }); // 重新抛出错误 throw error; } } // ✅ 使用自定义错误类 class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } ``` ### Vue.js代码规范 #### 组件结构 ```vue ``` #### CSS/SCSS规范 ```scss // ✅ 使用BEM命名规范 .animal-card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; &__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } &__title { font-size: 18px; font-weight: 600; color: #333; } &__status { padding: 4px 8px; border-radius: 4px; font-size: 12px; &--available { background-color: #e8f5e8; color: #2d8f2d; } &--adopted { background-color: #fff3cd; color: #856404; } } &__content { margin-bottom: 16px; } &__actions { display: flex; gap: 8px; } } // ✅ 使用CSS变量 :root { --primary-color: #007bff; --success-color: #28a745; --warning-color: #ffc107; --danger-color: #dc3545; --font-family: 'Helvetica Neue', Arial, sans-serif; } ``` ## 🧪 测试规范 ### 测试文件命名 - 单元测试: `*.test.js` 或 `*.spec.js` - 集成测试: `*.integration.test.js` - E2E测试: `*.e2e.test.js` ### 测试结构 ```javascript // ✅ 测试文件示例 describe('UserService', () => { let userService; let mockDatabase; beforeEach(() => { mockDatabase = createMockDatabase(); userService = new UserService(mockDatabase); }); afterEach(() => { mockDatabase.reset(); }); describe('createUser', () => { it('should create user with valid data', async () => { // Arrange const userData = { name: 'John Doe', email: 'john@example.com' }; // Act const result = await userService.createUser(userData); // Assert expect(result).toBeDefined(); expect(result.id).toBeTruthy(); expect(result.name).toBe(userData.name); expect(result.email).toBe(userData.email); }); it('should throw error with invalid email', async () => { // Arrange const userData = { name: 'John Doe', email: 'invalid-email' }; // Act & Assert await expect(userService.createUser(userData)) .rejects .toThrow('Invalid email format'); }); }); }); ``` ### 测试覆盖率要求 - **单元测试覆盖率**: ≥ 80% - **集成测试覆盖率**: ≥ 60% - **关键业务逻辑**: 100% ## 📝 文档规范 ### API文档 使用OpenAPI 3.0规范编写API文档: ```yaml # ✅ API文档示例 paths: /api/v1/users/{id}: get: summary: 获取用户信息 description: 根据用户ID获取用户详细信息 parameters: - name: id in: path required: true schema: type: integer description: 用户ID responses: '200': description: 成功获取用户信息 content: application/json: schema: $ref: '#/components/schemas/User' '404': description: 用户不存在 content: application/json: schema: $ref: '#/components/schemas/Error' ``` ### 代码注释 ```javascript /** * 动物认领服务类 * 处理动物认领相关的业务逻辑 */ class AdoptionService { /** * 创建认领申请 * @param {Object} adoptionData - 认领申请数据 * @param {number} adoptionData.userId - 申请人ID * @param {number} adoptionData.animalId - 动物ID * @param {string} adoptionData.reason - 认领原因 * @param {Object} adoptionData.contact - 联系方式 * @returns {Promise} 认领申请对象 * @throws {ValidationError} 当数据验证失败时 * @throws {BusinessError} 当业务规则验证失败时 * * @example * const adoption = await adoptionService.createAdoption({ * userId: 123, * animalId: 456, * reason: '我想给这只小狗一个温暖的家', * contact: { phone: '13800138000', address: '北京市朝阳区' } * }); */ async createAdoption(adoptionData) { // 实现代码... } } ``` ## 🔒 安全规范 ### 输入验证 ```javascript // ✅ 使用joi进行数据验证 const Joi = require('joi'); const userSchema = Joi.object({ name: Joi.string().min(2).max(50).required(), email: Joi.string().email().required(), password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required(), phone: Joi.string().pattern(/^1[3-9]\d{9}$/).optional() }); // 验证用户输入 const { error, value } = userSchema.validate(userData); if (error) { throw new ValidationError(error.details[0].message); } ``` ### SQL注入防护 ```javascript // ✅ 使用参数化查询 const getUserById = async (id) => { const query = 'SELECT * FROM users WHERE id = ?'; const result = await db.query(query, [id]); return result[0]; }; // ❌ 避免字符串拼接 const getUserById = async (id) => { const query = `SELECT * FROM users WHERE id = ${id}`; // 危险! const result = await db.query(query); return result[0]; }; ``` ### 敏感信息处理 ```javascript // ✅ 密码加密 const bcrypt = require('bcrypt'); const hashPassword = async (password) => { const saltRounds = 12; return await bcrypt.hash(password, saltRounds); }; // ✅ 敏感信息过滤 const sanitizeUser = (user) => { const { password, salt, ...safeUser } = user; return safeUser; }; ``` ## 🚀 性能优化规范 ### 数据库查询优化 ```javascript // ✅ 使用索引和限制查询 const getAnimals = async (filters, pagination) => { const { page = 1, limit = 20 } = pagination; const offset = (page - 1) * limit; const query = ` SELECT a.*, u.name as owner_name FROM animals a LEFT JOIN users u ON a.owner_id = u.id WHERE a.status = ? ORDER BY a.created_at DESC LIMIT ? OFFSET ? `; return await db.query(query, [filters.status, limit, offset]); }; // ✅ 使用缓存 const Redis = require('redis'); const redis = Redis.createClient(); const getCachedUser = async (userId) => { const cacheKey = `user:${userId}`; // 尝试从缓存获取 let user = await redis.get(cacheKey); if (user) { return JSON.parse(user); } // 从数据库获取 user = await User.findById(userId); // 存入缓存,过期时间1小时 await redis.setex(cacheKey, 3600, JSON.stringify(user)); return user; }; ``` ### 前端性能优化 ```vue ``` ## 📋 Git工作流规范 ### 分支命名 - **主分支**: `main` - **开发分支**: `develop` - **功能分支**: `feature/功能名称` - **修复分支**: `fix/问题描述` - **发布分支**: `release/版本号` ### 提交信息规范 使用Conventional Commits规范: ```bash # ✅ 正确的提交信息 feat: 添加用户认证功能 fix: 修复动物列表分页问题 docs: 更新API文档 style: 统一代码格式 refactor: 重构用户服务层 test: 添加用户注册测试用例 chore: 更新依赖包版本 # 详细提交信息示例 feat: 添加动物认领申请功能 - 实现认领申请表单 - 添加申请状态跟踪 - 集成邮件通知功能 - 添加相关测试用例 Closes #123 ``` ### 代码审查清单 - [ ] 代码符合项目规范 - [ ] 功能实现正确 - [ ] 测试用例充分 - [ ] 文档更新完整 - [ ] 性能影响评估 - [ ] 安全风险评估 - [ ] 向后兼容性检查 ## 🛠️ 开发工具配置 ### ESLint配置 ```json { "extends": [ "eslint:recommended", "@vue/eslint-config-prettier" ], "rules": { "indent": ["error", 2], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": "warn", "no-debugger": "error", "no-unused-vars": "error" } } ``` ### Prettier配置 ```json { "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "printWidth": 80, "bracketSpacing": true, "arrowParens": "avoid" } ``` ### VS Code配置 ```json { "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "emmet.includeLanguages": { "vue": "html" }, "files.associations": { "*.vue": "vue" } } ``` ## 📚 学习资源 ### 官方文档 - [Vue.js 官方文档](https://vuejs.org/) - [Node.js 官方文档](https://nodejs.org/) - [Express.js 官方文档](https://expressjs.com/) - [MySQL 官方文档](https://dev.mysql.com/doc/) ### 最佳实践 - [JavaScript 最佳实践](https://github.com/airbnb/javascript) - [Vue.js 风格指南](https://vuejs.org/style-guide/) - [Node.js 最佳实践](https://github.com/goldbergyoni/nodebestpractices) ### 工具和库 - [ESLint](https://eslint.org/) - 代码检查 - [Prettier](https://prettier.io/) - 代码格式化 - [Jest](https://jestjs.io/) - 测试框架 - [Joi](https://joi.dev/) - 数据验证 ## 🔄 规范更新 本规范会根据项目发展和团队反馈持续更新。如有建议或问题,请通过以下方式反馈: 1. 创建GitHub Issue 2. 提交Pull Request 3. 团队会议讨论 --- **文档版本**: v1.0.0 **最后更新**: 2024年1月15日 **下次审查**: 2024年4月15日