feat: Add admin panel for system management, new authentication and payment blueprints, logger and stats services, and related model updates.

This commit is contained in:
24024 2026-01-18 21:09:43 +08:00
parent e790ac0ae6
commit ced1020235
6 changed files with 33 additions and 29 deletions

View File

@ -1,7 +1,7 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from datetime import datetime, timedelta from datetime import timedelta
from extensions import db from extensions import db
from models import User, Role, Permission, SystemDict, SystemNotification, Order, to_bj_time from models import User, Role, Permission, SystemDict, SystemNotification, Order, to_bj_time, get_bj_now
from middlewares.auth import permission_required from middlewares.auth import permission_required
from services.logger import system_logger from services.logger import system_logger
@ -277,7 +277,7 @@ def delete_notification():
@admin_bp.route('/orders', methods=['GET']) @admin_bp.route('/orders', methods=['GET'])
@permission_required('manage_system') # 仅限超级管理员 @permission_required('manage_system') # 仅限超级管理员
def get_orders(): def get_orders():
thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) thirty_min_ago = get_bj_now() - timedelta(minutes=30)
# 过滤掉超过 30 分钟未支付的订单 # 过滤掉超过 30 分钟未支付的订单
orders = Order.query.filter( orders = Order.query.filter(

View File

@ -1,8 +1,8 @@
from flask import Blueprint, request, jsonify, session, render_template, redirect, url_for from flask import Blueprint, request, jsonify, session, render_template, redirect, url_for
import json import json
from datetime import datetime, timedelta from datetime import timedelta
from extensions import db from extensions import db
from models import User from models import User, get_bj_now
from services.sms_service import SMSService from services.sms_service import SMSService
from services.captcha_service import CaptchaService from services.captcha_service import CaptchaService
from services.logger import system_logger from services.logger import system_logger
@ -57,7 +57,7 @@ def buy_page():
user_id = session['user_id'] user_id = session['user_id']
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) thirty_min_ago = get_bj_now() - timedelta(minutes=30)
# 获取用户个人充值记录 (过滤掉超过 30 分钟未支付的订单) # 获取用户个人充值记录 (过滤掉超过 30 分钟未支付的订单)
personal_orders = Order.query.filter( personal_orders = Order.query.filter(
@ -158,8 +158,7 @@ def send_code():
success, msg = SMSService.send_code(phone) success, msg = SMSService.send_code(phone)
if success: if success:
# 设置各种限制标记 # 设置各种限制标记
from datetime import datetime now = get_bj_now()
now = datetime.now()
seconds_until_midnight = ((23 - now.hour) * 3600) + ((59 - now.minute) * 60) + (60 - now.second) seconds_until_midnight = ((23 - now.hour) * 3600) + ((59 - now.minute) * 60) + (60 - now.second)
redis_client.setex(f"sms_lock:{phone}", 60, "1") redis_client.setex(f"sms_lock:{phone}", 60, "1")

View File

@ -1,10 +1,10 @@
from flask import Blueprint, request, redirect, url_for, session, jsonify, render_template from flask import Blueprint, request, redirect, url_for, session, jsonify, render_template
from extensions import db from extensions import db
from models import Order, User, to_bj_time from models import Order, User, to_bj_time, get_bj_now
from services.alipay_service import AlipayService from services.alipay_service import AlipayService
from services.logger import system_logger from services.logger import system_logger
import uuid import uuid
from datetime import datetime, timedelta from datetime import timedelta
payment_bp = Blueprint('payment', __name__, url_prefix='/payment') payment_bp = Blueprint('payment', __name__, url_prefix='/payment')
@ -91,7 +91,7 @@ def payment_history():
if 'user_id' not in session: if 'user_id' not in session:
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) thirty_min_ago = get_bj_now() - timedelta(minutes=30)
user_id = session['user_id'] user_id = session['user_id']
orders = Order.query.filter( orders = Order.query.filter(
@ -111,7 +111,7 @@ def api_payment_history():
if 'user_id' not in session: if 'user_id' not in session:
return jsonify({'code': 401, 'msg': '请先登录'}), 401 return jsonify({'code': 401, 'msg': '请先登录'}), 401
thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) thirty_min_ago = get_bj_now() - timedelta(minutes=30)
user_id = session['user_id'] user_id = session['user_id']
orders = Order.query.filter( orders = Order.query.filter(
@ -155,7 +155,7 @@ def payment_notify():
if order and order.status == 'PENDING': if order and order.status == 'PENDING':
order.status = 'PAID' order.status = 'PAID'
order.trade_no = trade_no order.trade_no = trade_no
order.paid_at = datetime.now() order.paid_at = get_bj_now()
user = db.session.get(User, order.user_id) user = db.session.get(User, order.user_id)
if user: if user:

View File

@ -6,18 +6,20 @@ from werkzeug.security import generate_password_hash, check_password_hash
UTC_TZ = timezone.utc UTC_TZ = timezone.utc
BEIJING_TZ = timezone(timedelta(hours=8)) BEIJING_TZ = timezone(timedelta(hours=8))
def get_bj_now():
"""获取当前北京时间(无时区信息的 naive datetime"""
return datetime.now(BEIJING_TZ).replace(tzinfo=None)
def to_bj_time(dt): def to_bj_time(dt):
"""将 UTC 时间转换为北京时间 (UTC+8) """返回北京时间
无论系统时区设置如何都能正确转换为北京时间 注意为统一时间处理现在所有时间存储都直接使用北京时间
此函数主要用于兼容性直接返回输入时间
""" """
if not dt: if not dt:
return None return None
# 如果 dt 没有时区信息,假设它是 UTC 时间 # 直接返回(数据库存储的已经是北京时间)
if dt.tzinfo is None: return dt if dt.tzinfo is None else dt.replace(tzinfo=None)
dt = dt.replace(tzinfo=UTC_TZ)
# 转换为北京时间
return dt.astimezone(BEIJING_TZ).replace(tzinfo=None)
# 角色与权限的多对多关联表 # 角色与权限的多对多关联表
@ -54,7 +56,7 @@ class User(db.Model):
is_banned = db.Column(db.Boolean, default=False) # 账号是否被封禁 is_banned = db.Column(db.Boolean, default=False) # 账号是否被封禁
# 关联角色 ID # 关联角色 ID
role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=get_bj_now)
@property @property
def created_at_bj(self): def created_at_bj(self):
@ -106,7 +108,7 @@ class GenerationRecord(db.Model):
cost = db.Column(db.Integer, default=0) # 消耗积分 cost = db.Column(db.Integer, default=0) # 消耗积分
# 存储生成的图片 URL 列表 (JSON 字符串) # 存储生成的图片 URL 列表 (JSON 字符串)
image_urls = db.Column(db.Text) image_urls = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=get_bj_now)
@property @property
def created_at_bj(self): def created_at_bj(self):
@ -128,7 +130,7 @@ class SystemNotification(db.Model):
title = db.Column(db.String(200), nullable=False) title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False) content = db.Column(db.Text, nullable=False)
is_active = db.Column(db.Boolean, default=True) is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=get_bj_now)
@property @property
def created_at_bj(self): def created_at_bj(self):
@ -148,7 +150,7 @@ class Order(db.Model):
points = db.Column(db.Integer, nullable=False) # 购买的积分 points = db.Column(db.Integer, nullable=False) # 购买的积分
status = db.Column(db.String(20), default='PENDING') # PENDING, PAID, CANCELLED status = db.Column(db.String(20), default='PENDING') # PENDING, PAID, CANCELLED
trade_no = db.Column(db.String(64)) # 支付宝交易号 trade_no = db.Column(db.String(64)) # 支付宝交易号
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=get_bj_now)
paid_at = db.Column(db.DateTime) paid_at = db.Column(db.DateTime)
@property @property
@ -178,7 +180,7 @@ class SystemLog(db.Model):
method = db.Column(db.String(10)) method = db.Column(db.String(10))
user_agent = db.Column(db.String(255)) user_agent = db.Column(db.String(255))
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=get_bj_now)
@property @property
def created_at_bj(self): def created_at_bj(self):

