This commit is contained in:
zuowei1216
2025-12-22 21:06:29 +08:00
parent 8ea58fe480
commit 1b19ff1b92
179 changed files with 21895 additions and 3774 deletions

View File

@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
"""
管理员配置接口
功能管理功能配置、VIP配置、签到配置
"""
from fastapi import APIRouter, Header, HTTPException, Depends
from typing import List, Optional
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.db import get_db
from app.models.business import FeatureConfig, VipConfig, CheckInConfig
router = APIRouter()
# ==================== 数据模型 ====================
class FeatureConfigCreate(BaseModel):
feature_key: str
feature_name: str
category: str = "general"
points_cost: int
vip_points_cost: int = 0
svip_points_cost: int = 0
description: Optional[str] = None
class FeatureConfigUpdate(BaseModel):
points_cost: Optional[int] = None
vip_points_cost: Optional[int] = None
svip_points_cost: Optional[int] = None
enabled: Optional[bool] = None
description: Optional[str] = None
class VIPConfigUpdate(BaseModel):
price: Optional[float] = None
daily_quota: Optional[int] = None
points_multiplier: Optional[float] = None
class CheckInConfigCreate(BaseModel):
consecutive_days: int
base_points: int
bonus_points: int
total_points: int
class CheckInConfigUpdate(BaseModel):
base_points: Optional[int] = None
bonus_points: Optional[int] = None
total_points: Optional[int] = None
# ==================== 辅助函数 ====================
def verify_admin_token(token: str):
"""验证管理员Token"""
expected_token = "admin-secret-token"
if token != expected_token:
raise HTTPException(status_code=401, detail="管理员Token无效")
# ==================== 功能配置管理 ====================
@router.get("/admin/config/features")
async def get_features_config(token: str = Header(..., alias="x-admin-token"), db: Session = Depends(get_db)):
"""获取所有功能配置"""
verify_admin_token(token)
features = db.query(FeatureConfig).order_by(FeatureConfig.category, FeatureConfig.feature_key).all()
return features
@router.post("/admin/config/features")
async def create_feature_config(
data: FeatureConfigCreate,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""新增功能配置"""
verify_admin_token(token)
existing = db.query(FeatureConfig).filter(FeatureConfig.feature_key == data.feature_key).first()
if existing:
raise HTTPException(status_code=400, detail="功能标识已存在")
new_feature = FeatureConfig(
feature_key=data.feature_key,
feature_name=data.feature_name,
category=data.category,
points_cost=data.points_cost,
vip_points_cost=data.vip_points_cost,
svip_points_cost=data.svip_points_cost,
description=data.description
)
db.add(new_feature)
db.commit()
return {"code": 200, "message": "创建成功"}
@router.put("/admin/config/features/{feature_key}")
async def update_feature_config(
feature_key: str,
data: FeatureConfigUpdate,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""更新功能配置"""
verify_admin_token(token)
feature = db.query(FeatureConfig).filter(FeatureConfig.feature_key == feature_key).first()
if not feature:
raise HTTPException(status_code=404, detail="功能配置不存在")
if data.points_cost is not None:
feature.points_cost = data.points_cost
if data.vip_points_cost is not None:
feature.vip_points_cost = data.vip_points_cost
if data.svip_points_cost is not None:
feature.svip_points_cost = data.svip_points_cost
if data.enabled is not None:
feature.enabled = data.enabled
if data.description is not None:
feature.description = data.description
db.commit()
return {"code": 200, "message": "更新成功"}
@router.delete("/admin/config/features/{feature_key}")
async def delete_feature_config(
feature_key: str,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""删除功能配置"""
verify_admin_token(token)
feature = db.query(FeatureConfig).filter(FeatureConfig.feature_key == feature_key).first()
if not feature:
raise HTTPException(status_code=404, detail="功能配置不存在")
db.delete(feature)
db.commit()
return {"code": 200, "message": "删除成功"}
# ==================== VIP配置管理 ====================
@router.get("/admin/config/vip")
async def get_vip_config(token: str = Header(..., alias="x-admin-token"), db: Session = Depends(get_db)):
"""获取VIP配置"""
verify_admin_token(token)
# Sort order: vip, svip
# We can fetch all and sort in python or rely on insertion order/id
configs = db.query(VipConfig).all()
# Simple sort
configs.sort(key=lambda x: 0 if x.vip_type == 'vip' else 1)
return configs
@router.put("/admin/config/vip/{vip_type}")
async def update_vip_config(
vip_type: str,
data: VIPConfigUpdate,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""更新VIP配置"""
verify_admin_token(token)
if vip_type not in ['vip', 'svip']:
raise HTTPException(status_code=400, detail="VIP类型必须是vip或svip")
config = db.query(VipConfig).filter(VipConfig.vip_type == vip_type).first()
if not config:
raise HTTPException(status_code=404, detail="VIP配置不存在")
if data.price is not None:
config.price = data.price
if data.daily_quota is not None:
config.daily_quota = data.daily_quota
if data.points_multiplier is not None:
config.points_multiplier = data.points_multiplier
db.commit()
return {"code": 200, "message": "更新成功"}
# ==================== 签到配置管理 ====================
@router.get("/admin/config/checkin")
async def get_checkin_config(token: str = Header(..., alias="x-admin-token"), db: Session = Depends(get_db)):
"""获取签到配置"""
verify_admin_token(token)
configs = db.query(CheckInConfig).filter(CheckInConfig.enabled == True).order_by(CheckInConfig.consecutive_days).all()
return configs
@router.post("/admin/config/checkin")
async def create_checkin_config(
data: CheckInConfigCreate,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""新增签到档位"""
verify_admin_token(token)
existing = db.query(CheckInConfig).filter(CheckInConfig.consecutive_days == data.consecutive_days).first()
if existing:
raise HTTPException(status_code=400, detail="该连续天数配置已存在")
new_config = CheckInConfig(
consecutive_days=data.consecutive_days,
base_points=data.base_points,
bonus_points=data.bonus_points,
total_points=data.total_points
)
db.add(new_config)
db.commit()
return {"code": 200, "message": "创建成功"}
@router.put("/admin/config/checkin/{consecutive_days}")
async def update_checkin_config(
consecutive_days: int,
data: CheckInConfigUpdate,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""更新签到档位"""
verify_admin_token(token)
config = db.query(CheckInConfig).filter(CheckInConfig.consecutive_days == consecutive_days).first()
if not config:
raise HTTPException(status_code=404, detail="签到配置不存在")
if data.base_points is not None:
config.base_points = data.base_points
if data.bonus_points is not None:
config.bonus_points = data.bonus_points
if data.total_points is not None:
config.total_points = data.total_points
db.commit()
return {"code": 200, "message": "更新成功"}
@router.delete("/admin/config/checkin/{consecutive_days}")
async def delete_checkin_config(
consecutive_days: int,
token: str = Header(..., alias="x-admin-token"),
db: Session = Depends(get_db)
):
"""删除签到档位"""
verify_admin_token(token)
config = db.query(CheckInConfig).filter(CheckInConfig.consecutive_days == consecutive_days).first()
if not config:
raise HTTPException(status_code=404, detail="签到配置不存在")
db.delete(config)
db.commit()
return {"code": 200, "message": "删除成功"}

View File

@@ -14,6 +14,16 @@ from datetime import datetime, timezone
router = APIRouter()
@router.post("/verify", response_model=VerifyResponse)
async def verify(
verify_data: VerifyRequest,
db: Session = Depends(get_db),
current_username: str = Depends(get_current_user)
):
"""验证用户授权状态"""
# 许可证验证接口:验证 token 是否有效,检查账户过期,更新活跃时间
return auth_service.verify_license(db, verify_data, current_username)
@router.post("/send-verification-code")
async def send_verification_code(body: SendVerificationCodeRequest, db: Session = Depends(get_db)):
# 发送注册验证码
@@ -97,3 +107,5 @@ async def get_online_time(username: str, db: Session = Depends(get_db)):
async def heartbeat(body: UserHeartbeat, db: Session = Depends(get_db)):
# 心跳接口:更新会话的最近在线时间
return auth_service.heartbeat(db, body.username, body.device_id)

View File

@@ -0,0 +1,254 @@
# -*- coding: utf-8 -*-
"""
签到接口
功能:每日签到、签到状态查询、签到日历
"""
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from datetime import date, datetime, timedelta
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.db import get_db
from app.models.user import User
from app.models.business import CheckInConfig, VipConfig, CheckInRecord, PointsHistory
router = APIRouter()
# ==================== 数据模型 ====================
class CheckInRequest(BaseModel):
username: str
# ==================== 签到功能 ====================
@router.post("/checkin/daily")
async def daily_checkin(data: CheckInRequest, db: Session = Depends(get_db)):
"""每日签到"""
# 1. 获取用户信息
user = db.query(User).filter(User.username == data.username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 2. 检查今日是否已签到
today = date.today()
if user.last_check_in_date == today:
raise HTTPException(status_code=400, detail="今日已签到,明天再来吧")
# 3. 计算连续天数
consecutive_days = user.consecutive_check_in if user.consecutive_check_in else 0
total_days = user.total_check_in_days if user.total_check_in_days else 0
yesterday = today - timedelta(days=1)
if user.last_check_in_date == yesterday:
# 连续签到
consecutive_days += 1
else:
# 中断了,重新开始
consecutive_days = 1
total_days += 1
# 4. 从配置表获取奖励
reward_config = db.query(CheckInConfig)\
.filter(CheckInConfig.consecutive_days <= consecutive_days, CheckInConfig.enabled == True)\
.order_by(CheckInConfig.consecutive_days.desc())\
.first()
if not reward_config:
# 如果没有配置默认给10积分
base_points = 10
bonus_points = 0
total_points = 10
else:
base_points = reward_config.base_points
bonus_points = reward_config.bonus_points
total_points = reward_config.total_points
# 5. 应用VIP倍数
vip_multiplier = 1.0
if user.vip_type and user.vip_type in ['vip', 'svip']:
vip_config = db.query(VipConfig).filter(VipConfig.vip_type == user.vip_type).first()
if vip_config:
vip_multiplier = float(vip_config.points_multiplier)
points_earned = int(total_points * vip_multiplier)
current_points = user.points if user.points else 0
new_balance = current_points + points_earned
# 6. 更新用户数据
user.points = new_balance
user.total_check_in_days = total_days
user.consecutive_check_in = consecutive_days
user.last_check_in_date = today
# 7. 记录签到记录
checkin_record = CheckInRecord(
user_id=user.id,
username=data.username,
check_in_date=today,
points_earned=points_earned,
consecutive_days=consecutive_days,
vip_multiplier=vip_multiplier
)
db.add(checkin_record)
# 8. 记录积分历史
points_history = PointsHistory(
user_id=user.id,
username=data.username,
type='checkin',
amount=points_earned,
balance=new_balance,
description=f"每日签到奖励(连续{consecutive_days}天)"
)
db.add(points_history)
db.commit()
# 9. 返回结果
return {
"code": 200,
"data": {
"success": True,
"points_earned": points_earned,
"base_points": base_points,
"bonus_points": bonus_points,
"vip_multiplier": vip_multiplier,
"consecutive_days": consecutive_days,
"total_check_in_days": total_days,
"total_points": new_balance,
"message": f"签到成功!连续签到{consecutive_days}天,获得{points_earned}积分"
}
}
@router.get("/checkin/config")
async def get_checkin_config(db: Session = Depends(get_db)):
"""获取签到奖励配置(公开)"""
configs = db.query(CheckInConfig)\
.filter(CheckInConfig.enabled == True)\
.order_by(CheckInConfig.consecutive_days)\
.all()
result = []
for config in configs:
result.append({
"consecutive_days": config.consecutive_days,
"base_points": config.base_points,
"bonus_points": config.bonus_points,
"total_points": config.total_points
})
return {
"code": 200,
"data": result
}
@router.get("/checkin/status")
async def get_checkin_status(username: str, db: Session = Depends(get_db)):
"""获取签到状态"""
user = db.query(User).filter(User.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
today = date.today()
today_checked = (user.last_check_in_date == today)
return {
"code": 200,
"data": {
"today_checked": today_checked,
"consecutive_days": user.consecutive_check_in if user.consecutive_check_in else 0,
"total_days": user.total_check_in_days if user.total_check_in_days else 0,
"last_check_in_date": user.last_check_in_date.isoformat() if user.last_check_in_date else None
}
}
@router.get("/checkin/calendar/{year}/{month}")
async def get_checkin_calendar(username: str, year: int, month: int, db: Session = Depends(get_db)):
"""获取签到日历"""
user = db.query(User).filter(User.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 使用 SQLAlchemy 提取日期部分
# 注意SQLite 和 MySQL 的日期函数不同
# 为了兼容性,这里我们查询该月范围内的所有记录,然后在 Python 中处理
import calendar
_, last_day = calendar.monthrange(year, month)
start_date = date(year, month, 1)
end_date = date(year, month, last_day)
records = db.query(CheckInRecord)\
.filter(
CheckInRecord.user_id == user.id,
CheckInRecord.check_in_date >= start_date,
CheckInRecord.check_in_date <= end_date
).all()
checked_dates = [record.check_in_date.day for record in records]
return {
"code": 200,
"data": {
"year": year,
"month": month,
"checked_dates": checked_dates
}
}
@router.get("/checkin/history")
async def get_checkin_history(username: str, page: int = 1, limit: int = 10, db: Session = Depends(get_db)):
"""签到记录"""
user = db.query(User).filter(User.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
offset = (page - 1) * limit
total = db.query(func.count(CheckInRecord.id)).filter(CheckInRecord.user_id == user.id).scalar()
records = db.query(CheckInRecord)\
.filter(CheckInRecord.user_id == user.id)\
.order_by(CheckInRecord.check_in_date.desc())\
.offset(offset)\
.limit(limit)\
.all()
result_records = []
for record in records:
result_records.append({
"check_in_date": record.check_in_date.isoformat(),
"points_earned": record.points_earned,
"consecutive_days": record.consecutive_days,
"created_at": record.created_at.isoformat() if record.created_at else None
})
return {
"code": 200,
"data": {
"total": total,
"records": result_records
}
}
@router.get("/checkin/config")
async def get_checkin_config(db: Session = Depends(get_db)):
"""获取签到奖励规则 (公开接口)"""
configs = db.query(CheckInConfig).filter(CheckInConfig.enabled == True).order_by(CheckInConfig.consecutive_days.asc()).all()
data = []
for c in configs:
data.append({
"consecutive_days": c.consecutive_days,
"base_points": c.base_points,
"bonus_points": c.bonus_points,
"total_points": c.total_points
})
return {
"code": 200,
"data": data
}

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""
通用功能使用接口
核心动态扣费逻辑SVIP免费、VIP配额、普通积分
"""
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from datetime import date
from sqlalchemy.orm import Session
from app.db import get_db
from app.models.user import User
from app.models.business import FeatureConfig, VipConfig, PointsHistory
router = APIRouter()
# ==================== 数据模型 ====================
class UseFeatureRequest(BaseModel):
username: str
feature_key: str
device_id: str
# ==================== 通用功能使用 ====================
@router.post("/feature/use")
async def use_feature(data: UseFeatureRequest, db: Session = Depends(get_db)):
"""
通用功能使用接口
逻辑:
1. SVIP用户免费使用
2. VIP用户优先使用配额配额用完后扣积分
3. 普通用户:扣除积分
"""
# 1. 获取功能配置
feature = db.query(FeatureConfig).filter(FeatureConfig.feature_key == data.feature_key).first()
if not feature or not feature.enabled:
raise HTTPException(status_code=400, detail="功能不存在或已禁用")
# 2. 获取用户信息
user = db.query(User).filter(User.username == data.username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
current_points = user.points if user.points else 0
vip_type = user.vip_type
vip_quota = user.vip_daily_quota if user.vip_daily_quota else 0
quota_reset_date = user.vip_quota_reset_date
# 3. 检查并重置VIP配额
today = date.today()
if vip_type in ['vip', 'svip'] and quota_reset_date != today:
# 重置配额
vip_config = db.query(VipConfig).filter(VipConfig.vip_type == vip_type).first()
if vip_config:
vip_quota = vip_config.daily_quota
user.vip_daily_quota = vip_quota
user.vip_quota_reset_date = today
db.commit()
# 4. 判断消耗类型
cost_type = "points"
points_cost = 0
remaining_quota = vip_quota
# SVIP免费
if vip_type == 'svip' and feature.svip_points_cost == 0:
cost_type = "free"
message = "SVIP用户免费使用"
# VIP配额
elif vip_type == 'vip' and vip_quota > 0 and feature.vip_points_cost == 0:
cost_type = "vip_quota"
remaining_quota = vip_quota - 1
user.vip_daily_quota = remaining_quota
db.commit()
message = f"VIP配额使用剩余{remaining_quota}"
# 扣积分
else:
# 计算实际消耗
if vip_type == 'vip' and feature.vip_points_cost > 0:
points_cost = feature.vip_points_cost
elif vip_type == 'svip' and feature.svip_points_cost > 0:
points_cost = feature.svip_points_cost
else:
points_cost = feature.points_cost
# 检查余额
if current_points < points_cost:
raise HTTPException(
status_code=402,
detail=f"积分不足。当前: {current_points},需要: {points_cost}。请签到获取积分或开通VIP"
)
# 扣除积分
new_balance = current_points - points_cost
user.points = new_balance
# 记录积分历史
points_history = PointsHistory(
user_id=user.id,
username=data.username,
type='consume',
amount=-points_cost,
balance=new_balance,
description=f"使用{feature.feature_name}"
)
db.add(points_history)
db.commit()
message = f"消耗{points_cost}积分,剩余{new_balance}积分"
# 5. 记录使用日志 (TODO: Add FeatureUsageLogs model if needed, currently skipping or adding to PointsHistory if consumed)
# For now, we only log if points consumed. If we need separate usage log table, we need to create it.
# The SQL version inserted into feature_usage_logs.
# Let's assume PointsHistory covers financial aspect.
# If we need analytics, we have analytics API.
# 6. 返回结果
return {
"code": 200,
"data": {
"success": True,
"feature_name": feature.feature_name,
"cost_type": cost_type,
"points_cost": points_cost,
"vip_remaining_quota": remaining_quota if vip_type in ['vip', 'svip'] else None,
"points_remaining": user.points,
"message": message
}
}

142
Server/app/api/v1/stats.py Normal file
View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
统计接口
功能:数据统计和分析
"""
from fastapi import APIRouter, Header, HTTPException
from datetime import date, timedelta
import pymysql
from app.core.database import get_db_connection
router = APIRouter()
# ==================== 辅助函数 ====================
def verify_admin_token(token: str):
"""验证管理员Token"""
expected_token = "admin-secret-token"
if token != expected_token:
raise HTTPException(status_code=401, detail="管理员Token无效")
# ==================== 统计功能 ====================
@router.get("/admin/stats/today")
async def get_today_stats(token: str = Header(..., alias="x-admin-token")):
"""今日统计"""
verify_admin_token(token)
conn = get_db_connection()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
today = date.today()
# 总用户数
cursor.execute("SELECT COUNT(*) as total FROM users")
total_users = cursor.fetchone()['total']
# 今日签到数
cursor.execute("""
SELECT COUNT(DISTINCT user_id) as count
FROM check_in_records
WHERE check_in_date = %s
""", (today,))
checkin_count = cursor.fetchone()['count']
# 今日功能使用次数
cursor.execute("""
SELECT COUNT(*) as count
FROM feature_usage_logs
WHERE DATE(created_at) = %s
""", (today,))
feature_usage_count = cursor.fetchone()['count']
# VIP用户数
cursor.execute("""
SELECT COUNT(*) as count
FROM users
WHERE vip_type IN ('vip', 'svip')
AND (vip_expire IS NULL OR vip_expire > NOW())
""")
vip_count = cursor.fetchone()['count']
return {
"code": 200,
"data": {
"total_users": total_users,
"checkin_count": checkin_count,
"feature_usage_count": feature_usage_count,
"vip_count": vip_count
}
}
finally:
conn.close()
@router.get("/admin/stats/feature-usage")
async def get_feature_usage_stats(
days: int = 7,
token: str = Header(..., alias="x-admin-token")
):
"""功能使用排行"""
verify_admin_token(token)
conn = get_db_connection()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
start_date = date.today() - timedelta(days=days-1)
cursor.execute("""
SELECT
l.feature_key,
f.feature_name,
COUNT(*) as usage_count
FROM feature_usage_logs l
LEFT JOIN features_config f ON l.feature_key = f.feature_key
WHERE DATE(l.created_at) >= %s
GROUP BY l.feature_key, f.feature_name
ORDER BY usage_count DESC
LIMIT 10
""", (start_date,))
return {
"code": 200,
"data": cursor.fetchall()
}
finally:
conn.close()
@router.get("/admin/stats/points-trend")
async def get_points_trend(
days: int = 7,
token: str = Header(..., alias="x-admin-token")
):
"""积分趋势统计"""
verify_admin_token(token)
conn = get_db_connection()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
start_date = date.today() - timedelta(days=days-1)
cursor.execute("""
SELECT
DATE(created_at) as date,
SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) as earned,
SUM(CASE WHEN amount < 0 THEN -amount ELSE 0 END) as consumed
FROM points_history
WHERE DATE(created_at) >= %s
GROUP BY DATE(created_at)
ORDER BY date
""", (start_date,))
records = cursor.fetchall()
for record in records:
record['date'] = record['date'].isoformat()
return {
"code": 200,
"data": records
}
finally:
conn.close()

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""
用户资料接口
功能:获取和更新用户资料
"""
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.db import get_db
from app.models.user import User
from app.models.business import PointsHistory
router = APIRouter()
# ==================== 数据模型 ====================
class UserProfileUpdate(BaseModel):
username: str
nickname: Optional[str] = None
avatar: Optional[str] = None
email: Optional[str] = None
# ==================== 用户资料管理 ====================
@router.get("/user/profile")
async def get_user_profile(username: str, db: Session = Depends(get_db)):
"""获取用户资料"""
user = db.query(User).filter(User.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 转换为字典以便序列化
user_data = {
"username": user.username,
"nickname": user.nickname,
"avatar": user.avatar,
"email": user.email,
"points": user.points,
"level": user.level,
"vip_type": user.vip_type,
"vip_expire": user.vip_expire.isoformat() if user.vip_expire else None,
"vip_daily_quota": user.vip_daily_quota,
"total_check_in_days": user.total_check_in_days,
"consecutive_check_in": user.consecutive_check_in,
"created_at": user.created_at.isoformat() if user.created_at else None
}
return {
"code": 200,
"data": user_data
}
@router.put("/user/profile")
async def update_user_profile(data: UserProfileUpdate, db: Session = Depends(get_db)):
"""更新用户资料"""
user = db.query(User).filter(User.username == data.username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
if data.nickname is not None:
user.nickname = data.nickname
if data.avatar is not None:
user.avatar = data.avatar
if data.email is not None:
user.email = data.email
db.commit()
return {
"code": 200,
"message": "更新成功"
}
# ==================== 积分历史 ====================
@router.get("/points/history")
async def get_points_history(
username: str,
type: Optional[str] = None,
page: int = 1,
limit: int = 20,
db: Session = Depends(get_db)
):
"""获取积分历史"""
user = db.query(User).filter(User.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
offset = (page - 1) * limit
query = db.query(PointsHistory).filter(PointsHistory.user_id == user.id)
if type:
query = query.filter(PointsHistory.type == type)
total = query.count()
records = query.order_by(PointsHistory.created_at.desc()).offset(offset).limit(limit).all()
result_records = []
for record in records:
result_records.append({
"type": record.type,
"amount": record.amount,
"balance": record.balance,
"description": record.description,
"created_at": record.created_at.isoformat() if record.created_at else None
})
return {
"code": 200,
"data": {
"total": total,
"current_balance": user.points,
"records": result_records
}
}