update pay ui

This commit is contained in:
2025-12-13 09:55:27 +08:00
parent cc33dd29eb
commit 1949d9b922

252
app_vx.py
View File

@@ -2732,6 +2732,7 @@ def api_get_events():
end_date = request.args.get('end_date') end_date = request.args.get('end_date')
date_range = request.args.get('date_range') date_range = request.args.get('date_range')
recent_days = request.args.get('recent_days', type=int) recent_days = request.args.get('recent_days', type=int)
time_filter = request.args.get('time_filter') # 时间快速筛选参数
# 行业筛选参数(重新设计) # 行业筛选参数(重新设计)
ind_type = request.args.get('ind_type', 'all') ind_type = request.args.get('ind_type', 'all')
@@ -2792,9 +2793,13 @@ def api_get_events():
if event_type != 'all': if event_type != 'all':
query = query.filter_by(event_type=event_type) query = query.filter_by(event_type=event_type)
# 重要性筛选 # 重要性筛选(支持多选,逗号分隔,如 importance=S,A,B
if importance != 'all': if importance != 'all':
query = query.filter_by(importance=importance) importance_list = [i.strip().upper() for i in importance.split(',') if i.strip()]
if len(importance_list) == 1:
query = query.filter_by(importance=importance_list[0])
elif len(importance_list) > 1:
query = query.filter(Event.importance.in_(importance_list))
# 行业类型筛选使用ind_type字段 # 行业类型筛选使用ind_type字段
if ind_type != 'all': if ind_type != 'all':
@@ -2806,41 +2811,117 @@ def api_get_events():
# ==================== 日期筛选 ==================== # ==================== 日期筛选 ====================
if recent_days: # 时间快速筛选(优先级最高)
cutoff_date = datetime.now() - timedelta(days=recent_days) time_filter_applied = False
query = query.filter(Event.created_at >= cutoff_date) if time_filter:
else: now = datetime.now()
# 处理日期范围字符串 today = now.date()
if date_range and '' in date_range:
try:
start_date_str, end_date_str = date_range.split('')
start_date = start_date_str.strip()
end_date = end_date_str.strip()
except ValueError:
pass
# 开始日期 if time_filter == 'latest':
if start_date: # 最新最近100条不设时间筛选但限制数量在后面排序后处理
try: time_filter_applied = True
if len(start_date) == 10: # 特殊处理latest模式下per_page强制为100忽略分页
start_datetime = datetime.strptime(start_date, '%Y-%m-%d') per_page = 100
else: page = 1
start_datetime = datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S')
query = query.filter(Event.created_at >= start_datetime)
except ValueError:
pass
# 结束日期 elif time_filter == 'intraday':
if end_date: # 盘中从今天早上9:30到当前时间
try: start_time = datetime.combine(today, datetime.strptime('09:30', '%H:%M').time())
if len(end_date) == 10: query = query.filter(Event.created_at >= start_time)
end_datetime = datetime.strptime(end_date, '%Y-%m-%d') query = query.filter(Event.created_at <= now)
end_datetime = end_datetime.replace(hour=23, minute=59, second=59) time_filter_applied = True
else:
end_datetime = datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S') elif time_filter == 'morning':
query = query.filter(Event.created_at <= end_datetime) # 早盘从今天早上9:30到11:30上午盘交易时段
except ValueError: start_time = datetime.combine(today, datetime.strptime('09:30', '%H:%M').time())
pass end_time = datetime.combine(today, datetime.strptime('11:30', '%H:%M').time())
query = query.filter(Event.created_at >= start_time)
query = query.filter(Event.created_at <= end_time)
time_filter_applied = True
elif time_filter == 'afternoon':
# 午盘从今天上午11:30至今下午盘交易时段开始后
start_time = datetime.combine(today, datetime.strptime('11:30', '%H:%M').time())
query = query.filter(Event.created_at >= start_time)
query = query.filter(Event.created_at <= now)
time_filter_applied = True
elif time_filter == 'today':
# 今日全天从昨天15:00到现在
yesterday = today - timedelta(days=1)
start_time = datetime.combine(yesterday, datetime.strptime('15:00', '%H:%M').time())
query = query.filter(Event.created_at >= start_time)
query = query.filter(Event.created_at <= now)
time_filter_applied = True
elif time_filter == 'yesterday':
# 昨日:上一个交易日的完整数据
# 计算上一个交易日(跳过周末)
def get_previous_trading_day(d):
d = d - timedelta(days=1)
while d.weekday() >= 5: # 5=周六, 6=周日
d = d - timedelta(days=1)
return d
last_trading_day = get_previous_trading_day(today)
day_before_last = get_previous_trading_day(last_trading_day)
# 从上上个交易日15:00到上个交易日15:00
start_time = datetime.combine(day_before_last, datetime.strptime('15:00', '%H:%M').time())
end_time = datetime.combine(last_trading_day, datetime.strptime('15:00', '%H:%M').time())
query = query.filter(Event.created_at >= start_time)
query = query.filter(Event.created_at <= end_time)
time_filter_applied = True
elif time_filter == 'week':
# 近一周自然日7天内
start_time = datetime.combine(today - timedelta(days=7), datetime.min.time())
query = query.filter(Event.created_at >= start_time)
time_filter_applied = True
elif time_filter == 'month':
# 近一月自然日30天内
start_time = datetime.combine(today - timedelta(days=30), datetime.min.time())
query = query.filter(Event.created_at >= start_time)
time_filter_applied = True
# 如果没有使用time_filter则使用其他日期筛选方式
if not time_filter_applied:
if recent_days:
cutoff_date = datetime.now() - timedelta(days=recent_days)
query = query.filter(Event.created_at >= cutoff_date)
else:
# 处理日期范围字符串
if date_range and '' in date_range:
try:
start_date_str, end_date_str = date_range.split('')
start_date = start_date_str.strip()
end_date = end_date_str.strip()
except ValueError:
pass
# 开始日期
if start_date:
try:
if len(start_date) == 10:
start_datetime = datetime.strptime(start_date, '%Y-%m-%d')
else:
start_datetime = datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S')
query = query.filter(Event.created_at >= start_datetime)
except ValueError:
pass
# 结束日期
if end_date:
try:
if len(end_date) == 10:
end_datetime = datetime.strptime(end_date, '%Y-%m-%d')
end_datetime = end_datetime.replace(hour=23, minute=59, second=59)
else:
end_datetime = datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S')
query = query.filter(Event.created_at <= end_datetime)
except ValueError:
pass
# ==================== 行业层级筛选(申银万国行业分类) ==================== # ==================== 行业层级筛选(申银万国行业分类) ====================
@@ -3189,6 +3270,8 @@ def api_get_events():
applied_filters['type'] = event_type applied_filters['type'] = event_type
if importance != 'all': if importance != 'all':
applied_filters['importance'] = importance applied_filters['importance'] = importance
if time_filter:
applied_filters['time_filter'] = time_filter
if start_date: if start_date:
applied_filters['start_date'] = start_date applied_filters['start_date'] = start_date
if end_date: if end_date:
@@ -4057,25 +4140,34 @@ def api_login_wechat():
_WECHAT_ACCESS_TOKEN_KEY = 'vf_wechat_access_token' _WECHAT_ACCESS_TOKEN_KEY = 'vf_wechat_access_token'
def get_wechat_access_token(): def get_wechat_access_token(force_refresh=False):
""" """
获取微信小程序 access_token使用 Redis 缓存,支持多 worker 共享) 获取微信小程序 access_token使用 Redis 缓存,支持多 worker 共享)
access_token 有效期为 7200 秒,提前 5 分钟刷新 access_token 有效期为 7200 秒,提前 5 分钟刷新
Args:
force_refresh: 是否强制刷新(当 token 失效时使用)
""" """
import time import time
redis_client = None
try: try:
import redis import redis
redis_client = redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379/0')) redis_client = redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379/0'))
# 尝试从 Redis 获取缓存的 token # 如果不是强制刷新,尝试从 Redis 获取缓存的 token
cached = redis_client.get(_WECHAT_ACCESS_TOKEN_KEY) if not force_refresh:
if cached: cached = redis_client.get(_WECHAT_ACCESS_TOKEN_KEY)
logger.debug("从 Redis 获取微信 access_token") if cached:
return cached.decode('utf-8') logger.debug("从 Redis 获取微信 access_token")
return cached.decode('utf-8')
else:
# 强制刷新时,先删除旧的缓存
redis_client.delete(_WECHAT_ACCESS_TOKEN_KEY)
logger.info("强制刷新:已删除旧的 access_token 缓存")
except Exception as e: except Exception as e:
logger.warning(f"Redis 获取 access_token 失败: {e},将直接请求微信接口") logger.warning(f"Redis 操作失败: {e},将直接请求微信接口")
redis_client = None redis_client = None
# 请求新的 access_token # 请求新的 access_token
@@ -4153,44 +4245,48 @@ def api_bindphone_wechat():
'data': None 'data': None
}), 400 }), 400
# 1. 获取 access_token # 调用微信接口获取手机号(支持 token 失效自动重试)
access_token = get_wechat_access_token() def call_wechat_phone_api(force_refresh=False):
if not access_token: access_token = get_wechat_access_token(force_refresh=force_refresh)
return jsonify({ if not access_token:
'code': 500, return None, {'errcode': -1, 'errmsg': '获取 access_token 失败'}
'message': '获取微信凭证失败,请稍后重试',
'data': None
}), 500
# 2. 调用微信接口获取手机号 wx_phone_url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}'
wx_phone_url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}' payload = {'code': code}
payload = {'code': code}
try: try:
response = requests.post(wx_phone_url, json=payload, timeout=10) response = requests.post(wx_phone_url, json=payload, timeout=10)
result = response.json() return access_token, response.json()
logger.info(f"微信获取手机号响应: {result}") except Exception as e:
except Exception as e: logger.error(f"调用微信获取手机号接口异常: {e}")
logger.error(f"调用微信获取手机号接口异常: {e}") return access_token, {'errcode': -1, 'errmsg': str(e)}
return jsonify({
'code': 500,
'message': '调用微信接口失败,请稍后重试',
'data': None
}), 500
# 3. 解析微信返回结果 # 第一次尝试
if result.get('errcode') != 0: access_token, result = call_wechat_phone_api(force_refresh=False)
logger.info(f"微信获取手机号响应: {result}")
# 如果 token 失效40001, 42001强制刷新后重试一次
errcode = result.get('errcode')
if errcode in (40001, 42001, 40014):
logger.warning(f"access_token 失效 (errcode={errcode}),强制刷新后重试")
access_token, result = call_wechat_phone_api(force_refresh=True)
logger.info(f"重试后微信获取手机号响应: {result}")
errcode = result.get('errcode')
# 解析微信返回结果
if errcode != 0:
error_msg = result.get('errmsg', '未知错误') error_msg = result.get('errmsg', '未知错误')
logger.error(f"微信获取手机号失败: errcode={result.get('errcode')}, errmsg={error_msg}") logger.error(f"微信获取手机号失败: errcode={errcode}, errmsg={error_msg}")
# 常见错误码处理 # 常见错误码处理
errcode = result.get('errcode')
if errcode == 40029: if errcode == 40029:
return jsonify({'code': 400, 'message': 'code无效或已过期请重新获取', 'data': None}), 400 return jsonify({'code': 400, 'message': 'code无效或已过期请重新获取', 'data': None}), 400
elif errcode == 40013: elif errcode == 40013:
return jsonify({'code': 400, 'message': 'AppID无效', 'data': None}), 400 return jsonify({'code': 400, 'message': 'AppID无效', 'data': None}), 400
elif errcode == -1: elif errcode == -1:
return jsonify({'code': 500, 'message': '微信服务繁忙,请稍后重试', 'data': None}), 500 return jsonify({'code': 500, 'message': '微信服务繁忙,请稍后重试', 'data': None}), 500
elif errcode in (40001, 42001, 40014):
return jsonify({'code': 500, 'message': '微信凭证失效,请稍后重试', 'data': None}), 500
else: else:
return jsonify({'code': 400, 'message': f'获取手机号失败: {error_msg}', 'data': None}), 400 return jsonify({'code': 400, 'message': f'获取手机号失败: {error_msg}', 'data': None}), 400
@@ -5807,9 +5903,17 @@ def api_calendar_events():
if end_date: if end_date:
query += " AND calendar_time <= :end_date" query += " AND calendar_time <= :end_date"
params['end_date'] = datetime.fromisoformat(end_date) params['end_date'] = datetime.fromisoformat(end_date)
# 重要性筛选(支持多选,逗号分隔,如 importance=S,A,B
if importance != 'all': if importance != 'all':
query += " AND star = :importance" importance_list = [i.strip().upper() for i in importance.split(',') if i.strip()]
params['importance'] = importance if len(importance_list) == 1:
query += " AND star = :importance"
params['importance'] = importance_list[0]
elif len(importance_list) > 1:
placeholders = ', '.join([f':imp_{i}' for i in range(len(importance_list))])
query += f" AND star IN ({placeholders})"
for i, imp in enumerate(importance_list):
params[f'imp_{i}'] = imp
if category != 'all': if category != 'all':
# category参数用于筛选inferred_tag字段如"大周期"、"大消费"等) # category参数用于筛选inferred_tag字段如"大周期"、"大消费"等)
query += " AND inferred_tag = :category" query += " AND inferred_tag = :category"
@@ -5847,8 +5951,14 @@ def api_calendar_events():
count_query += " AND calendar_time >= :start_date" count_query += " AND calendar_time >= :start_date"
if end_date: if end_date:
count_query += " AND calendar_time <= :end_date" count_query += " AND calendar_time <= :end_date"
# 重要性筛选(支持多选,逗号分隔)
if importance != 'all': if importance != 'all':
count_query += " AND star = :importance" importance_list = [i.strip().upper() for i in importance.split(',') if i.strip()]
if len(importance_list) == 1:
count_query += " AND star = :importance"
elif len(importance_list) > 1:
placeholders = ', '.join([f':imp_{i}' for i in range(len(importance_list))])
count_query += f" AND star IN ({placeholders})"
if category != 'all': if category != 'all':
count_query += " AND inferred_tag = :category" count_query += " AND inferred_tag = :category"