View File

@ -86,13 +86,16 @@ class SystemLogger:
try: try:
from models import SystemLog from models import SystemLog
# 直接存储北京时间,避免时区转换问题
bj_now = datetime.now(BEIJING_TZ).replace(tzinfo=None)
log_data = { log_data = {
'level': level, 'level': level,
'message': message, 'message': message,
'module': module, 'module': module,
'user_id': extra.get('user_id') if extra else None, 'user_id': extra.get('user_id') if extra else None,
'extra': json.dumps(extra, ensure_ascii=False) if extra else None, 'extra': json.dumps(extra, ensure_ascii=False) if extra else None,
'created_at': datetime.now(timezone.utc).replace(tzinfo=None) # 存储 UTC 时间(无时区信息) 'created_at': bj_now # 直接存储北京时间
} }
# 捕获请求上下文信息 # 捕获请求上下文信息

View File

@ -1,10 +1,10 @@
from models import GenerationRecord, Order, db, to_bj_time from models import GenerationRecord, Order, db, to_bj_time, get_bj_now
from sqlalchemy import func from sqlalchemy import func
from datetime import datetime, timedelta from datetime import timedelta
def get_point_stats(user_id, days=7): def get_point_stats(user_id, days=7):
"""获取用户积分消耗统计数据 (用于图表)""" """获取用户积分消耗统计数据 (用于图表)"""
end_date = datetime.utcnow() end_date = get_bj_now()
start_date = end_date - timedelta(days=days-1) start_date = end_date - timedelta(days=days-1)
# 1. 获取消耗统计 (从 GenerationRecord) # 1. 获取消耗统计 (从 GenerationRecord)