From ced1020235d8296dc78493b0a100a5181924c223 Mon Sep 17 00:00:00 2001 From: 24024 <240241002@qq.com> Date: Sun, 18 Jan 2026 21:09:43 +0800 Subject: [PATCH] feat: Add admin panel for system management, new authentication and payment blueprints, logger and stats services, and related model updates. --- blueprints/admin.py | 6 +++--- blueprints/auth.py | 9 ++++----- blueprints/payment.py | 10 +++++----- models.py | 26 ++++++++++++++------------ services/logger.py | 5 ++++- services/stats_service.py | 6 +++--- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/blueprints/admin.py b/blueprints/admin.py index cdddad8..3cf21c3 100644 --- a/blueprints/admin.py +++ b/blueprints/admin.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, jsonify -from datetime import datetime, timedelta +from datetime import timedelta 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 services.logger import system_logger @@ -277,7 +277,7 @@ def delete_notification(): @admin_bp.route('/orders', methods=['GET']) @permission_required('manage_system') # 仅限超级管理员 def get_orders(): - thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) + thirty_min_ago = get_bj_now() - timedelta(minutes=30) # 过滤掉超过 30 分钟未支付的订单 orders = Order.query.filter( diff --git a/blueprints/auth.py b/blueprints/auth.py index 140f363..3babd7d 100644 --- a/blueprints/auth.py +++ b/blueprints/auth.py @@ -1,8 +1,8 @@ from flask import Blueprint, request, jsonify, session, render_template, redirect, url_for import json -from datetime import datetime, timedelta +from datetime import timedelta from extensions import db -from models import User +from models import User, get_bj_now from services.sms_service import SMSService from services.captcha_service import CaptchaService from services.logger import system_logger @@ -57,7 +57,7 @@ def buy_page(): user_id = session['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 分钟未支付的订单) personal_orders = Order.query.filter( @@ -158,8 +158,7 @@ def send_code(): success, msg = SMSService.send_code(phone) if success: # 设置各种限制标记 - from datetime import datetime - now = datetime.now() + now = get_bj_now() seconds_until_midnight = ((23 - now.hour) * 3600) + ((59 - now.minute) * 60) + (60 - now.second) redis_client.setex(f"sms_lock:{phone}", 60, "1") diff --git a/blueprints/payment.py b/blueprints/payment.py index 3e8018d..ae426a2 100644 --- a/blueprints/payment.py +++ b/blueprints/payment.py @@ -1,10 +1,10 @@ from flask import Blueprint, request, redirect, url_for, session, jsonify, render_template 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.logger import system_logger import uuid -from datetime import datetime, timedelta +from datetime import timedelta payment_bp = Blueprint('payment', __name__, url_prefix='/payment') @@ -91,7 +91,7 @@ def payment_history(): if 'user_id' not in session: 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'] orders = Order.query.filter( @@ -111,7 +111,7 @@ def api_payment_history(): if 'user_id' not in session: 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'] orders = Order.query.filter( @@ -155,7 +155,7 @@ def payment_notify(): if order and order.status == 'PENDING': order.status = 'PAID' order.trade_no = trade_no - order.paid_at = datetime.now() + order.paid_at = get_bj_now() user = db.session.get(User, order.user_id) if user: diff --git a/models.py b/models.py index 8305a02..b69e1e9 100644 --- a/models.py +++ b/models.py @@ -6,18 +6,20 @@ from werkzeug.security import generate_password_hash, check_password_hash UTC_TZ = timezone.utc 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): - """将 UTC 时间转换为北京时间 (UTC+8) + """返回北京时间 - 无论系统时区设置如何,都能正确转换为北京时间 + 注意:为统一时间处理,现在所有时间存储都直接使用北京时间。 + 此函数主要用于兼容性,直接返回输入时间。 """ if not dt: return None - # 如果 dt 没有时区信息,假设它是 UTC 时间 - if dt.tzinfo is None: - dt = dt.replace(tzinfo=UTC_TZ) - # 转换为北京时间 - return dt.astimezone(BEIJING_TZ).replace(tzinfo=None) + # 直接返回(数据库存储的已经是北京时间) + return dt if dt.tzinfo is None else dt.replace(tzinfo=None) # 角色与权限的多对多关联表 @@ -54,7 +56,7 @@ class User(db.Model): is_banned = db.Column(db.Boolean, default=False) # 账号是否被封禁 # 关联角色 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 def created_at_bj(self): @@ -106,7 +108,7 @@ class GenerationRecord(db.Model): cost = db.Column(db.Integer, default=0) # 消耗积分 # 存储生成的图片 URL 列表 (JSON 字符串) 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 def created_at_bj(self): @@ -128,7 +130,7 @@ class SystemNotification(db.Model): title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) 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 def created_at_bj(self): @@ -148,7 +150,7 @@ class Order(db.Model): points = db.Column(db.Integer, nullable=False) # 购买的积分 status = db.Column(db.String(20), default='PENDING') # PENDING, PAID, CANCELLED 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) @property @@ -178,7 +180,7 @@ class SystemLog(db.Model): method = db.Column(db.String(10)) 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 def created_at_bj(self): diff --git a/services/logger.py b/services/logger.py index bb06e42..6fed2fc 100644 --- a/services/logger.py +++ b/services/logger.py @@ -86,13 +86,16 @@ class SystemLogger: try: from models import SystemLog + # 直接存储北京时间,避免时区转换问题 + bj_now = datetime.now(BEIJING_TZ).replace(tzinfo=None) + log_data = { 'level': level, 'message': message, 'module': module, 'user_id': extra.get('user_id') 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 # 直接存储北京时间 } # 捕获请求上下文信息 diff --git a/services/stats_service.py b/services/stats_service.py index a56d26c..1a8c05a 100644 --- a/services/stats_service.py +++ b/services/stats_service.py @@ -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 datetime import datetime, timedelta +from datetime import timedelta 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) # 1. 获取消耗统计 (从 GenerationRecord)