# 解班客项目开发规范和最佳实践

## 📋 文档概述

本文档制定了解班客项目的开发规范、编码标准和最佳实践，旨在提高代码质量、团队协作效率和项目可维护性。

### 文档目标
- 建立统一的代码规范和编码标准
- 规范开发流程和团队协作方式
- 提高代码质量和可维护性
- 确保项目的长期稳定发展

## 🎯 开发原则

### 核心原则
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日