接口跑通,基础功能全部实现

This commit is contained in:
2026-03-16 16:14:08 +08:00
parent f81aec48ca
commit 2f2d5c3795
38 changed files with 3352 additions and 1754 deletions

View File

@@ -13,9 +13,11 @@ import os
import logging
from shared.models import get_db, User
from shared.config import shared_settings
from auth_service.app.models.database import create_tables
from auth_service.app.schemas.user import (
UserCreate, UserLogin, UserResponse, Token, TokenData, RefreshTokenRequest, AuthResponse,
UserCreate, UserLogin, UserResponse, Token, TokenData,
RefreshTokenRequest, AuthResponse, WxLoginRequest,
)
from auth_service.app.services.auth_service import AuthService
from auth_service.app.utils.security import (
@@ -235,3 +237,99 @@ async def get_current_user_info(current_user: UserResponse = Depends(get_current
Get current user information
"""
return current_user
@app.post("/auth/wx-login", response_model=AuthResponse)
async def wx_login(body: WxLoginRequest, db: AsyncSession = Depends(get_db)):
"""
微信小程序登录。
流程:
1. 用 code 调微信 code2Session 接口换取 openid
2. 查找是否已有该 openid 的用户
3. 没有则自动注册,有则直接登录
4. 返回 JWT token
"""
import httpx
appid = shared_settings.WX_APPID
secret = shared_settings.WX_SECRET
if not appid or not secret:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="微信小程序未配置 APPID 和 SECRET",
)
# Step 1: code 换 openid
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(
"https://api.weixin.qq.com/sns/jscode2session",
params={
"appid": appid,
"secret": secret,
"js_code": body.code,
"grant_type": "authorization_code",
},
)
wx_data = resp.json()
openid = wx_data.get("openid")
if not openid:
errcode = wx_data.get("errcode", "unknown")
errmsg = wx_data.get("errmsg", "未知错误")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"微信登录失败: {errmsg} (errcode={errcode})",
)
# Step 2: 查找已有用户
result = await db.execute(select(User).where(User.wx_openid == openid))
user = result.scalar_one_or_none()
if user:
# 已有用户 — 更新昵称头像(如果传了)
if body.nickname and body.nickname != user.wx_nickname:
user.wx_nickname = body.nickname
if body.avatar_url and body.avatar_url != user.wx_avatar:
user.wx_avatar = body.avatar_url
await db.commit()
await db.refresh(user)
else:
# Step 3: 自动注册
import uuid
nickname = body.nickname or f"wx_{openid[:8]}"
# 生成唯一 username避免冲突
username = f"wx_{openid[:12]}"
user = User(
id=str(uuid.uuid4()),
username=username,
wx_openid=openid,
wx_nickname=nickname,
wx_avatar=body.avatar_url,
is_active=True,
)
db.add(user)
await db.commit()
await db.refresh(user)
logger.info(f"微信用户自动注册: openid={openid[:16]}..., username={username}")
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="账号已被禁用",
)
# Step 4: 签发 token
access_token = create_access_token(
data={"sub": str(user.id), "username": user.username}
)
refresh_token = await create_refresh_token(str(user.id))
return AuthResponse(
access_token=access_token,
refresh_token=refresh_token,
token_type="bearer",
expires_in=3600,
user=UserResponse.from_orm(user),
)

View File

@@ -28,12 +28,17 @@ class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
is_active: Optional[bool] = None
class UserResponse(UserBase):
class UserResponse(BaseModel):
"""Schema for user response data"""
id: UUID
username: str
email: Optional[EmailStr] = None
created_at: datetime
is_active: bool
wx_openid: Optional[str] = None
wx_nickname: Optional[str] = None
wx_avatar: Optional[str] = None
class Config:
from_attributes = True # Enable ORM mode
@@ -64,3 +69,10 @@ class TokenData(BaseModel):
sub: str = Field(..., description="Subject (user ID)")
username: str = Field(..., description="Username")
exp: Optional[int] = None
class WxLoginRequest(BaseModel):
"""微信小程序登录请求"""
code: str = Field(..., description="wx.login() 获取的临时登录凭证")
nickname: Optional[str] = Field(None, max_length=100, description="微信昵称")
avatar_url: Optional[str] = Field(None, max_length=500, description="微信头像 URL")