862 lines
20 KiB
Markdown
862 lines
20 KiB
Markdown
|
|
# 解班客项目开发规范和最佳实践
|
|||
|
|
|
|||
|
|
## 📋 文档概述
|
|||
|
|
|
|||
|
|
本文档制定了解班客项目的开发规范、编码标准和最佳实践,旨在提高代码质量、团队协作效率和项目可维护性。
|
|||
|
|
|
|||
|
|
### 文档目标
|
|||
|
|
- 建立统一的代码规范和编码标准
|
|||
|
|
- 规范开发流程和团队协作方式
|
|||
|
|
- 提高代码质量和可维护性
|
|||
|
|
- 确保项目的长期稳定发展
|
|||
|
|
|
|||
|
|
## 🎯 开发原则
|
|||
|
|
|
|||
|
|
### 核心原则
|
|||
|
|
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
|
|||
|
|
<!-- ✅ 正确示例 -->
|
|||
|
|
<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_` 前缀
|
|||
|
|
|
|||
|
|
```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<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;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 错误处理
|
|||
|
|
```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
|
|||
|
|
<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规范
|
|||
|
|
```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<Object>} 认领申请对象
|
|||
|
|
* @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
|
|||
|
|
<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规范:
|
|||
|
|
|
|||
|
|
```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日
|