feat(init): 添加完整的认证和业务路由模块
- 实现微信登录、手机号绑定、token验证和登出功能 - 添加打卡系统,支持正常、迟到、请假等状态记录 - 集成课程投票功能,支持多课程选择和评分评论 - 开发反馈系统,支持图文反馈提交和管理员处理 - 构建首页页面模块,动态展示学习资源和个人信息 - 设计计划管理系统,支持周/月/年计划制定和跟踪 - 实现小组计划统计和历史记录查询功能 - 完善用户认证会话管理和数据库交互逻辑
This commit is contained in:
171
routes/checkin.py
Normal file
171
routes/checkin.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import logging
|
||||
import re
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .. import db
|
||||
from .auth import get_current_user
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/checkin", tags=["checkin"])
|
||||
|
||||
CHECKIN_STATUSES = {"正常", "迟到", "请假", "不参加讨论", "早退"}
|
||||
DEFAULT_STATUS = "正常"
|
||||
DEFAULT_COURSE_TYPE = "会员课程"
|
||||
MAX_NOTE_LEN = 500
|
||||
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
||||
SELECT_COLS = "id,user_id,class_date,checked_at,status,course_type,note"
|
||||
|
||||
|
||||
def get_class_date():
|
||||
today = date.today()
|
||||
return today + timedelta(days=(6 - today.weekday()))
|
||||
|
||||
|
||||
def _is_valid_date(value):
|
||||
if not isinstance(value, str) or not DATE_RE.match(value):
|
||||
return False
|
||||
try:
|
||||
return datetime.strptime(value, "%Y-%m-%d").date().isoformat() == value
|
||||
except ValueError as exc:
|
||||
logger.warning("日期校验失败: %s", exc)
|
||||
return False
|
||||
|
||||
|
||||
def fmt_dt(value):
|
||||
if isinstance(value, datetime):
|
||||
return value.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return str(value) if value else ""
|
||||
|
||||
|
||||
def fmt_rec(rec, name=""):
|
||||
return {
|
||||
"id": rec["id"],
|
||||
"user_id": rec["user_id"],
|
||||
"name": name,
|
||||
"class_date": str(rec["class_date"]),
|
||||
"checked_at": fmt_dt(rec.get("checked_at")),
|
||||
"status": rec.get("status", DEFAULT_STATUS),
|
||||
"course_type": rec.get("course_type", DEFAULT_COURSE_TYPE),
|
||||
"note": rec.get("note", ""),
|
||||
}
|
||||
|
||||
|
||||
class CheckinRequest(BaseModel):
|
||||
status: str = DEFAULT_STATUS
|
||||
class_date: str = ""
|
||||
note: str = ""
|
||||
course_type: str = DEFAULT_COURSE_TYPE
|
||||
|
||||
|
||||
@router.get("/week")
|
||||
def week(user=Depends(get_current_user)):
|
||||
class_date = str(get_class_date())
|
||||
rec = db.fetch_one(
|
||||
"SELECT " + SELECT_COLS + " FROM checkin_records_old WHERE user_id=%s AND class_date=%s",
|
||||
(user["id"], class_date),
|
||||
)
|
||||
return {"class_date": class_date, "checked": rec is not None, "record": fmt_rec(rec) if rec else None}
|
||||
|
||||
|
||||
@router.post("")
|
||||
def checkin(req: CheckinRequest, user=Depends(get_current_user)):
|
||||
status = req.status.strip()
|
||||
if status not in CHECKIN_STATUSES:
|
||||
raise HTTPException(status_code=400, detail="无效的打卡状态")
|
||||
|
||||
class_date = req.class_date.strip() or str(get_class_date())
|
||||
if not _is_valid_date(class_date):
|
||||
raise HTTPException(status_code=400, detail="无效的上课日期")
|
||||
|
||||
note = req.note.strip() if status != DEFAULT_STATUS else ""
|
||||
if len(note) > MAX_NOTE_LEN:
|
||||
raise HTTPException(status_code=400, detail="备注过长")
|
||||
|
||||
now = datetime.now()
|
||||
# 根据 user_id 判断当天是否已打卡:已打卡 → 更新;未打卡 → 新增
|
||||
existing = db.fetch_one(
|
||||
"SELECT id FROM checkin_records_old WHERE user_id=%s AND class_date=%s",
|
||||
(user["id"], class_date),
|
||||
)
|
||||
if existing:
|
||||
db.execute(
|
||||
"UPDATE checkin_records_old SET checked_at=%s, status=%s, course_type=%s, note=%s WHERE id=%s",
|
||||
(now, status, DEFAULT_COURSE_TYPE, note, existing["id"]),
|
||||
)
|
||||
else:
|
||||
# course_type 固定为会员课程(规范),忽略客户端传入值
|
||||
db.execute(
|
||||
"INSERT INTO checkin_records_old (user_id, class_date, checked_at, status, course_type, note) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s)",
|
||||
(user["id"], class_date, now, status, DEFAULT_COURSE_TYPE, note),
|
||||
)
|
||||
rec = db.fetch_one(
|
||||
"SELECT " + SELECT_COLS + " FROM checkin_records_old WHERE user_id=%s AND class_date=%s",
|
||||
(user["id"], class_date),
|
||||
)
|
||||
return {"success": True, "record": fmt_rec(rec, user.get("name", "")) if rec else None}
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
def list_records(page: int = 1, size: int = 10, user=Depends(get_current_user)):
|
||||
page = max(1, page)
|
||||
size = max(1, min(50, size))
|
||||
offset = (page - 1) * size
|
||||
total_row = db.fetch_one(
|
||||
"SELECT COUNT(*) AS cnt FROM checkin_records_old WHERE user_id=%s", (user["id"],)
|
||||
)
|
||||
total = total_row["cnt"] if total_row else 0
|
||||
rows = db.fetch_all(
|
||||
"SELECT " + SELECT_COLS + " FROM checkin_records_old "
|
||||
"WHERE user_id=%s ORDER BY class_date DESC LIMIT %s OFFSET %s",
|
||||
(user["id"], size, offset),
|
||||
)
|
||||
return {"list": [fmt_rec(r, user.get("name", "")) for r in rows], "total": total, "page": page, "size": size}
|
||||
|
||||
|
||||
@router.get("/group")
|
||||
def group_view(class_date: str = "", user=Depends(get_current_user)):
|
||||
class_date = class_date.strip() or str(get_class_date())
|
||||
if not _is_valid_date(class_date):
|
||||
raise HTTPException(status_code=400, detail="无效的上课日期")
|
||||
|
||||
user_row = db.fetch_one("SELECT `group` FROM auth_users WHERE id=%s", (user["id"],))
|
||||
if not user_row or not user_row.get("group"):
|
||||
return {"class_date": class_date, "group_name": "", "members": []}
|
||||
|
||||
group_name = user_row["group"]
|
||||
# 单查询:成员 + 同组成员该上课日的打卡记录,LEFT JOIN 一次取回
|
||||
rows = db.fetch_all(
|
||||
"SELECT m.id AS user_id, m.student_id, m.name, "
|
||||
" r.status, r.checked_at, r.note "
|
||||
"FROM auth_users m "
|
||||
"LEFT JOIN checkin_records_old r "
|
||||
" ON r.user_id = m.id AND r.class_date = %s "
|
||||
"WHERE m.`group` = %s "
|
||||
"ORDER BY m.student_id",
|
||||
(class_date, group_name),
|
||||
)
|
||||
|
||||
result_members = []
|
||||
for row in rows:
|
||||
rec = None
|
||||
if row.get("status") is not None:
|
||||
rec = {
|
||||
"status": row["status"],
|
||||
"checked_at": fmt_dt(row.get("checked_at")),
|
||||
"note": row.get("note", ""),
|
||||
}
|
||||
result_members.append({
|
||||
"user_id": row["user_id"],
|
||||
"student_id": row["student_id"],
|
||||
"name": row["name"],
|
||||
"role": "",
|
||||
"checked": rec is not None,
|
||||
"record": rec,
|
||||
})
|
||||
return {"class_date": class_date, "group_name": group_name, "members": result_members}
|
||||
Reference in New Issue
Block a user