20 KiB
解班客项目开发规范和最佳实践
📋 文档概述
本文档制定了解班客项目的开发规范、编码标准和最佳实践,旨在提高代码质量、团队协作效率和项目可维护性。
文档目标
- 建立统一的代码规范和编码标准
- 规范开发流程和团队协作方式
- 提高代码质量和可维护性
- 确保项目的长期稳定发展
🎯 开发原则
核心原则
- 可读性优先:代码应该易于理解和维护
- 一致性:遵循统一的编码风格和命名规范
- 简洁性:避免过度设计,保持代码简洁
- 可测试性:编写易于测试的代码
- 安全性:始终考虑安全因素
- 性能意识:在保证功能的前提下优化性能
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
<!-- ✅ 正确示例 -->
<template>
<UserProfile
:user-data="userData"
@user-updated="handleUserUpdate"
/>
</template>
<script>
export default {
name: 'UserProfile',
props: {
userData: Object,
isEditable: Boolean
},
emits: ['user-updated', 'profile-changed']
}
</script>
数据库命名
- 表名: 使用复数形式,下划线分隔
- 字段名: 使用下划线分隔
- 索引名: 使用
idx_前缀 - 外键名: 使用
fk_前缀
-- ✅ 正确示例
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代码规范
基本格式
// ✅ 使用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}`;
注释规范
/**
* 获取用户信息
* @param {number} userId - 用户ID
* @param {Object} options - 查询选项
* @param {boolean} options.includeProfile - 是否包含个人资料
* @returns {Promise<Object>} 用户信息对象
* @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;
}
错误处理
// ✅ 使用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代码规范
组件结构
<template>
<!-- 模板内容 -->
<div class="user-profile">
<div class="user-profile__header">
<h2 class="user-profile__title">{{ user.name }}</h2>
</div>
<div class="user-profile__content">
<UserAvatar
:src="user.avatar"
:alt="user.name"
@click="handleAvatarClick"
/>
</div>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';
import UserAvatar from '@/components/common/UserAvatar.vue';
export default {
name: 'UserProfile',
components: {
UserAvatar
},
props: {
userId: {
type: Number,
required: true
},
editable: {
type: Boolean,
default: false
}
},
emits: ['profile-updated', 'avatar-changed'],
setup(props, { emit }) {
// 响应式数据
const user = ref(null);
const loading = ref(false);
// 计算属性
const displayName = computed(() => {
return user.value ? user.value.name : 'Unknown User';
});
// 方法
const loadUser = async () => {
loading.value = true;
try {
user.value = await userStore.fetchUser(props.userId);
} catch (error) {
console.error('Failed to load user:', error);
} finally {
loading.value = false;
}
};
const handleAvatarClick = () => {
if (props.editable) {
emit('avatar-changed');
}
};
// 生命周期
onMounted(() => {
loadUser();
});
// 返回模板需要的数据和方法
return {
user,
loading,
displayName,
handleAvatarClick
};
}
};
</script>
<style lang="scss" scoped>
.user-profile {
padding: 20px;
&__header {
margin-bottom: 16px;
}
&__title {
font-size: 24px;
font-weight: 600;
color: #333;
}
&__content {
display: flex;
align-items: center;
}
}
</style>
CSS/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
测试结构
// ✅ 测试文件示例
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文档:
# ✅ 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'
代码注释
/**
* 动物认领服务类
* 处理动物认领相关的业务逻辑
*/
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<Object>} 认领申请对象
* @throws {ValidationError} 当数据验证失败时
* @throws {BusinessError} 当业务规则验证失败时
*
* @example
* const adoption = await adoptionService.createAdoption({
* userId: 123,
* animalId: 456,
* reason: '我想给这只小狗一个温暖的家',
* contact: { phone: '13800138000', address: '北京市朝阳区' }
* });
*/
async createAdoption(adoptionData) {
// 实现代码...
}
}
🔒 安全规范
输入验证
// ✅ 使用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注入防护
// ✅ 使用参数化查询
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];
};
敏感信息处理
// ✅ 密码加密
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;
};
🚀 性能优化规范
数据库查询优化
// ✅ 使用索引和限制查询
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;
};
前端性能优化
<template>
<!-- ✅ 使用v-show代替v-if进行频繁切换 -->
<div v-show="isVisible" class="content">
<!-- ✅ 使用key优化列表渲染 -->
<div
v-for="animal in animals"
:key="animal.id"
class="animal-item"
>
{{ animal.name }}
</div>
</div>
</template>
<script>
import { ref, computed, watchEffect } from 'vue';
export default {
setup() {
// ✅ 使用computed缓存计算结果
const expensiveValue = computed(() => {
return animals.value.filter(animal => animal.status === 'available')
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
});
// ✅ 使用防抖处理搜索
const searchTerm = ref('');
const debouncedSearch = debounce((term) => {
performSearch(term);
}, 300);
watchEffect(() => {
debouncedSearch(searchTerm.value);
});
return {
expensiveValue,
searchTerm
};
}
};
</script>
📋 Git工作流规范
分支命名
- 主分支:
main - 开发分支:
develop - 功能分支:
feature/功能名称 - 修复分支:
fix/问题描述 - 发布分支:
release/版本号
提交信息规范
使用Conventional Commits规范:
# ✅ 正确的提交信息
feat: 添加用户认证功能
fix: 修复动物列表分页问题
docs: 更新API文档
style: 统一代码格式
refactor: 重构用户服务层
test: 添加用户注册测试用例
chore: 更新依赖包版本
# 详细提交信息示例
feat: 添加动物认领申请功能
- 实现认领申请表单
- 添加申请状态跟踪
- 集成邮件通知功能
- 添加相关测试用例
Closes #123
代码审查清单
- 代码符合项目规范
- 功能实现正确
- 测试用例充分
- 文档更新完整
- 性能影响评估
- 安全风险评估
- 向后兼容性检查
🛠️ 开发工具配置
ESLint配置
{
"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配置
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"bracketSpacing": true,
"arrowParens": "avoid"
}
VS Code配置
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"emmet.includeLanguages": {
"vue": "html"
},
"files.associations": {
"*.vue": "vue"
}
}
📚 学习资源
官方文档
最佳实践
工具和库
🔄 规范更新
本规范会根据项目发展和团队反馈持续更新。如有建议或问题,请通过以下方式反馈:
- 创建GitHub Issue
- 提交Pull Request
- 团队会议讨论
文档版本: v1.0.0
最后更新: 2024年1月15日
下次审查: 2024年4月15日