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
+25
View File
@@ -153,8 +153,13 @@ class StorageSettings(BaseModel):
bucket: str = Field(default="avatars", min_length=3, max_length=63)
max_size_mb: int = Field(default=2, ge=1, le=10)
class FeedbackSettings(BaseModel):
bucket: str = Field(default="feedback-images", min_length=3, max_length=63)
max_size_mb: int = Field(default=5, ge=1, le=20)
attachment: AttachmentSettings = Field(default_factory=AttachmentSettings)
avatar: AvatarSettings = Field(default_factory=AvatarSettings)
feedback: FeedbackSettings = Field(default_factory=FeedbackSettings)
class LlmSettings(BaseModel):
@@ -235,6 +240,22 @@ def _resolve_env_file() -> str:
PROJECT_ROOT = _resolve_project_root()
class FeedbackReportSettings(BaseModel):
email: str = Field(default="support@example.com", description="客服邮箱")
cron: str = Field(default="0 10 * * *", description="报告生成cron表达式")
enabled: bool = Field(default=False, description="是否启用报告推送")
class EmailSettings(BaseModel):
host: str = Field(default="smtp.feishu.cn", description="SMTP 服务器地址")
port: int = Field(default=465, ge=1, le=65535, description="SMTP 端口")
username: str = Field(default="", description="SMTP 用户名")
password: SecretStr = Field(default=SecretStr(""), description="SMTP 密码")
use_ssl: bool = Field(default=True, description="是否使用 SSL")
from_address: str = Field(default="noreply@example.com", description="发件人地址")
from_name: str = Field(default="Eryao Feedback", description="发件人显示名称")
class Settings(BaseSettings):
runtime: RuntimeSettings = RuntimeSettings()
cors: CorsSettings = CorsSettings()
@@ -250,6 +271,10 @@ class Settings(BaseSettings):
taskiq: TaskiqSettings = Field(default_factory=TaskiqSettings)
agent_runtime: AgentRuntimeSettings = Field(default_factory=AgentRuntimeSettings)
points_policy: PointsPolicySettings = Field(default_factory=PointsPolicySettings)
feedback_report: FeedbackReportSettings = Field(
default_factory=FeedbackReportSettings
)
email: EmailSettings = Field(default_factory=EmailSettings)
@computed_field
@property