This commit is contained in:
zlwu committed 2026-08-11 11:13:01 +08:00
1 parent 80138c6388
commit 693e8a678e
682 files changed
+211375 -5306

No files matched your search

+8 -31
View File
@@ -11,12 +11,14 @@ from app.core.database import get_db
from app.core.response import error_response, success_response
from app.crud.user import user_crud
from app.services.issue_user_tokens import issue_tokens_for_user
from app.services.wechat_login_service import resolve_wechat_login_user
from app.services.wechat_mini_auth import (
WechatMiniAuthError,
code2session,
get_phone_number,
)
router = APIRouter(prefix="/mini/auth", tags=["小程序鉴权"])
@@ -34,7 +36,7 @@ async def _resolve_user_id(request: Request, db: Session) -> str:
@router.post("/wechat-login", response_model=dict)
async def wechat_login(body: dict, db: Session = Depends(get_db)):
"""微信授权登录:优先 openid;未绑定则用手机号匹配员工并绑定。"""
"""微信授权登录:须授权手机号;openid 已绑定也须与账号 phone_no 一致。"""
login_code = (body.get("loginCode") or "").strip()
phone_code = (body.get("phoneCode") or "").strip() or None
@@ -43,15 +45,6 @@ async def wechat_login(body: dict, db: Session = Depends(get_db)):
except WechatMiniAuthError as exc:
return error_response(400, str(exc))
openid = sess["openid"]
user = user_crud.get_by_wx_openid(db, openid)
if user:
try:
data = await issue_tokens_for_user(db, user)
except ValueError as exc:
return error_response(403, str(exc))
return success_response(data)
if not phone_code:
return error_response(400, "请授权手机号登录,或使用账号密码登录")
@@ -60,27 +53,11 @@ async def wechat_login(body: dict, db: Session = Depends(get_db)):
except WechatMiniAuthError as exc:
return error_response(400, str(exc))
users = user_crud.list_active_by_phone_no(db, phone)
if not users:
return error_response(
401, "未找到与该手机号匹配的员工账号,请使用账号密码登录"
)
if len(users) > 1:
return error_response(400, "手机号对应多个账号,请使用账号密码登录")
user = users[0]
other = user_crud.get_by_wx_openid(db, openid)
if other and other.id != user.id:
return error_response(409, "该微信已绑定其他账号")
existing_oid = (getattr(user, "wx_openid", None) or "").strip()
if existing_oid and existing_oid != openid:
return error_response(409, "该账号已绑定其他微信,请联系管理员")
user.wx_openid = openid
user.update_time = datetime.now()
db.add(user)
db.commit()
db.refresh(user)
user, err_msg, err_code = resolve_wechat_login_user(
db, openid=sess["openid"], phone=phone
)
if err_msg:
return error_response(err_code or 400, err_msg)
try:
data = await issue_tokens_for_user(db, user)
+28 -37
View File
@@ -1,8 +1,9 @@
"""公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线)"""
import os
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile, File
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import Session
@@ -37,43 +38,11 @@ from app.services.web_auth_roles import (
)
from app.schemas.auth import ProfileUpdateRequest
from app.crud.user import update_user, change_password
from app.services.issue_user_tokens import build_user_info_payload
router = APIRouter(prefix="", tags=["公共接口"])
def _avatar_url_field(user: SysUser) -> Optional[str]:
"""返回库中原始头像路径,由前端按 VITE_MINIO_BASE_URL 拼展示 URL。"""
raw = getattr(user, "avatar", None)
if not raw:
return None
s = str(raw).strip()
return s if s else None
def _build_user_info_payload(user: SysUser, web_role: str, role_names: list = None) -> dict:
"""与 web-admin `userInfo` 字段约定一致。"""
phone = (getattr(user, "phone_no", None) or "").strip() or None
return {
"id": user.id,
"username": user.user_no,
"realName": user.nick_name,
"phone": phone,
"email": (getattr(user, "email", None) or "").strip() or None,
"hireDate": user.hire_date.strftime("%Y-%m-%d") if getattr(user, "hire_date", None) else None,
"probationMonths": getattr(user, "probation_months", None),
"lastWorkDay": user.last_work_day.strftime("%Y-%m-%d")
if getattr(user, "last_work_day", None)
else None,
"sex": getattr(user, "sex", None) or "0",
"remark": (getattr(user, "remark", None) or "").strip() or None,
"role": web_role,
"roleNames": role_names or [],
"regionId": user.dept_id,
"regionName": None,
"avatarUrl": _avatar_url_field(user),
}
def _build_current_user_payload(current_user: dict) -> dict:
"""组装 current-user 接口响应。"""
au = current_user.get("avatar_url")
@@ -90,7 +59,9 @@ def _build_current_user_payload(current_user: dict) -> dict:
"roleNames": current_user.get("role_names") or current_user.get("roleNames") or [],
"isSuperAdmin": current_user.get("role") == "super_admin",
"regionId": current_user.get("region_id"),
"regionName": None,
"regionName": current_user.get("region_name") or current_user.get("dept_name"),
"deptName": current_user.get("dept_name") or current_user.get("region_name"),
"postNames": current_user.get("post_names") or [],
"regionIds": [],
"isActive": current_user.get("is_active"),
"avatarUrl": avatar_url if avatar_url else None,
@@ -179,7 +150,7 @@ async def web_login(
"token": access_token,
"refreshToken": refresh_token,
"expiresIn": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
"userInfo": _build_user_info_payload(user, web_role, role_names),
"userInfo": build_user_info_payload(user, web_role, role_names, db=db),
}
)
@@ -305,7 +276,7 @@ async def update_current_user_profile(
return error_response(404, "用户不存在")
web_role = resolve_web_role_for_user(db, user.id)
role_names = get_user_role_names(db, user.id)
return success_response(_build_user_info_payload(user, web_role, role_names))
return success_response(build_user_info_payload(user, web_role, role_names, db=db))
except HTTPException:
return error_response(401, "Token无效或已过期")
except ValueError as exc:
@@ -416,3 +387,23 @@ async def get_web_auth_menu_codes(request: Request, db: Session = Depends(get_db
)
except HTTPException:
return error_response(401, "Token无效或已过期")
@router.get("/cn-holiday/range", response_model=dict)
def cn_holiday_range(
start: str = Query(..., description="YYYY-MM-DD"),
end: str = Query(..., description="YYYY-MM-DD"),
db: Session = Depends(get_db),
):
"""查询闭区间内每日节假日/休息日标志(只读本地缓存表)。"""
try:
start_d = datetime.strptime(start.strip(), "%Y-%m-%d").date()
end_d = datetime.strptime(end.strip(), "%Y-%m-%d").date()
except ValueError:
raise HTTPException(status_code=400, detail="start/end 须为 YYYY-MM-DD")
if end_d < start_d:
raise HTTPException(status_code=400, detail="end 不能早于 start")
from app.services.cn_holiday import list_day_flags
days = list_day_flags(db, start_d, end_d)
return success_response({"days": days})