mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
f8dcf6e3d3
BREAKING CHANGE: apps/backend/requirements.txt is no longer the backend dependency source. Use uv sync and uv run python main.py for backend setup and startup.
391 lines
8.1 KiB
Markdown
391 lines
8.1 KiB
Markdown
# 开发指南
|
|
|
|
## 开发环境设置
|
|
|
|
### 后端开发
|
|
|
|
```bash
|
|
# 克隆项目
|
|
git clone <repository>
|
|
cd CheckInApp
|
|
|
|
# 安装后端依赖
|
|
uv sync
|
|
|
|
uv run python main.py backend
|
|
```
|
|
|
|
### 前端开发
|
|
|
|
```bash
|
|
cd apps/frontend
|
|
|
|
# 安装依赖
|
|
npm install
|
|
|
|
# 启动开发服务器
|
|
npm run dev
|
|
|
|
# 代码格式化
|
|
npm run lint
|
|
npm run format
|
|
```
|
|
|
|
## 项目结构
|
|
|
|
### 后端目录说明
|
|
|
|
```
|
|
apps/backend/
|
|
├── main.py # FastAPI 应用入口,CORS、路由注册
|
|
├── config.py # Pydantic Settings 配置
|
|
├── dependencies.py # 依赖注入(认证、权限)
|
|
├── exceptions.py # 自定义异常类
|
|
│
|
|
├── models/ # SQLAlchemy 数据模型
|
|
│ ├── database.py # 数据库连接、Session
|
|
│ ├── user.py
|
|
│ ├── check_in_task.py
|
|
│ ├── check_in_record.py
|
|
│ └── task_template.py
|
|
│
|
|
├── schemas/ # Pydantic 请求响应模型
|
|
│ ├── user.py
|
|
│ ├── task.py
|
|
│ ├── auth.py
|
|
│ └── response.py # 标准化响应
|
|
│
|
|
├── api/ # API 路由(按模块划分)
|
|
│ ├── auth.py # 认证相关
|
|
│ ├── users.py # 用户管理
|
|
│ ├── tasks.py # 任务管理
|
|
│ ├── check_in.py # 打卡功能
|
|
│ ├── admin.py # 管理员功能
|
|
│ └── templates.py # 模板管理
|
|
│
|
|
├── services/ # 业务逻辑层
|
|
│ ├── auth_service.py
|
|
│ ├── user_service.py
|
|
│ ├── task_service.py
|
|
│ ├── check_in_service.py
|
|
│ ├── scheduler_service.py
|
|
│ ├── template_service.py
|
|
│ └── registration_manager.py
|
|
│
|
|
└── workers/ # Selenium 自动化
|
|
├── token_refresher.py # QQ 登录
|
|
├── check_in_worker.py # 打卡执行
|
|
└── email_notifier.py # 邮件发送
|
|
```
|
|
|
|
### 前端目录说明
|
|
|
|
```
|
|
apps/frontend/src/
|
|
├── main.js # Vue 应用入口
|
|
├── App.vue # 根组件
|
|
│
|
|
├── api/ # API 封装
|
|
│ ├── client.js # Axios 实例、拦截器
|
|
│ └── index.js # API 模块(auth, user, task 等)
|
|
│
|
|
├── views/ # 页面组件
|
|
│ ├── LoginView.vue
|
|
│ ├── DashboardView.vue
|
|
│ ├── TasksView.vue
|
|
│ ├── RecordsView.vue
|
|
│ └── admin/ # 管理员页面
|
|
│
|
|
├── components/ # 可复用组件
|
|
│ ├── TaskCard.vue
|
|
│ ├── RecordTable.vue
|
|
│ └── ...
|
|
│
|
|
├── stores/ # Pinia 状态管理
|
|
│ ├── auth.js # 认证状态
|
|
│ ├── user.js # 用户信息
|
|
│ └── task.js # 任务列表
|
|
│
|
|
├── router/ # Vue Router
|
|
│ └── index.js # 路由配置、导航守卫
|
|
│
|
|
└── composables/ # 组合式函数
|
|
├── useAuth.js
|
|
└── useTask.js
|
|
```
|
|
|
|
## 开发流程
|
|
|
|
### 添加新功能
|
|
|
|
#### 1. 后端 API 开发
|
|
|
|
**步骤**:
|
|
|
|
1. 在 `models/` 定义数据模型
|
|
2. 在 `schemas/` 定义请求响应模型
|
|
3. 在 `services/` 实现业务逻辑
|
|
4. 在 `api/` 创建路由端点
|
|
5. 在 `main.py` 注册路由
|
|
|
|
**示例**: 添加一个新的"任务标签"功能
|
|
|
|
```python
|
|
# models/task_tag.py
|
|
class TaskTag(Base):
|
|
__tablename__ = "task_tags"
|
|
id = Column(Integer, primary_key=True)
|
|
task_id = Column(Integer, ForeignKey("check_in_tasks.id"))
|
|
tag_name = Column(String(50))
|
|
|
|
# schemas/tag.py
|
|
class TaskTagCreate(BaseModel):
|
|
tag_name: str
|
|
|
|
# services/tag_service.py
|
|
def create_tag(db: Session, task_id: int, tag_data: TaskTagCreate):
|
|
tag = TaskTag(task_id=task_id, tag_name=tag_data.tag_name)
|
|
db.add(tag)
|
|
db.commit()
|
|
return tag
|
|
|
|
# api/tags.py
|
|
@router.post("/tasks/{task_id}/tags")
|
|
def add_tag(task_id: int, tag: TaskTagCreate, db: Session = Depends(get_db)):
|
|
return tag_service.create_tag(db, task_id, tag)
|
|
|
|
# apps/backend/main.py
|
|
from backend.api import tags
|
|
app.include_router(tags.router, prefix="/api")
|
|
```
|
|
|
|
#### 2. 前端开发
|
|
|
|
**步骤**:
|
|
|
|
1. 在 `api/index.js` 添加 API 调用
|
|
2. 在 `stores/` 创建或更新状态
|
|
3. 创建或更新 Vue 组件
|
|
4. 配置路由(如需要)
|
|
|
|
**示例**: 添加标签管理页面
|
|
|
|
```javascript
|
|
// api/index.js
|
|
export const tagApi = {
|
|
addTag: (taskId, data) => client.post(`/tasks/${taskId}/tags`, data),
|
|
getTags: (taskId) => client.get(`/tasks/${taskId}/tags`)
|
|
}
|
|
|
|
// stores/tag.js
|
|
export const useTagStore = defineStore('tag', {
|
|
state: () => ({
|
|
tags: []
|
|
}),
|
|
actions: {
|
|
async fetchTags(taskId) {
|
|
const { data } = await tagApi.getTags(taskId)
|
|
this.tags = data
|
|
}
|
|
}
|
|
})
|
|
|
|
// components/TagManager.vue
|
|
<template>
|
|
<div>
|
|
<a-tag v-for="tag in tags" :key="tag.id">{{ tag.tag_name }}</a-tag>
|
|
<a-button @click="addTag">添加标签</a-button>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### 数据库迁移
|
|
|
|
```bash
|
|
# 修改模型后生成迁移脚本
|
|
# 手动创建脚本在 apps/backend/scripts/migrate_*.py
|
|
|
|
# 执行迁移
|
|
uv run python apps/backend/scripts/migrate_xxx.py
|
|
```
|
|
|
|
### 测试
|
|
|
|
#### 后端测试
|
|
|
|
```python
|
|
# tests/test_task_service.py
|
|
import pytest
|
|
from backend.services import task_service
|
|
|
|
def test_create_task():
|
|
task = task_service.create_task(db, user_id=1, task_data)
|
|
assert task.name == "Test Task"
|
|
assert task.is_active == True
|
|
|
|
# 运行测试
|
|
uv run pytest tests/
|
|
```
|
|
|
|
#### 前端测试
|
|
|
|
```javascript
|
|
// tests/TaskCard.spec.js
|
|
import { mount } from '@vue/test-utils'
|
|
import TaskCard from '@/components/TaskCard.vue'
|
|
|
|
test('displays task name', () => {
|
|
const wrapper = mount(TaskCard, {
|
|
props: { task: { name: 'Test Task' } }
|
|
})
|
|
expect(wrapper.text()).toContain('Test Task')
|
|
})
|
|
|
|
// 运行测试
|
|
npm run test
|
|
```
|
|
|
|
## 代码规范
|
|
|
|
### 后端规范
|
|
|
|
- 使用 Black 格式化: `uv run black apps/backend/`
|
|
- 遵循 PEP 8
|
|
- 函数添加类型注解
|
|
- API 路由使用 Pydantic 模型验证
|
|
- 使用 dependency injection
|
|
|
|
```python
|
|
# 好的示例
|
|
async def create_task(
|
|
task_data: TaskCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
) -> TaskResponse:
|
|
"""创建新任务"""
|
|
return task_service.create_task(db, current_user.id, task_data)
|
|
```
|
|
|
|
### 前端规范
|
|
|
|
- 使用 ESLint + Prettier
|
|
- 组件使用 Composition API
|
|
- 使用 `<script setup>` 语法
|
|
- Props 添加类型验证
|
|
- 统一使用 Ant Design Vue 组件
|
|
|
|
```vue
|
|
<!-- 好的示例 -->
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
task: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const isActive = computed(() => props.task.is_active)
|
|
</script>
|
|
```
|
|
|
|
## 调试技巧
|
|
|
|
### 后端调试
|
|
|
|
```python
|
|
# 使用 logging
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(f"Creating task for user {user_id}")
|
|
|
|
# 使用 pdb
|
|
import pdb; pdb.set_trace()
|
|
|
|
# 查看 SQL 查询
|
|
import logging
|
|
logging.basicConfig()
|
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
|
```
|
|
|
|
### 前端调试
|
|
|
|
```javascript
|
|
// 使用 Vue Devtools
|
|
// Chrome 扩展安装
|
|
|
|
// console 输出
|
|
console.log('Task data:', task.value)
|
|
|
|
// 调试 API 请求
|
|
axios.interceptors.request.use(request => {
|
|
console.log('Request:', request)
|
|
return request
|
|
})
|
|
```
|
|
|
|
## 常见问题
|
|
|
|
### CORS 错误
|
|
|
|
在 `.env` 添加前端地址:
|
|
|
|
```env
|
|
CORS_ORIGINS=http://localhost:3000
|
|
```
|
|
|
|
### 数据库锁定
|
|
|
|
使用 connection pool:
|
|
|
|
```python
|
|
engine = create_engine(
|
|
DATABASE_URL,
|
|
pool_pre_ping=True,
|
|
pool_size=10
|
|
)
|
|
```
|
|
|
|
### Selenium 超时
|
|
|
|
增加等待时间:
|
|
|
|
```python
|
|
WebDriverWait(driver, 30).until(...)
|
|
```
|
|
|
|
## Git 工作流
|
|
|
|
```bash
|
|
# 创建功能分支
|
|
git checkout -b feature/task-tags
|
|
|
|
# 提交代码
|
|
git add .
|
|
git commit -m "feat: add task tag functionality"
|
|
|
|
# 合并到主分支
|
|
git checkout main
|
|
git merge feature/task-tags
|
|
|
|
# 推送
|
|
git push origin main
|
|
```
|
|
|
|
## 性能优化
|
|
|
|
### 后端优化
|
|
|
|
- 使用 SQLAlchemy 的 `joinedload` 避免 N+1 查询
|
|
- 添加数据库索引
|
|
- 使用异步任务处理耗时操作
|
|
- 启用 gzip 压缩
|
|
|
|
### 前端优化
|
|
|
|
- 使用虚拟滚动处理大列表
|
|
- 路由懒加载
|
|
- 图片懒加载
|
|
- 使用 `keep-alive` 缓存组件
|