Compare commits

...

2 Commits

6 changed files with 34 additions and 30 deletions

View File

@ -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(

View File

@ -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")
@ -421,7 +420,7 @@ def get_logs():
for log in pagination.items:
logs_data.append({
"id": log.id,
"time": log.created_at.strftime('%Y-%m-%d %H:%M:%S'),
"time": log.created_at_bj.strftime('%Y-%m-%d %H:%M:%S'),
"level": log.level,
"message": log.message,
"module": log.module,

View File

@ -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:

View File

@ -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 to_bj_time(dt):
"""将 UTC 时间转换为北京时间 (UTC+8)
def get_bj_now():
"""获取当前北京时间(无时区信息的 naive datetime"""
return datetime.now(BEIJING_TZ).replace(tzinfo=None)
无论系统时区设置如何都能正确转换为北京时间
def to_bj_time(dt):
"""返回北京时间
注意为统一时间处理现在所有时间存储都直接使用北京时间
此函数主要用于兼容性直接返回输入时间
"""
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):

View File

@ -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 # 直接存储北京时间
}
# 捕获请求上下文信息

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 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)