小程序在9月27日发布了正式版

This commit is contained in:
qcloud
2025-10-01 08:29:09 +08:00
parent 8d90798647
commit c44e6715a0
15 changed files with 6679 additions and 25074 deletions

View File

@@ -324,7 +324,7 @@ def mesum_list(tenant_id):
# model_type = request.args.get("type")
try:
res = []
overviews=MesumOverviewService.get_all()
overviews=MesumOverviewService.get_all(reverse=False,order_by ='sort_order')
for o in overviews:
res.append(o.to_dict())
return get_result(data=res)

View File

@@ -991,6 +991,7 @@ class CanvasTemplate(DataBaseModel):
# ------------added by cyx for mesum overview
class MesumOverview(DataBaseModel):
id = BigIntegerField(primary_key=True,null=False) # 非自增主键
name = CharField(
max_length=128,
null=False,
@@ -1043,6 +1044,14 @@ class MesumOverview(DataBaseModel):
free = IntegerField(default=0, index=False)
hotspot_rank = IntegerField(default=50, index=False)
functions = CharField(
max_length=300,
null=False,
help_text="此场所软件开放的功能",
index=False)
sort_order = IntegerField(default=0, null=False) #
def __str__(self):
return self.name
@@ -1063,6 +1072,8 @@ class MesumAntique(DataBaseModel):
ttsUrl_child = CharField(max_length=256, null=True)
photo_url = CharField(max_length=256, null=True)
orgin_text = TextField(null=True)
tags = CharField(max_length=200, null=True)
sort_order = IntegerField(default=0) # 添加sort_order字段
class Meta:
db_table = 'mesum_antique'

View File

@@ -25,7 +25,7 @@ from api.db.services.common_service import CommonService
from api.db.services.brief_service import MesumOverviewService
from api.utils import get_uuid, get_format_time, current_timestamp, datetime_format
from api.db import StatusEnum
import logging
import logging,json
class MesumAntiqueService(CommonService):
model = MesumAntique
@@ -33,8 +33,25 @@ class MesumAntiqueService(CommonService):
@classmethod
@DB.connection_context()
def get_by_mesum_id(cls, mesum_id):
objs = cls.query(mesum_id=mesum_id)
return objs
# objs = cls.query(mesum_id=mesum_id)
# return objs
"""
根据mesum_id查询记录并按category和sort_order排序
"""
try:
objs = (cls.model
.select()
.where(cls.model.mesum_id == mesum_id)
.order_by(
cls.model.category, # 一级排序按category
cls.model.sort_order.asc() # 二级排序sort_order升序
)
.execute())
return list(objs)
except Exception as e:
print(f"根据mesum_id查询失败: {e}")
return []
@classmethod
@DB.connection_context()
@@ -44,6 +61,15 @@ class MesumAntiqueService(CommonService):
try:
mesum_brief = MesumOverviewService.query(id=mesum_id)
if mesum_brief:
"""
20250922 数据库中category 改为如下形式
[{"name":"第一展厅(新石器-唐、战国、秦汉)","audio":"xxxx","photo":"xxx"},
{"name":"第二展厅 (辽金)","audio":"xxx","photo":""},
{"name":"第三展厅(元)","audio":"xxx","photo":""},
{"name":"第四展厅(明清-至今)","audio":"xxx","photo":""}]
"""
"""
categories_text= mesum_brief[0].category
# 统一替换中文分号为英文分号,并去除末尾分号
if categories_text:
@@ -51,6 +77,9 @@ class MesumAntiqueService(CommonService):
# 分割并清理空格/空值
mesum_antique_categories = [dynasty.strip() for dynasty in categories_text.split(";") if dynasty.strip()]
"""
mesum_category_json = json.loads(mesum_brief[0].category.strip())
mesum_antique_categories = [item["name"] for item in mesum_category_json]
finally:
pass
categories = [category.category
@@ -82,13 +111,61 @@ class MesumAntiqueService(CommonService):
@classmethod
@DB.connection_context()
def get_labels_ext(cls, mesum_id):
def get_labels_ext(cls, mesum_id,tags = None):
# 根据mesum_id过滤并排除空的category
query = cls.model.select().where(
"""query = cls.model.select().where(
(cls.model.mesum_id == mesum_id) &
(cls.model.category != "")
).order_by(cls.model.category)
).order_by(
cls.model.category, # 一级排序按category
cls.model.sort_order.asc() # 二级排序sort_order升序
)
"""
# 基础查询条件
conditions = [
(cls.model.mesum_id == mesum_id),
(cls.model.category != ""),
(cls.model.category.is_null(False)) # 确保category不为NULL
]
# 添加tags条件
if tags is not None:
# 如果tags是字符串按逗号分割处理
if isinstance(tags, str):
tags_list = [tag.strip() for tag in tags.split(',') if tag.strip()]
elif isinstance(tags, list):
tags_list = tags
else:
tags_list = []
if tags_list:
# 构建tags匹配条件tags包含任意一个指定的标签
tag_conditions = []
for tag in tags_list:
# 匹配tags字段包含该标签考虑逗号分隔的情况
tag_conditions.append(
(cls.model.tags.contains(tag)) |
(cls.model.tags.startswith(f"{tag},")) |
(cls.model.tags.endswith(f",{tag}")) |
(cls.model.tags == tag)
)
# 将所有tag条件用OR连接
tags_condition = reduce(lambda a, b: a | b, tag_conditions)
conditions.append(tags_condition)
else:
# 如果tags为空但传入了参数确保tags不为空
conditions.append(
(cls.model.tags != "") &
(cls.model.tags.is_null(False))
)
else:
# 如果未传入tags参数包含所有tags记录包括空值
pass
# 执行查询,添加二级排序
query = cls.model.select().where(*conditions).order_by(
cls.model.category, # 一级排序按category
cls.model.sort_order.asc() # 二级排序按sort_order升序
)
# 按category分组并存储结果
grouped_data = {}
for obj in query.dicts().execute():

View File

@@ -14,7 +14,7 @@
# limitations under the License.
#
from datetime import datetime
import logging
import peewee
from api.db.db_models import DB
@@ -38,7 +38,7 @@ class CommonService:
else:
query_records = cls.model.select()
if reverse is not None:
if not order_by or not hasattr(cls, order_by):
if not order_by or not hasattr(cls.model, order_by): # 20250923 这里之前使用cls 而没有model是错误的
order_by = "create_time"
if reverse is True:
query_records = query_records.order_by(

File diff suppressed because it is too large Load Diff

View File

@@ -147,7 +147,7 @@ def create_museum(data: dict):
return execute_query(sql, params)
def get_museums(search: str = None, free: int = None):
def get_museums(search: str = None, free: int = None, id_list: Union[None, int, List[int]] = None):
base_sql = "SELECT * FROM mesum_overview WHERE 1=1"
params = []
@@ -159,11 +159,30 @@ def get_museums(search: str = None, free: int = None):
base_sql += " AND free = %s"
params.append(free)
# 处理 id_list 参数
if id_list is not None:
# 如果 id_list 是单个整数,转换为列表
if isinstance(id_list, int):
id_list = [id_list]
# 如果 id_list 是列表且不为空
if isinstance(id_list, list) and len(id_list) > 0:
# 创建占位符字符串,例如 "%s, %s, %s"
placeholders = ", ".join(["%s"] * len(id_list))
base_sql += f" AND id IN ({placeholders})"
params.extend(id_list)
if isinstance(id_list, str):
id_list = [int(x) for x in id_list.split(',') if x.strip().isdigit()]
# 创建占位符字符串,例如 "%s, %s, %s"
placeholders = ", ".join(["%s"] * len(id_list))
base_sql += f" AND id IN ({placeholders})"
params.extend(id_list)
base_sql += " ORDER BY create_time DESC"
return execute_query(base_sql, tuple(params))
def update_museum(museum_id: int, data: dict):
"""动态更新博物馆信息仅更新data中包含的字段"""
allowed_fields = {
@@ -940,7 +959,7 @@ def get_subscription_template_by_id(template_id: int) -> dict:
return result[0] if result else None
def get_active_user_subscription(user_id: str, museum_id: int) -> dict:
def get_user_subscription(user_id: str, museum_id: int) -> dict:
"""
获取用户在指定博物馆的有效订阅
@@ -975,6 +994,82 @@ def get_active_user_subscription(user_id: str, museum_id: int) -> dict:
return result[0] if result else None
def get_all_users_subscriptions_paginated(
museum_id: Optional[int] = None,
page: int = 1,
page_size: int = 50
) -> Dict[str, any]:
"""
获取所有用户的有效订阅(分页版)
参数说明:
- museum_id: 博物馆ID如果为None则查询所有博物馆
- page: 页码从1开始
- page_size: 每页记录数
返回:
- 包含订阅列表和分页信息的字典
"""
offset = (page - 1) * page_size
# 基础查询条件
#where_conditions = ["us.is_active = 1", "us.end_date > NOW()"]
where_conditions = []
params = []
if museum_id is not None:
where_conditions.append("ms.museum_id = %s")
params.append(museum_id)
where_clause = " AND ".join(where_conditions)
# 获取总数
count_sql = f"""
SELECT COUNT(*) as total
FROM user_subscriptions us
JOIN museum_subscriptions ms ON us.museum_subscription_id = ms.sub_id
WHERE {where_clause}
"""
total_result = execute_query(count_sql, params)
total = total_result[0]['total'] if total_result else 0
# 获取分页数据
data_sql = f"""
SELECT
us.*,
st.name as template_name,
st.description as template_description,
st.validity_type as template_validity_type,
ui.openid as openid,
ui.phone as phone,
ms.museum_id,
ms.price as price,
m.name as museum_name
FROM user_subscriptions us
JOIN museum_subscriptions ms ON us.museum_subscription_id = ms.sub_id
JOIN subscription_templates st ON ms.template_id = st.id
JOIN users_info ui ON us.user_id = ui.user_id
LEFT JOIN mesum_overview m ON ms.museum_id = m.id
WHERE {where_clause}
ORDER BY us.create_date, ms.museum_id, us.user_id ASC
LIMIT %s OFFSET %s
"""
# 添加分页参数
data_params = params + [page_size, offset]
data_result = execute_query(data_sql, data_params)
return {
"data": data_result if data_result else [],
"pagination": {
"page": page,
"page_size": page_size,
"total": total,
"total_pages": (total + page_size - 1) // page_size if page_size > 0 else 0
}
}
def get_user_subscription_history(user_id: str) -> list:
"""
获取用户的订阅历史记录
@@ -1191,7 +1286,7 @@ def check_user_subscription(user_id: str, museum_id: int) -> dict:
- 如果没有,检查免费订阅是否可用
"""
# 1. 检查当前有效订阅
active_sub = get_active_user_subscription(user_id, museum_id)
active_sub = get_user_subscription(user_id, museum_id)
if active_sub:
return active_sub
@@ -1288,6 +1383,15 @@ def calculate_subscription_expiry(start_date: datetime, validity_type: str) -> d
elif validity_type == "1month":
# 下个月的同一天(自动处理月末情况)
return start_date + relativedelta(months=1)
elif validity_type == "2month":
# 下个月的同一天(自动处理月末情况)
return start_date + relativedelta(months=2)
elif validity_type == "3month":
# 下个月的同一天(自动处理月末情况)
return start_date + relativedelta(months=3)
elif validity_type == "6month":
# 下个月的同一天(自动处理月末情况)
return start_date + relativedelta(months=6)
elif validity_type == "1year":
# 下一年的同一天
return start_date + relativedelta(years=1)
@@ -1359,3 +1463,26 @@ def is_subscription_valid(subscription: dict) -> bool:
pass
return True
# 查询用户(多条件)
def get_admin_account_info(status: int = None, email: str = None, phone: str = None):
base_sql = "SELECT * FROM rag_flow.admin_account WHERE 1=1"
params = []
if status is not None:
base_sql += " AND status = %s"
params.append(status)
if email:
base_sql += " AND email = %s"
params.append(email)
if phone:
base_sql += " AND phone = %s"
params.append(phone)
base_sql += " ORDER BY create_time DESC"
return execute_query(base_sql, tuple(params))

View File

@@ -241,6 +241,8 @@ fake_phone_number = {
'hxbtest004':'19912345634',
'hxbtest005':'19912345635',
}
@login_router.post("/testAccountLogin")
async def test_account_login(request: Request):
# 获取原始请求数据
@@ -407,6 +409,14 @@ async def verify_token(user: dict = Depends(optional_current_user)):
"""
logging.info(f"verify_token user={user}")
if user:
try:
update_data = {
"last_login_time": int(datetime.now().timestamp())
}
updated_user = update_user(user["user_id"], update_data)
except Exception as e:
pass
return JSONResponse({
"valid": True,
"user": {

View File

@@ -20,6 +20,7 @@ from app.tts_service import tts_router,tts_lifespan
from app.login_service import login_router
from app.chat_service import chat_router
from app.payment_service import payment_router
from app.system_admin import system_admin_router
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -60,6 +61,7 @@ app.include_router(tts_router, prefix="/tts")
app.include_router(login_router, prefix="/auth")
app.include_router(chat_router, prefix="/chat")
app.include_router(payment_router, prefix="/payment")
app.include_router(system_admin_router,prefix="/system_admin")
# 挂载静态文件(可选)
# app.mount("/static", StaticFiles(directory="static"), name="static")

View File

@@ -8,6 +8,7 @@ import xml.etree.ElementTree as ET
import requests
import time
from datetime import datetime, timedelta, date
from dateutil.relativedelta import relativedelta
from decimal import Decimal
from uuid import UUID
import json
@@ -553,6 +554,7 @@ async def get_order_detial(
"msg": "success",
"data": result})
# 用户是否有权访问这个博物馆
@payment_router.post("/get_user_museum_subscriptions")
async def get_user_museum_subscriptions(
request: Request,
@@ -959,41 +961,6 @@ async def deliver_virtual_goods(order: dict) -> bool:
logger.exception(f"发货处理异常: {str(e)}")
return False
# 套餐激活函数
def activate_user_product(user_id: str, product_id: int, museum_id: int, order_id: str):
product = get_product_by_id(product_id)
if not product:
return
# 计算有效期
start_time = datetime.now()
end_time = calculate_expiry(start_time, product["validity_type"])
# 创建用户套餐记录
create_user_product({
"user_id": user_id,
"product_id": product_id,
"museum_id": museum_id,
"order_id": order_id,
"start_time": start_time,
"end_time": end_time,
"is_active": True
})
# 禁用同一博物馆的旧套餐
deactivate_previous_products(user_id, museum_id)
def calculate_expiry(start_time: datetime, validity_type: str) -> datetime:
if validity_type == "free":
return start_time + timedelta(days=7) # 免费套餐7天
elif validity_type == "1month":
return start_time + timedelta(days=30)
elif validity_type == "1year":
return start_time + timedelta(days=365)
else: # permanent
return datetime(2999, 12, 31)
async def generate_wx_prepay_params_v3(order_id: str, total_fee: int, openid: str, body: str):
"""微信支付v3统一下单"""
@@ -1194,6 +1161,128 @@ async def query_order_by_out_trade_no(out_trade_no: str):
except Exception as e:
logging.error(f"订单查询异常: {str(e)}", exc_info=True)
return None
"""
20250929 临时增加生成1个临时的收款二维码用于测试学校智能水表收费
"""
@payment_router.post("/create_temp_qrcode_simple")
async def create_temp_qrcode_simple(
request: Request
):
"""
生成临时收款二维码(简化版,不创建订单)
请求参数: {"amount": 金额(元), "description": "商品描述"}
返回: 二维码URL
"""
try:
data = await request.json()
amount = data.get("amount")
description = data.get("description", "临时收款")
if not amount or float(amount) <= 0:
raise HTTPException(status_code=400, detail="金额必须大于0")
# 生成临时订单ID仅用于支付不存储
order_id = f"SMART_WATER_{int(time.time())}{random.randint(1000, 9999)}"
amount_float = float(amount)
amount_in_cents = int(amount_float * 100) # 转换为分
# 生成Native支付二维码
try:
qr_code_url = await generate_simple_native_qrcode(
order_id=order_id,
total_fee=amount_in_cents,
body=description
)
except Exception as error:
logger.info(f"生成支付二维码失败{error}")
if not qr_code_url:
raise HTTPException(status_code=500, detail="生成支付二维码失败")
return JSONResponse({
"code": 0,
"msg": "success",
"data": {
"order_id": order_id, # 返回订单ID用于测试查询
"amount": amount_float,
"description": description,
"qr_code_url": qr_code_url,
"expire_time": (datetime.now() + timedelta(hours=4)).isoformat(),
"note": "此为测试二维码,支付成功后不会激活任何服务"
}
})
except ValueError:
raise HTTPException(status_code=400, detail="金额格式错误")
except Exception as e:
logger.error(f"生成临时收款二维码失败: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}")
async def generate_simple_native_qrcode(order_id: str, total_fee: int, body: str):
"""生成简化的Native支付二维码"""
try:
nonce_str = generate_nonce_str(32)
params = {
"appid": WX_APPID,
"mch_id": WX_MCH_ID,
"nonce_str": nonce_str,
"body": f"测试-{body}"[:128], # 添加测试前缀
"out_trade_no": order_id,
"total_fee": str(total_fee),
"spbill_create_ip": "127.0.0.1",
"notify_url": WX_PAY_NOTIFY_URL, # 使用相同的回调接口
"trade_type": "NATIVE",
"time_expire": (datetime.now() + timedelta(hours=4)).strftime('%Y%m%d%H%M%S') # 4小时过期
}
# 生成签名
params["sign"] = generate_sign_v2(params, WX_PAY_KEY)
# 转换为XML
xml_data = dict_to_xml(params)
try:
# 调用微信统一下单接口
response = requests.post(
"https://api.mch.weixin.qq.com/pay/unifiedorder",
data=xml_data.encode('utf-8'),
headers={"Content-Type": "application/xml"},
timeout=10
)
except Exception as error:
logging.info(f"调用生成支持二维码失败:{error}")
logging.info(f"res={response}")
if response.status_code != 200:
logger.error(f"Native支付接口错误: {response.status_code}")
return None
response_data = xml_to_dict(response.text)
if response_data.get("return_code") != "SUCCESS":
error_msg = response_data.get("return_msg", "未知错误")
logger.error(f"Native支付下单失败: {error_msg}")
return None
if response_data.get("result_code") != "SUCCESS":
error_code = response_data.get("err_code", "")
error_msg = response_data.get("err_code_des", "未知错误")
logger.error(f"Native支付业务失败: [{error_code}] {error_msg}")
return None
# 返回二维码链接
code_url = response_data.get("code_url")
if code_url:
logger.info(f"生成测试二维码成功: {order_id}, 金额: {total_fee}")
return code_url
else:
logger.error("Native支付返回缺少code_url")
return None
except Exception as e:
logger.error(f"生成测试二维码异常: {str(e)}", exc_info=True)
return None
"""
需要在 database.py 中实现以下函数:

View File

@@ -0,0 +1,231 @@
from fastapi import APIRouter, Depends, HTTPException, status, Request, Response,Query
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.security import OAuth2PasswordBearer
import hashlib
import random
import string
import xml.etree.ElementTree as ET
import requests
import time
from datetime import datetime, timedelta, date
from decimal import Decimal
from uuid import UUID
import json
import logging
from app.database import *
from jose import JWTError, jwt
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.backends import default_backend
import httpx,threading,asyncio
class CustomJSONResponse(JSONResponse):
"""
自定义 JSON 响应类,处理特殊类型:
- datetime: 转换为 ISO 8601 字符串
- date: 转换为 ISO 8601 字符串
- Decimal: 转换为 float
"""
def render(self, content: any) -> bytes:
"""
重写渲染方法,使用自定义编码器
"""
class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, obj):
"""
增强型 JSON 编码器,处理多种特殊类型:
- datetime: 转换为 ISO 8601 字符串
- date: 转换为 ISO 8601 字符串
- time: 转换为 ISO 8601 字符串
- Decimal: 转换为 float
- UUID: 转换为字符串
- numpy 类型: 转换为 Python 原生类型
"""
# 处理日期时间类型
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, date):
return obj.isoformat()
# 处理 Decimal 类型
if isinstance(obj, Decimal):
return float(obj)
# 处理 UUID 类型
if isinstance(obj, UUID):
return str(obj)
"""
# 处理 numpy 类型
if isinstance(obj, np.integer):
return int(obj)
if isinstance(obj, np.floating):
return float(obj)
if isinstance(obj, np.ndarray):
return obj.tolist()
"""
# 处理其他自定义类型
if hasattr(obj, '__json__'):
return obj.__json__()
# 默认处理
return super().default(obj)
return json.dumps(
content,
ensure_ascii=False,
allow_nan=False,
indent=None,
separators=(",", ":"),
cls=EnhancedJSONEncoder
).encode("utf-8")
system_admin_router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
logger = logging.getLogger("system_admin")
JWT_SECRET_KEY = "3e5b8d7f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d"
ALGORITHM = "HS256"
# JWT工具函数
def create_jwt(user_id: str) -> str:
payload = {
"sub": user_id,
"exp": datetime.utcnow() + timedelta(days=7)
}
return jwt.encode(payload, JWT_SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user(token: str = Depends(oauth2_scheme)):
"""
可选用户依赖项不抛出401错误
返回: 用户对象 或 None
"""
try:
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
return get_user_by_id(user_id)
except (JWTError, StopIteration):
return None
@system_admin_router.get("/get_subscriptions")
async def get_museum_subscriptions(
museum_id: Optional[int] = Query(None, description="博物馆ID不提供则返回所有博物馆"),
page: int = Query(1, ge=1, description="页码从1开始"),
page_size: int = Query(50, ge=1, le=100, description="每页记录数最大100"),
#current_user: dict = Depends(get_current_user)
):
# 直接使用参数FastAPI 会自动处理类型转换和验证
result = get_all_users_subscriptions_paginated(museum_id, page, page_size)
return CustomJSONResponse({
"code": 0,
"status": "success",
"data": result.get('data'),
"pagination":result.get("pagination")
})
def generate_json_response(code: str, message: str) -> JSONResponse:
return JSONResponse(
content={"code": code, "message": message},
status_code=200
)
@system_admin_router.post("/login")
async def test_account_login(request: Request):
# 获取原始请求数据
try:
data = await request.json()
except json.JSONDecodeError:
raise HTTPException(400, "Invalid JSON")
# 校验必要参数
required_fields = ["user","password"]
if not all(k in data for k in required_fields):
raise HTTPException(400, "Missing required fields")
user = data.get('user')
password = data.get('password')
account_info=get_admin_account_info(phone=user)
if account_info and len(account_info)>0:
account_info = account_info[0]
else:
return JSONResponse({
"code": 0,
"status": "error",
"data": {
"status":"error",
"msg": "账户不存在",
"token": create_jwt(user),
"user_info": {
},
"menu_authed": [],
"museum_authed": []
}
})
if account_info['password'] != password:
return JSONResponse({
"code": 0,
"status": "error",
"data": {
"status":"error",
"msg": "密码不正确",
"token": create_jwt(user),
"user_info": {
},
"menu_authed": [],
"museum_authed": []
}
})
museum_authed_str = account_info.get("museum_authed",None)
menu_authed_str = account_info.get("menu_authed","")
logging.info(f"account_info={account_info} {type(account_info)} {museum_authed_str}")
if not museum_authed_str: # 使用not 可以同步判断为空字符串
all_museums = get_museums()
logger.info(f"all {all_museums}")
museum_authed_list = [
{"id": int(item.id), "name": item.name}
for item in all_museums
if hasattr(item, 'id') and hasattr(item, 'name') and item.id is not None and item.name is not None
]
else:
museum_records = get_museums(id_list = museum_authed_str)
museum_authed_list = [
{"id": int(item['id']), "name": item['name']}
for item in museum_records
if isinstance(item, dict) and 'id' in item and 'name' in item and item['id'] is not None and item[
'name'] is not None
]
#museum_authed_list = [int(x) for x in museum_authed_str.split(',') if x.strip().isdigit()]
menu_authed_list = [x for x in menu_authed_str.split(',') ]
# ========== 数据库操作开始 ==========
# 使用数据库查询替代内存查询
#db_users = get_users(openid=user)
#user = db_users[0] if db_users else None
# ========== 数据库操作结束 ==========
logging.info(f"system account login return {user} {password}")
# 生成token
return JSONResponse({
"code": 0,
"status": "success",
"data": {
"status":"success",
"token": create_jwt(user),
"user_info": {
},
"menu_authed":menu_authed_list,
"museum_authed":museum_authed_list
}
})

View File

@@ -1091,6 +1091,7 @@ class QwenTTS:
self.synthesizer = None
self.callback = None
self.is_cosyvoice = False
self.is_cosyvoice_v2 = False
self.cosyvoice = ""
self.voice = ""
self.format = format
@@ -1103,6 +1104,9 @@ class QwenTTS:
self.is_cosyvoice = True
self.cosyvoice = parts[0]
self.voice = parts[1]
if parts[0] == 'cosyvoice-v2':
self.is_cosyvoice_v2 = True
logging.info(f"---begin--init QwenTTS-- {format} {sample_rate} {model_name} {self.cosyvoice} {self.voice}") # cyx
self.completion_event = None # 新增:用于通知任务完成
# 特殊字符及其拼音映射
@@ -1292,6 +1296,12 @@ class QwenTTS:
"""
在文本中查找特殊字符并用<phoneme>标签包裹它们
"""
"""
SSMLSpeech Synthesis Markup Language 是一种基于 XML 的语音合成标记语言。
它不仅能让语音合成大模型读出更丰富的文本内容,还支持对语速、语调、停顿、音量等语音特征进行精细控制,
甚至可以添加背景音乐带来更具表现力的语音效果。本文介绍CosyVoice的SSML功能及使用。
仅限cosyvoice-v2模型
"""
# 如果文本已经是SSML格式直接返回
if text.strip().startswith("<speak>") and text.strip().endswith("</speak>"):
return text
@@ -1311,7 +1321,9 @@ class QwenTTS:
return f"<speak>{text}</speak>"
def text_tts_call(self, text):
if self.special_characters :
# SSMLSpeech Synthesis Markup Language 是一种基于 XML 的语音合成标记语言。
# 仅限cosyvoice-v2模型
if self.special_characters and self.is_cosyvoice_v2 :
text = self.apply_phoneme_tags(text)
#logging.info(f"Applied SSML phoneme tags to text: {text}")
volume = 50

View File

@@ -45,3 +45,5 @@
[DEBUG] 错误详情: 可能原因 -
通用错误
[SUCCESS] Sun Aug 17 03:55:56 PM CST 2025 同步成功 | 大小: 18M
[SUCCESS] Mon Sep 8 09:20:51 AM CST 2025 同步成功 | 大小: 19M
[SUCCESS] Tue Sep 23 12:10:14 PM CST 2025 同步成功 | 大小: 19M

View File

@@ -120,7 +120,7 @@ pyicu = "^2.13.1"
flasgger = "^0.9.7.1"
polars = { version = "^1.9.0", markers = "platform_machine == 'x86_64'" }
polars-lts-cpu = { version = "^1.9.0", markers = "platform_machine == 'arm64'" }
python-dateutil="^2.8.2"
[tool.poetry.group.full]
optional = true