准备修改大模型对话输出的文本的tts生成为stream_call,进行备份提交
This commit is contained in:
388
asr-monitor-test/app/login_service.py
Normal file
388
asr-monitor-test/app/login_service.py
Normal file
@@ -0,0 +1,388 @@
|
||||
from fastapi import WebSocket, APIRouter,WebSocketDisconnect,Request,Body,Query
|
||||
from fastapi import FastAPI, UploadFile, File, Form, Header,Depends
|
||||
from fastapi.responses import StreamingResponse,JSONResponse
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
import logging
|
||||
from fastapi import HTTPException
|
||||
from Crypto.Cipher import AES
|
||||
import base64,uuid
|
||||
import requests
|
||||
from datetime import datetime,timedelta
|
||||
from database import *
|
||||
login_router = APIRouter()
|
||||
logger = logging.getLogger("login")
|
||||
|
||||
# 初始化 OAuth2 方案(必须放在使用它的函数之前)
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # tokenUrl 对应登录接口路径
|
||||
|
||||
# 需要配置的参数(从环境变量获取)
|
||||
WX_APPID = "wxed388cef83f109a3" # 小程序appid
|
||||
WX_SECRET = "f687afd2c8fae49b4aed2e4a8dd76e6e" # 小程序密钥
|
||||
WX_API_URL = "https://api.weixin.qq.com/sns/jscode2session"
|
||||
JWT_SECRET_KEY="3e5b8d7f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d4e0f1a9c2b6d"
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
# 伪数据库
|
||||
fake_db = {
|
||||
"users": [],
|
||||
"museums": []
|
||||
}
|
||||
|
||||
"""
|
||||
1. SECRET_KEY 的作用
|
||||
作用 说明
|
||||
签名验证 用于签发(sign)和验证(verify)JWT 的合法性,防止令牌被篡改
|
||||
安全保障 作为加密盐值(salt),确保令牌无法被伪造或逆向破解
|
||||
身份验证 确保令牌是由可信的服务器颁发的,而非第三方伪造的
|
||||
"""
|
||||
# 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)
|
||||
|
||||
|
||||
def decrypt_phone(encrypted_data: str, session_key: str, iv: str) -> dict:
|
||||
try:
|
||||
|
||||
# Base64解码
|
||||
session_key_bin = base64.b64decode(session_key + "=") # 补齐可能缺失的padding
|
||||
encrypted_data_bin = base64.b64decode(encrypted_data)
|
||||
iv_bin = base64.b64decode(iv)
|
||||
|
||||
# 创建解密器
|
||||
cipher = AES.new(session_key_bin, AES.MODE_CBC, iv_bin)
|
||||
|
||||
# 执行解密
|
||||
decrypted = cipher.decrypt(encrypted_data_bin)
|
||||
|
||||
# 去除PKCS#7填充
|
||||
pad = decrypted[-1]
|
||||
decrypted = decrypted[:-pad]
|
||||
|
||||
# 解析JSON
|
||||
result = json.loads(decrypted.decode('utf-8'))
|
||||
if 'purePhoneNumber' not in result:
|
||||
raise ValueError("解密数据不包含手机号")
|
||||
|
||||
return result['purePhoneNumber']
|
||||
except Exception as e:
|
||||
raise ValueError(f"解密过程失败: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"解密失败: {str(e)}")
|
||||
|
||||
|
||||
async def get_wx_session(code: str):
|
||||
"""
|
||||
调用微信接口获取session_key和openid
|
||||
返回: {"openid": str, "session_key": str}
|
||||
"""
|
||||
params = {
|
||||
"appid": WX_APPID,
|
||||
"secret": WX_SECRET,
|
||||
"js_code": code,
|
||||
"grant_type": "authorization_code"
|
||||
}
|
||||
|
||||
try:
|
||||
# 调用微信接口
|
||||
response = requests.get(WX_API_URL, params=params, timeout=5)
|
||||
response.raise_for_status() # 检查HTTP状态码
|
||||
wx_data = response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail=f"微信接口请求失败: {str(e)}"
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail="微信接口返回数据解析失败"
|
||||
)
|
||||
|
||||
# 处理微信返回错误
|
||||
if "errcode" in wx_data:
|
||||
error_map = {
|
||||
40029: "无效的code",
|
||||
45011: "API调用太频繁",
|
||||
-1: "微信系统繁忙"
|
||||
}
|
||||
detail = error_map.get(
|
||||
wx_data["errcode"],
|
||||
f"微信接口错误: {wx_data.get('errmsg', '未知错误')}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail=detail
|
||||
)
|
||||
|
||||
if "openid" not in wx_data:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="微信认证失败:缺少openid"
|
||||
)
|
||||
logging.info(f"wx_data {wx_data}")
|
||||
return wx_data
|
||||
|
||||
|
||||
@login_router.post("/login")
|
||||
async def wechat_login(request: Request):
|
||||
# 获取原始请求数据
|
||||
try:
|
||||
data = await request.json()
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(400, "Invalid JSON")
|
||||
# 校验必要参数
|
||||
required_fields = ["code", "encryptedData", "iv"]
|
||||
if not all(k in data for k in required_fields):
|
||||
raise HTTPException(400, "Missing required fields")
|
||||
code = data.get('code')
|
||||
# 调用微信接口获取session信息
|
||||
try:
|
||||
wx_data = await get_wx_session(code)
|
||||
logging.info(f"wechat login wx_data={wx_data}")
|
||||
except HTTPException as e:
|
||||
raise e # 直接抛出已处理过的异常
|
||||
|
||||
# 伪解密手机号
|
||||
try:
|
||||
#phone_number = "138" + str(hash(data["encryptedData"]))[-8:]
|
||||
phone_number=decrypt_phone(data["encryptedData"],wx_data['session_key'],data["iv"])
|
||||
except:
|
||||
raise HTTPException(400, "Decrypt failed")
|
||||
|
||||
logging.info(f"decrypt_phone return {phone_number}")
|
||||
# ========== 数据库操作开始 ==========
|
||||
# 使用数据库查询替代内存查询
|
||||
db_users = get_users(phone=phone_number)
|
||||
user = db_users[0] if db_users else None
|
||||
|
||||
# 用户不存在时创建新用户
|
||||
if not user:
|
||||
try:
|
||||
new_user = {
|
||||
"user_id": str(uuid.uuid4()), # 使用UUID生成唯一ID
|
||||
"openid": wx_data["openid"],
|
||||
"phone": str(phone_number),
|
||||
"status": 1, # 默认启用状态
|
||||
"balance": 0 # 初始余额设为0
|
||||
# museums字段需要另存关联表,此处暂时保留伪数据
|
||||
}
|
||||
create_user(new_user) # 调用CRUD创建方法
|
||||
user = new_user
|
||||
except Exception as e: # 捕获唯一约束等异常
|
||||
logging.error(f"User creation failed: {str(e)}")
|
||||
raise HTTPException(500, "User registration failed")
|
||||
|
||||
# 更新最后登录时间
|
||||
update_data = {
|
||||
"last_login_time": int(datetime.now().timestamp()),
|
||||
"token": create_jwt(user["user_id"]) # 生成新token
|
||||
}
|
||||
updated_user = update_user(user["user_id"], update_data)
|
||||
# ========== 数据库操作结束 ==========
|
||||
"""
|
||||
# 查找或创建用户
|
||||
user = next((u for u in fake_db["users"] if u["phone"] == phone_number), None)
|
||||
if not user:
|
||||
user = {
|
||||
"id": str(uuid.uuid4()),
|
||||
"openid": wx_data["openid"],
|
||||
"phone": phone_number,
|
||||
"museums": [1, 2, 3] # 伪权限数据
|
||||
}
|
||||
fake_db["users"].append(user)
|
||||
logging.info(f"login return {user}")
|
||||
"""
|
||||
|
||||
# 生成token
|
||||
return JSONResponse({
|
||||
"token": create_jwt(user["user_id"]),
|
||||
"user_info": {
|
||||
"phone": phone_number,
|
||||
"museums": get_museum_avail(user)
|
||||
}
|
||||
})
|
||||
|
||||
def get_museum_avail(user):
|
||||
museum_list = get_museums(None, None)
|
||||
id_list = [museum['id'] for museum in museum_list]
|
||||
return id_list
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import base64
|
||||
import json
|
||||
import requests
|
||||
|
||||
|
||||
def decrypt_wechat_phone(
|
||||
encrypted_data: str,
|
||||
code: str,
|
||||
appid: str,
|
||||
secret: str
|
||||
) -> str:
|
||||
"""
|
||||
微信手机号解密函数(完整本地处理版)
|
||||
|
||||
参数:
|
||||
encrypted_data: 前端传递的加密数据
|
||||
code: 前端通过uni.login获取的临时code
|
||||
appid: 小程序appid
|
||||
secret: 小程序appsecret
|
||||
|
||||
返回:
|
||||
str: 解密后的手机号
|
||||
|
||||
异常:
|
||||
ValueError: 当任何步骤失败时抛出
|
||||
"""
|
||||
# 步骤1:获取session_key
|
||||
params = {
|
||||
"appid": WX_APPID,
|
||||
"secret": WX_SECRET,
|
||||
"js_code": code,
|
||||
"grant_type": "authorization_code"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(WX_API_URL, params=params, timeout=5)
|
||||
response.raise_for_status()
|
||||
wx_data = response.json()
|
||||
|
||||
if 'session_key' not in wx_data:
|
||||
raise ValueError(f"获取session_key失败: {wx_data.get('errmsg', '未知错误')}")
|
||||
|
||||
session_key = wx_data['session_key']
|
||||
iv = encrypted_data.split('=')[1][:24] # 从加密数据中提取iv(根据实际情况调整)
|
||||
except Exception as e:
|
||||
raise ValueError(f"微信接口请求失败: {str(e)}")
|
||||
|
||||
# 步骤2:执行AES解密
|
||||
try:
|
||||
# Base64解码
|
||||
session_key_bin = base64.b64decode(session_key + "=") # 补齐可能缺失的padding
|
||||
encrypted_data_bin = base64.b64decode(encrypted_data)
|
||||
iv_bin = base64.b64decode(iv)
|
||||
|
||||
# 创建解密器
|
||||
cipher = AES.new(session_key_bin, AES.MODE_CBC, iv_bin)
|
||||
|
||||
# 执行解密
|
||||
decrypted = cipher.decrypt(encrypted_data_bin)
|
||||
|
||||
# 去除PKCS#7填充
|
||||
pad = decrypted[-1]
|
||||
decrypted = decrypted[:-pad]
|
||||
|
||||
# 解析JSON
|
||||
result = json.loads(decrypted.decode('utf-8'))
|
||||
|
||||
if 'purePhoneNumber' not in result:
|
||||
raise ValueError("解密数据不包含手机号")
|
||||
|
||||
return result['purePhoneNumber']
|
||||
except Exception as e:
|
||||
raise ValueError(f"解密过程失败: {str(e)}")
|
||||
|
||||
|
||||
# 在 login_service.py 中添加以下内容
|
||||
|
||||
async def optional_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
|
||||
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)) :
|
||||
# 身份验证核心逻辑
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="无法验证凭证",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
# 1. 解码并验证JWT
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise credentials_exception
|
||||
|
||||
# 2. 从数据库获取完整用户信息
|
||||
user = get_user_by_id(user_id)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
return user
|
||||
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
@login_router.get("/verify")
|
||||
async def verify_token(user: dict = Depends(optional_current_user)):
|
||||
"""
|
||||
Token 验证接口
|
||||
返回格式:
|
||||
{
|
||||
"valid": bool,
|
||||
"user": {
|
||||
"user_id": str,
|
||||
"phone": str,
|
||||
"museums": List[int]
|
||||
} | null
|
||||
}
|
||||
"""
|
||||
logging.info(f"verify_token user={user}")
|
||||
if user:
|
||||
return JSONResponse({
|
||||
"valid": True,
|
||||
"user": {
|
||||
"user_id": user["user_id"],
|
||||
"phone": user["phone"],
|
||||
#"museums": user["museums"]
|
||||
}
|
||||
})
|
||||
else:
|
||||
return JSONResponse({
|
||||
"valid": False,
|
||||
"user": None,
|
||||
"detail": "无效的认证凭据"
|
||||
})
|
||||
|
||||
@login_router.get("/get_museum_list")
|
||||
async def get_museum_list(current_user = Depends(optional_current_user)):
|
||||
#orders = Order.query.filter_by(user_id=current_user.id).all()
|
||||
logging.info(f"get_museum_list user={current_user}")
|
||||
museums_paid = get_users_museums_by_user_id(current_user.get('user_id'))
|
||||
paid_id_list = [museum['museum_id'] for museum in museums_paid]
|
||||
logging.info(f"get_museum_list paid={paid_id_list}")
|
||||
museum_list = get_museums(None, None)
|
||||
for museum in museum_list:
|
||||
if museum.get('id') in paid_id_list:
|
||||
museum['paid'] = True
|
||||
else:
|
||||
museum['paid'] = False
|
||||
try:
|
||||
return JSONResponse(museum_list)
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
@login_router.get("/get_museum_id_auth")
|
||||
async def get_museum_id_auth(current_user = Depends(optional_current_user)):
|
||||
try:
|
||||
museum_list = get_museums(None, None)
|
||||
id_list = [museum['id'] for museum in museum_list]
|
||||
logging.info(f"get_museum_id_auth={id_list}")
|
||||
return JSONResponse(id_list)
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
Reference in New Issue
Block a user