feat(feedback): implement user feedback collection system with email reporting

Backend:
- Add user_feedback table with RLS policy
- Create feedback submission API (multipart/form-data)
- Implement xlsx report generation with embedded images
- Add scheduled email delivery via Feishu SMTP
- Create HTML email templates (daily_report, no_feedback)

Frontend:
- Add feedback screen with type selection and image picker
- Support anonymous submission via skipAuth flag
- Collect device info and app version

Protocol:
- Document feedback API contract and error codes
- Update http-error-codes.md with FEEDBACK_* codes
This commit is contained in:
qzl
2026-04-20 12:49:54 +08:00
parent 913ed26f8d
commit 6a2a9d2c87
46 changed files with 4768 additions and 9 deletions
@@ -0,0 +1,22 @@
{"check": "dependency_installed", "description": "验证 openpyxl 已安装", "command": "uv pip show openpyxl"}
{"check": "migration_file_exists", "description": "验证迁移文件已创建", "command": "ls backend/alembic/versions/*create_user_feedback.py"}
{"check": "schema_file_exists", "description": "验证 Schema 文件已创建", "command": "test -f backend/src/v1/feedback/schemas.py"}
{"check": "schema_strict_validation", "description": "验证 Schema 使用 extra=forbid 强约束", "command": "grep -q 'extra.*forbid' backend/src/v1/feedback/schemas.py"}
{"check": "model_file_exists", "description": "验证模型文件已创建", "command": "test -f backend/src/models/feedback.py"}
{"check": "repository_file_exists", "description": "验证 Repository 文件已创建", "command": "test -f backend/src/v1/feedback/repository.py"}
{"check": "service_file_exists", "description": "验证 Service 文件已创建", "command": "test -f backend/src/v1/feedback/service.py"}
{"check": "router_file_exists", "description": "验证路由文件已创建", "command": "test -f backend/src/v1/feedback/router.py"}
{"check": "router_registered", "description": "验证路由已注册", "command": "grep -q 'feedback' backend/src/v1/router.py"}
{"check": "error_codes_added", "description": "验证错误码已添加", "command": "grep -q 'FEEDBACK_' docs/protocols/common/http-error-codes.md"}
{"check": "frontend_model_exists", "description": "验证前端模型已创建", "command": "test -f apps/lib/features/settings/data/models/feedback.dart"}
{"check": "frontend_api_exists", "description": "验证前端 API 已创建", "command": "test -f apps/lib/features/settings/data/apis/feedback_api.dart"}
{"check": "frontend_screen_exists", "description": "验证前端反馈页已创建", "command": "test -f apps/lib/features/settings/presentation/screens/feedback_screen.dart"}
{"check": "l10n_zh_added", "description": "验证中文翻译已添加", "command": "grep -q 'settingsFeedbackTitle' apps/lib/l10n/app_zh.arb"}
{"check": "l10n_en_added", "description": "验证英文翻译已添加", "command": "grep -q 'settingsFeedbackTitle' apps/lib/l10n/app_en.arb"}
{"check": "database_table_exists", "description": "验证数据库表已创建", "command": "psql -c '\\d user_feedback'"}
{"check": "api_submit_works", "description": "测试反馈提交 API(匿名用户)", "command": "curl -X POST http://localhost:8000/api/v1/feedback -H 'Content-Type: application/json' -d '{\"feedback_type\":\"bug\",\"content\":\"test\",\"images\":[],\"device_info\":{\"platform\":\"ios\",\"model\":\"test\"},\"app_version\":\"1.0\",\"os_version\":\"iOS 17\"}'"}
{"check": "api_validation_content_empty", "description": "测试 API 参数验证(内容为空)", "command": "curl -X POST http://localhost:8000/api/v1/feedback -H 'Content-Type: application/json' -d '{\"feedback_type\":\"bug\",\"content\":\"\",\"images\":[],\"device_info\":{\"platform\":\"ios\",\"model\":\"test\"},\"app_version\":\"1.0\",\"os_version\":\"iOS 17\"}' | grep -q 'FEEDBACK_CONTENT_EMPTY'"}
{"check": "api_validation_too_many_images", "description": "测试 API 参数验证(图片超限)", "command": "curl -X POST http://localhost:8000/api/v1/feedback -H 'Content-Type: application/json' -d '{\"feedback_type\":\"bug\",\"content\":\"test\",\"images\":[\"url1\",\"url2\",\"url3\",\"url4\"],\"device_info\":{\"platform\":\"ios\",\"model\":\"test\"},\"app_version\":\"1.0\",\"os_version\":\"iOS 17\"}' | grep -q 'FEEDBACK_TOO_MANY_IMAGES'"}
{"check": "frontend_builds", "description": "验证前端编译通过", "command": "cd apps && flutter analyze"}
{"check": "backend_typecheck", "description": "验证后端类型检查通过", "command": "uv run basedpyright backend/src/v1/feedback/"}
{"check": "backend_lint", "description": "验证后端 lint 通过", "command": "uv run ruff check backend/src/v1/feedback/"}
@@ -0,0 +1,20 @@
{"step": 1, "action": "add_dependency", "description": "添加 openpyxl 依赖", "command": "uv add openpyxl", "files": ["pyproject.toml"]}
{"step": 2, "action": "create_migration", "description": "创建 user_feedback 数据库表迁移", "files": ["backend/alembic/versions/20260417_1_create_user_feedback.py"]}
{"step": 3, "action": "create_storage_bucket", "description": "创建 Supabase Storage feedback bucket", "files": ["supabase/storage/feedback"]}
{"step": 4, "action": "create_schemas", "description": "创建 Pydantic Schema(强约束,extra=forbid", "files": ["backend/src/v1/feedback/schemas.py"]}
{"step": 5, "action": "create_model", "description": "创建 Feedback 数据库模型", "files": ["backend/src/models/feedback.py"]}
{"step": 6, "action": "create_repository", "description": "创建 FeedbackRepositoryCRUD 操作)", "files": ["backend/src/v1/feedback/repository.py"]}
{"step": 7, "action": "create_service", "description": "创建 FeedbackService(业务逻辑)", "files": ["backend/src/v1/feedback/service.py"]}
{"step": 8, "action": "create_router", "description": "创建 POST /api/v1/feedback 路由", "files": ["backend/src/v1/feedback/router.py"]}
{"step": 9, "action": "register_router", "description": "注册 feedback router 到主路由", "files": ["backend/src/v1/router.py"]}
{"step": 10, "action": "add_error_codes", "description": "添加反馈相关错误码到协议文档", "files": ["docs/protocols/common/http-error-codes.md"]}
{"step": 11, "action": "create_frontend_model", "description": "创建前端 Feedback 数据模型", "files": ["apps/lib/features/settings/data/models/feedback.dart"]}
{"step": 12, "action": "create_frontend_api", "description": "创建前端 FeedbackApi", "files": ["apps/lib/features/settings/data/apis/feedback_api.dart"]}
{"step": 13, "action": "create_frontend_repository", "description": "创建前端 FeedbackRepository", "files": ["apps/lib/features/settings/data/repositories/feedback_repository.dart"]}
{"step": 14, "action": "create_feedback_screen", "description": "创建反馈表单页(含图片上传组件)", "files": ["apps/lib/features/settings/presentation/screens/feedback_screen.dart"]}
{"step": 15, "action": "add_feedback_entry", "description": "在 SettingsScreen 添加反馈入口", "files": ["apps/lib/features/settings/presentation/screens/settings_screen.dart"]}
{"step": 16, "action": "add_l10n_zh", "description": "添加中文翻译", "files": ["apps/lib/l10n/app_zh.arb"]}
{"step": 17, "action": "add_l10n_en", "description": "添加英文翻译", "files": ["apps/lib/l10n/app_en.arb"]}
{"step": 18, "action": "add_l10n_zh_hant", "description": "添加繁体中文翻译", "files": ["apps/lib/l10n/app_zh_hant.arb"]}
{"step": 19, "action": "run_migration", "description": "执行数据库迁移", "command": "./infra/scripts/dev-migrate.sh migrate"}
{"step": 20, "action": "run_l10n_gen", "description": "生成国际化代码", "command": "cd apps && flutter gen-l10n"}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
{
"name": "feat-user-feedback",
"title": "用户反馈投送功能",
"type": "feature",
"priority": "medium",
"dev_type": "fullstack",
"created_at": "2026-04-17",
"status": "planning",
"worktree": "feat-user-feedback",
"branch": "worktree/feat-user-feedback",
"description": "实现用户反馈投送功能:前端反馈表单 + 后端数据存储 + 图片上传。Phase 2 实现定时报告生成和邮件推送。",
"prd": "prd.md",
"phase": {
"current": 1,
"total": 3,
"description": "Phase 1: 前端反馈 + 后端存储 + 图片上传"
},
"tech_stack": {
"backend": {
"language": "Python",
"framework": "FastAPI",
"orm": "SQLAlchemy",
"task_queue": "Taskiq",
"xlsx_lib": "openpyxl"
},
"frontend": {
"language": "Dart",
"framework": "Flutter",
"storage": "Supabase Storage"
}
},
"features": {
"anonymous_feedback": true,
"image_upload": true,
"max_images": 3,
"max_content_length": 500,
"report_generation": false,
"email_notification": false
},
"constraints": {
"schema_strict": "所有字段使用 Pydantic Schema 强约束,extra=forbid",
"no_contact_email": "删除 contact_email 字段,不存储用户联系方式",
"no_admin_routes": "暂不实现管理员路由",
"config_via_settings": "客服邮箱和推送时间通过 Settings 环境变量配置"
},
"checklist": {
"backend": [
"添加 openpyxl 依赖",
"创建 user_feedback 表迁移(含 images JSONB 字段)",
"创建 Supabase Storage feedback bucket",
"创建 Pydantic Schema(强约束,extra=forbid",
"创建 Feedback 模型",
"创建 FeedbackRepositoryCRUD 操作)",
"创建 FeedbackService(业务逻辑)",
"创建 POST /api/v1/feedback 接口",
"添加错误码到协议文档"
],
"frontend": [
"创建 Feedback 数据模型",
"创建 FeedbackApi",
"创建 FeedbackRepository",
"创建 FeedbackScreen(含图片上传组件)",
"在 SettingsScreen 添加反馈入口",
"添加 l10n keys(中/英/繁)"
]
},
"notes": [
"Phase 1 只实现前端反馈 + 后端存储 + 图片上传",
"Phase 2 实现定时报告生成和邮件推送",
"使用 Python openpyxl 生成 xlsx 报告(技术栈统一)",
"Schema 使用 extra=forbid 禁止模糊字段",
"客服邮箱通过 FEEDBACK_REPORT_EMAIL 环境变量配置",
"推送时间通过 FEEDBACK_REPORT_CRON 环境变量配置"
]
}