Files
wechat_backend/routes/feedback.py
王宏豪 f3fbd0377a feat(init): 添加完整的认证和业务路由模块
- 实现微信登录、手机号绑定、token验证和登出功能
- 添加打卡系统,支持正常、迟到、请假等状态记录
- 集成课程投票功能,支持多课程选择和评分评论
- 开发反馈系统,支持图文反馈提交和管理员处理
- 构建首页页面模块,动态展示学习资源和个人信息
- 设计计划管理系统,支持周/月/年计划制定和跟踪
- 实现小组计划统计和历史记录查询功能
- 完善用户认证会话管理和数据库交互逻辑
2026-09-03 16:43:11 +08:00

110 lines
3.5 KiB
Python

import base64
import json
import logging
import os
import re
from datetime import datetime
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel
from .. import config
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/feedback", tags=["feedback"])
def _image_dir():
return os.path.join(config.FEEDBACK_DIR, "images")
def _ensure_dirs():
os.makedirs(config.FEEDBACK_DIR, exist_ok=True)
os.makedirs(_image_dir(), exist_ok=True)
def _load_items():
items = []
for fn in os.listdir(config.FEEDBACK_DIR):
if fn.endswith(".json"):
path = os.path.join(config.FEEDBACK_DIR, fn)
try:
with open(path, encoding="utf-8") as f:
items.append(json.load(f))
except Exception:
logger.exception("读取反馈数据文件失败: %s", path)
return items
@router.get("")
def list_feedback():
items = _load_items()
unsolved = sorted([x for x in items if x.get("status") == "unsolved"], key=lambda x: x["created_at"], reverse=True)
solved = sorted([x for x in items if x.get("status") != "unsolved"], key=lambda x: x["created_at"], reverse=True)
return unsolved + solved
class FeedbackRequest(BaseModel):
name: str = ""
description: str = ""
image: str = ""
@router.post("")
def submit_feedback(req: FeedbackRequest):
_ensure_dirs()
fid = os.urandom(4).hex()
now = datetime.now().strftime("%Y-%m-%d %H:%M")
item = {
"id": fid,
"name": req.name.strip() or "匿名",
"description": req.description.strip(),
"status": "unsolved",
"created_at": now,
}
img = req.image
if img.startswith("data:image/"):
try:
fmt = re.sub(r"[^a-zA-Z0-9]", "", img.split(";")[0].split("/")[1])
with open(os.path.join(_image_dir(), fid + "." + fmt), "wb") as f:
f.write(base64.b64decode(img.split(",")[1]))
item["image"] = "/api/v1/feedback/file/" + fid + "." + fmt
except Exception:
logger.exception("保存反馈图片失败: %s", fid)
with open(os.path.join(config.FEEDBACK_DIR, fid + ".json"), "w", encoding="utf-8") as f:
json.dump(item, f, ensure_ascii=False)
return item
@router.get("/file/{name}")
def get_file(name: str):
safe_name = os.path.basename(name)
path = os.path.join(_image_dir(), safe_name)
if not os.path.exists(path):
raise HTTPException(status_code=404, detail="not found")
ext = os.path.splitext(safe_name)[1].lstrip(".").lower()
media = {"png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg", "gif": "image/gif", "webp": "image/webp"}.get(ext, "application/octet-stream")
return FileResponse(path, media_type=media)
@router.post("/resolve")
def resolve(id: str = "", token: str = ""):
safe_id = re.sub(r"[^a-zA-Z0-9]", "", id)
if token != config.ADMIN_KEY:
raise HTTPException(status_code=403, detail="权限不足")
if not safe_id:
raise HTTPException(status_code=404, detail="未找到")
path = os.path.join(config.FEEDBACK_DIR, safe_id + ".json")
if not os.path.exists(path):
raise HTTPException(status_code=404, detail="未找到")
with open(path, encoding="utf-8") as f:
item = json.load(f)
item["status"] = "solved"
item["resolved_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
with open(path, "w", encoding="utf-8") as f:
json.dump(item, f, ensure_ascii=False)
return {"ok": True, "id": safe_id}