278 lines
8.8 KiB
Python
278 lines
8.8 KiB
Python
from fastapi import HTTPException
|
||
from ..db import get_connection
|
||
import hashlib
|
||
import random
|
||
from ..utils.sms_sender import send_sms # 需要实现阿里云短信发送
|
||
import redis
|
||
from configparser import ConfigParser
|
||
import os
|
||
import requests
|
||
redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
||
from ..schemas.user_auth import (
|
||
UserLoginRequest,
|
||
CodeLoginRequest,
|
||
ResetPasswordRequest,
|
||
SendCodeRequest,
|
||
CheckUserExistenceRequest
|
||
)
|
||
from ..utils import get_accesstoken
|
||
|
||
async def authenticate_user(phone_number: str, password: str):
|
||
"""手机号密码登录验证"""
|
||
conn = get_connection()
|
||
try:
|
||
cursor = conn.cursor(dictionary=True)
|
||
cursor.execute("SELECT * FROM users WHERE phone_number = %s", (phone_number,))
|
||
user = cursor.fetchone()
|
||
|
||
if not user:
|
||
raise HTTPException(404, "用户不存在")
|
||
if user["password"] != hashlib.md5(password.encode()).hexdigest():
|
||
raise HTTPException(401, "密码错误")
|
||
return user
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
async def send_verification_code(phone_number: str):
|
||
"""发送4位验证码"""
|
||
code = f"{random.randint(0,9999):04}"
|
||
print(code)
|
||
# 存储验证码到Redis(需要配置Redis连接)
|
||
redis_client.setex(f"sms_code:{phone_number}", 300, code)
|
||
|
||
# 调用阿里云短信接口
|
||
await send_sms(
|
||
phone_number=phone_number,
|
||
template_code="SMS_480005109", # 实际模板ID
|
||
template_param={"code": code}
|
||
)
|
||
return {"message": "验证码已发送"}
|
||
|
||
async def verify_code_login(phone_number: str, code: str):
|
||
"""验证码登录"""
|
||
stored_code = redis_client.get(f"sms_code:{phone_number}")
|
||
if stored_code:
|
||
stored_code = stored_code.decode('utf-8')
|
||
if not stored_code or stored_code != code:
|
||
raise HTTPException(400, "验证码错误")
|
||
|
||
conn = get_connection()
|
||
try:
|
||
cursor = conn.cursor(dictionary=True)
|
||
cursor.execute("SELECT * FROM users WHERE phone_number = %s", (phone_number,))
|
||
user = cursor.fetchone()
|
||
if not user:
|
||
raise HTTPException(404, "用户不存在")
|
||
return user
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
async def reset_password(request: ResetPasswordRequest):
|
||
"""重置密码"""
|
||
# 先验证验证码
|
||
await verify_code_login(request.phone_number, request.code)
|
||
|
||
# 更新密码
|
||
hashed_pwd = hashlib.md5(request.new_password.encode()).hexdigest()
|
||
conn = get_connection()
|
||
try:
|
||
cursor = conn.cursor()
|
||
cursor.execute("UPDATE users SET password = %s WHERE phone_number = %s",
|
||
(hashed_pwd, request.phone_number))
|
||
conn.commit()
|
||
return {"message": "密码重置成功"}
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
async def register_user(phone_number: str, code: str, username: str, password: str):
|
||
"""注册新用户"""
|
||
# 先验证验证码
|
||
stored_code = redis_client.get(f"sms_code:{phone_number}")
|
||
if not stored_code or stored_code.decode() != code:
|
||
raise HTTPException(400, "验证码错误")
|
||
|
||
# 检查手机号是否已注册
|
||
conn = get_connection()
|
||
try:
|
||
cursor = conn.cursor(dictionary=True)
|
||
cursor.execute("SELECT * FROM users WHERE phone_number = %s", (phone_number,))
|
||
user = cursor.fetchone()
|
||
if user:
|
||
raise HTTPException(400, "该手机号已注册")
|
||
|
||
# 检查用户名是否已存在
|
||
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
|
||
user = cursor.fetchone()
|
||
if user:
|
||
raise HTTPException(400, "该用户名已被使用")
|
||
|
||
# 注册新用户
|
||
hashed_pwd = hashlib.md5(password.encode()).hexdigest()
|
||
cursor.execute("INSERT INTO users (phone_number, username, password) VALUES (%s, %s, %s)",
|
||
(phone_number, username, hashed_pwd))
|
||
conn.commit()
|
||
return {"message": "注册成功"}
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
async def check_user_existence(phone_number: str):
|
||
"""检查用户是否存在"""
|
||
conn = get_connection()
|
||
try:
|
||
cursor = conn.cursor()
|
||
cursor.execute(
|
||
"SELECT user_id FROM users WHERE phone_number = %s",
|
||
(phone_number,)
|
||
)
|
||
exists = cursor.fetchone() is not None
|
||
return {"exists": exists}
|
||
except Exception as e:
|
||
raise HTTPException(500, f"查询失败: {str(e)}")
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
|
||
async def check_wx_bind_status(token: str):
|
||
"""检查微信绑定状态"""
|
||
from ..utils.jwt_handler import verify_token
|
||
|
||
try:
|
||
# 验证JWT令牌
|
||
payload = verify_token(token)
|
||
phone_number = payload["sub"]
|
||
|
||
# 获取数据库连接
|
||
connection = get_connection()
|
||
cursor = connection.cursor(dictionary=True)
|
||
|
||
# 查询微信OpenID
|
||
cursor.execute(
|
||
"SELECT wx_openid FROM users WHERE phone_number = %s",
|
||
(phone_number,)
|
||
)
|
||
user = cursor.fetchone()
|
||
|
||
if not user:
|
||
raise HTTPException(404, "用户不存在")
|
||
|
||
return {"is_binded": user['wx_openid'] is not None}
|
||
|
||
except ValueError as e:
|
||
raise HTTPException(401, str(e))
|
||
except Exception as e:
|
||
raise HTTPException(500, str(e))
|
||
finally:
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
async def bind_wechat_openid(token: str, code: str):
|
||
"""通过微信code绑定openid"""
|
||
from ..utils.jwt_handler import verify_token
|
||
import requests
|
||
|
||
try:
|
||
# 验证JWT令牌
|
||
payload = verify_token(token)
|
||
phone_number = payload["sub"]
|
||
|
||
config = ConfigParser()
|
||
config.read(os.path.join(os.path.dirname(__file__), '../../config.conf'))
|
||
|
||
# 从微信获取openid(需要配置微信应用信息)
|
||
wechat_url = "https://api.weixin.qq.com/sns/jscode2session"
|
||
params = {
|
||
"appid": config.get("wechat", "appid"),
|
||
"secret": config.get("wechat", "secret"),
|
||
"js_code": code,
|
||
"grant_type": "authorization_code"
|
||
}
|
||
|
||
response = requests.get(wechat_url, params=params)
|
||
wechat_data = response.json()
|
||
|
||
if 'openid' not in wechat_data:
|
||
raise HTTPException(400, "微信授权失败")
|
||
|
||
openid = wechat_data['openid']
|
||
|
||
# 存储到数据库
|
||
connection = get_connection()
|
||
cursor = connection.cursor()
|
||
cursor.execute(
|
||
"UPDATE users SET wx_openid = %s WHERE phone_number = %s",
|
||
(openid, phone_number)
|
||
)
|
||
connection.commit()
|
||
|
||
return {"message": "微信绑定成功"}
|
||
|
||
except ValueError as e:
|
||
raise HTTPException(401, str(e))
|
||
except requests.RequestException:
|
||
raise HTTPException(503, "无法连接微信服务器")
|
||
except Exception as e:
|
||
connection.rollback()
|
||
raise HTTPException(500, str(e))
|
||
finally:
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
|
||
async def use_wx_phoneNumber(code: str):
|
||
"""通过微信code获取用户手机号并检查注册状态"""
|
||
try:
|
||
access_token = get_accesstoken.get_access_token()
|
||
|
||
wx_url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"
|
||
params = {"access_token": access_token}
|
||
response = requests.post(wx_url, params=params, json={"code": code})
|
||
wx_data = response.json()
|
||
|
||
if wx_data.get("errcode") != 0:
|
||
raise HTTPException(400, f"微信接口错误: {wx_data.get('errmsg')}")
|
||
|
||
phone_number = wx_data["phone_info"]["phoneNumber"]
|
||
|
||
conn = get_connection()
|
||
cursor = conn.cursor(dictionary=True)
|
||
|
||
# 检查用户是否存在
|
||
cursor.execute("SELECT * FROM users WHERE phone_number = %s", (phone_number,))
|
||
user = cursor.fetchone()
|
||
|
||
if not user:
|
||
# 自动注册新用户
|
||
|
||
cursor.execute(
|
||
"INSERT INTO users (phone_number, username, password) VALUES (%s, %s, %s)",
|
||
(phone_number, "用户_临时", "no_set")
|
||
)
|
||
# 获取新用户ID并更新用户名
|
||
user_id = cursor.lastrowid
|
||
cursor.execute(
|
||
"UPDATE users SET username = %s WHERE user_id = %s",
|
||
(f"用户_{user_id}", user_id)
|
||
)
|
||
conn.commit()
|
||
# 重新查询新用户信息
|
||
cursor.execute("SELECT * FROM users WHERE user_id = %s", (user_id,))
|
||
user = cursor.fetchone()
|
||
|
||
return {"user": user}
|
||
|
||
except requests.RequestException:
|
||
raise HTTPException(503, "无法连接微信服务器")
|
||
except KeyError:
|
||
raise HTTPException(400, "无效的微信响应格式")
|
||
finally:
|
||
# 确保关闭数据库连接
|
||
if 'cursor' in locals():
|
||
cursor.close()
|
||
if 'conn' in locals():
|
||
conn.close()
|