Program/mooc/api/v1/endpoints/wxapp.py

938 lines
32 KiB
Python
Raw Normal View History

from fastapi import APIRouter, Depends, Request, Form, Body, UploadFile,File
from fastapi.responses import PlainTextResponse
2025-01-15 20:26:58 +08:00
from typing import Optional, Dict, Any
from pydantic import BaseModel
from sqlalchemy.orm import Session
from mooc.db.database import get_db
from mooc.crud.crud_goouc_fullexam_user import (
CRUDUserDoexam,
CRUDUserExamAnswer,
CRUDUserWrongPraction,
CRUDUserCollectionPraction,
full_exam_user
2025-01-15 20:26:58 +08:00
)
from mooc.models.goouc_fullexam_user import (
UserDoexam,
UserExamAnswer,
UserWrongPraction,
UserCollectionPraction,
FullExamUser
2025-01-15 20:26:58 +08:00
)
from mooc.core.config import settings
from mooc.utils.wechat_client import wx_api
from datetime import datetime
import os
from mooc.core.logger import get_logger
import httpx
from mooc.core.response import ResponseFactory
import re
from mooc.crud.crud_goouc_fullexam import setting, xueshen
from mooc.crud.crud_goouc_fullexam_user import full_exam_user, user_member
2025-01-15 20:26:58 +08:00
wxapp_router = APIRouter()
# 创建模块级别的日志记录器
logger = get_logger(__name__)
2025-01-15 20:26:58 +08:00
class WxappRequest(BaseModel):
uid: Optional[str] = None
op: Optional[str] = None
m: Optional[str] = None
data: Dict[str, Any] = {}
code: Optional[str] = None
# 启用额外字段支持
model_config = {
"extra": "allow" # 允许存储模型中未定义的字段
}
# 添加一个方法便于获取任意字段,带默认值
def get_field(self, field_name: str, default=None):
"""获取字段值优先从直接属性获取然后从data字典获取"""
if hasattr(self, field_name):
return getattr(self, field_name)
return self.data.get(field_name, default)
2025-01-15 20:26:58 +08:00
@wxapp_router.post("/index")
async def handle_wxapp_request(
request: Request,
i: str,
t: Optional[str] = None,
v: Optional[str] = None,
c: Optional[str] = "entry",
a: Optional[str] = "wxapp",
do: Optional[str] = None,
db: Session = Depends(get_db),
file: UploadFile = None
2025-01-15 20:26:58 +08:00
):
# 获取表单数据
try:
form_data = await request.form()
logger.debug(f"Form data: {form_data}")
2025-01-15 20:26:58 +08:00
# 将表单数据转换为字典
data = dict(form_data)
# 检查是否包含上传文件
if "upfile" in form_data:
file = form_data["upfile"]
2025-01-15 20:26:58 +08:00
except Exception as e:
logger.error(f"Error reading form data: {e}")
2025-01-15 20:26:58 +08:00
# 如果没有表单数据尝试读取JSON
try:
data = await request.json()
logger.debug(f"JSON data: {data}")
2025-01-15 20:26:58 +08:00
except Exception as e:
logger.error(f"Error reading JSON: {e}")
2025-01-15 20:26:58 +08:00
data = {}
logger.debug(f"Final data: {data}")
logger.debug(f"Query params: {request.query_params}")
2025-01-15 20:26:58 +08:00
# 根据do参数处理不同的业务逻辑
if do == "login2":
# 针对login2直接传递表单数据
if "code" in data:
return await handle_login2(WxappRequest(code=data.get("code"),
data=data), db)
else:
# 如果没有code仍然尝试正常处理
return await handle_login2(WxappRequest(**data), db)
elif do == "Setuserinfo":
return await handle_set_user_info(WxappRequest(**data), db)
2025-01-15 20:26:58 +08:00
elif do == "ExamOperation":
return await handle_exam_operation(WxappRequest(**data), db, user_doexam, user_exam_answer)
elif do == "Collection":
return await handle_collection(WxappRequest(**data), db, user_collection)
elif do == "WrongQuestion":
return await handle_wrong_question(WxappRequest(**data), db, user_wrong_praction)
elif do == "TotalqNum":
# 添加新的处理逻辑
return {"code": 0, "data": {"total": 100}, "message": "success"}
2025-01-15 20:26:58 +08:00
elif do == "Index":
# 添加首页处理逻辑
return {"code": 0, "data": {}, "message": "success"}
2025-01-15 20:26:58 +08:00
elif do == "Advert":
# 添加广告处理逻辑
return {"code": 0, "data": [], "message": "success"}
elif do == "uploadImage":
if not file:
return ResponseFactory.error(code=400, message="缺少上传文件")
return await handle_upload_image(WxappRequest(**data), db, file)
elif do == "UpdateHeadimg":
return await handle_update_headimg(WxappRequest(**data), db)
2025-01-15 20:26:58 +08:00
return {"code": 404, "message": "接口未找到"}
2025-01-15 20:26:58 +08:00
async def handle_update_headimg(data: WxappRequest, db: Session):
"""处理更新用户头像的请求"""
logger.info(f"处理更新头像请求: uid={data.uid}data={data}")
try:
# 验证必要参数
uid = data.uid
# headimg = data.data.get("headimg")
# if not uid or not headimg:
# logger.error("参数不足: uid或headimg缺失")
# return ResponseFactory.error(code=1, message="传递的参数不存在", data="1001")
# 获取用户对象包含weid条件
user = full_exam_user.get_by_id_and_weid(db, int(uid), settings.WECHAT_UNIACID)
if not user:
logger.error(f"用户不存在: uid={uid}")
return ResponseFactory.error(code=1, message="用户不存在", data="error")
logger.debug(f"用户信息: {user.headimg}")
# 更新用户头像
try:
update_data = {"headimg": data.headimg}
full_exam_user.update(db, db_obj=user, obj_in=update_data)
logger.info(f"头像更新成功: uid={uid}, headimg={data.headimg}")
return ResponseFactory.success(data="ok", message="ok")
except Exception as e:
logger.error(f"头像更新失败: {str(e)}")
return ResponseFactory.error(code=1, message="error", data="error")
except Exception as e:
logger.exception(f"处理更新头像请求过程中发生异常: {str(e)}")
return ResponseFactory.error(code=1, message=str(e), data="error")
async def handle_upload_image(data: WxappRequest, db: Session, file: UploadFile = File(...)):
"""处理图片上传功能"""
import os
import time
import random
import string
import shutil
from pathlib import Path
logger.info(f"处理图片上传请求: uid={data.uid}, module={data.m}")
try:
# 定义允许的图片类型
allowed_types = [
"image/jpg", "image/jpeg", "image/png",
"image/pjpeg", "image/gif", "image/bmp", "image/x-png"
]
# 最大文件大小(2MB)
max_file_size = 2000000
# 检查文件是否存在
if not file:
logger.error("图片不存在!")
return ResponseFactory.error(code=400, message="图片不存在!")
# 检查文件内容类型
content_type = file.content_type
if content_type not in allowed_types:
logger.error(f"文件类型不符: {content_type}")
return ResponseFactory.error(code=400, message=f"文件类型不符: {content_type}")
# 读取文件内容以检查大小
contents = await file.read()
if len(contents) > max_file_size:
logger.error(f"文件太大: {len(contents)} bytes")
return ResponseFactory.error(code=400, message="文件太大!")
# 重置文件位置指针
await file.seek(0)
# 获取上传目标文件夹路径
module_name = data.m or "default"
# 使用绝对路径并确保目录存在
destination_folder = os.path.join(settings.UPLOAD_IMG_DIR, module_name)
logger.debug(f"上传目标文件夹: {destination_folder}")
# 确保目标目录存在
Path(destination_folder).mkdir(parents=True, exist_ok=True)
# 创建更安全的随机文件名 (时间戳 + 随机数)
current_time = int(time.time())
random_string = ''.join(random.choices(string.digits + string.ascii_lowercase, k=8))
file_extension = os.path.splitext(file.filename)[1].lower() # 转为小写以增加一致性
# 验证扩展名安全性
safe_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
if file_extension not in safe_extensions:
file_extension = '.jpg' # 默认使用安全扩展名
filename = f"{current_time}_{random_string}{file_extension}"
# 完整的保存路径
file_path = os.path.join(destination_folder, filename)
logger.debug(f"文件将保存至: {file_path}")
# 保存文件
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# 检查文件是否成功保存
if not os.path.exists(file_path):
logger.error(f"文件保存失败: {file_path}")
return ResponseFactory.error(code=500, message="文件保存失败")
# 构建文件URL路径
url_module_path = module_name.replace('\\', '/')
# 使用相对路径作为存储路径
relative_path = f"{url_module_path}/{filename}"
# 构建完整的外网访问URL
# 方法1: 使用BASE_URL配置构建绝对URL
absolute_url = f"{settings.BASE_URL}{settings.ATTACH_URL}{relative_path}"
logger.info(f"文件上传成功: {absolute_url}")
# 直接返回完整URL字符串
return PlainTextResponse(content=absolute_url)
except Exception as e:
logger.exception(f"文件上传过程中发生异常: {str(e)}")
# 直接返回错误消息字符串
return str(e)
def validate_phone(phone: str) -> bool:
# 基础格式验证
if not re.match(r"^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$", phone):
return False
# 特殊号段白名单
special_prefix = ["191", "192", "197"] # 根据实际情况更新
if any(phone.startswith(p) for p in special_prefix):
return True
# 虚拟运营商号段二次验证
if phone.startswith(("170", "171", "172")):
return validate_virtual_operator(phone) # 调用专用验证接口
return True
async def handle_set_user_info(data: WxappRequest, db: Session):
2025-01-15 20:26:58 +08:00
"""处理用户信息相关操作"""
logger.info(f"处理用户信息请求: op={data.op}, uid={data.uid},data={data}")
2025-01-15 20:26:58 +08:00
operations = {
"getinfo": get_user_info,
"checkStudent": check_student,
2025-01-15 20:26:58 +08:00
"update": update_user_info,
"getcode": get_verification_code,
"checkcode": check_verification_code,
2025-01-15 20:26:58 +08:00
}
# 如果op为空则默认执行更新用户信息操作
if not data.op:
logger.info(f"未指定操作类型,默认执行更新用户信息: uid={data.uid}")
try:
# 直接从请求对象中获取数据
update_data = {}
if hasattr(data, "name"):
update_data["name"] = data.name
if hasattr(data, "phone") and data.phone != "null":
update_data["phone"] = data.phone
# 添加请求中的其他字段
for field in ["student_id", "id_card", "school", "level"]:
if hasattr(data, field):
update_data["field"] = getattr(data, field)
logger.debug(f"更新用户数据: {update_data}")
result = await update_user_info(data.uid, update_data, db)
return ResponseFactory.success(
data=result,
message="更新成功"
)
except Exception as e:
logger.exception(f"更新用户信息时发生异常: {str(e)}")
return ResponseFactory.error(
code=1,
message=str(e)
)
# 处理指定操作类型
2025-01-15 20:26:58 +08:00
operation = operations.get(data.op)
logger.debug(f"operation: {operation}")
2025-01-15 20:26:58 +08:00
if not operation:
logger.warning(f"不支持的操作: {data.op}")
return ResponseFactory.error(
code=1,
message=f"不支持的操作: {data.op}"
)
2025-01-15 20:26:58 +08:00
try:
# 处理特殊情况getcode操作需要直接获取phone属性
if data.op == "getcode":
# 确保将phone从对象属性传递到data字典
data_dict = dict(data.data) if data.data else {} # 复制现有数据
# 从对象中获取phone属性
if hasattr(data, "phone"):
data_dict["phone"] = data.phone
result = await operation(data.uid, data_dict, db)
else:
# 其他操作保持原样
result = await operation(data.uid, data.data, db)
logger.debug(f"result: {result}")
return ResponseFactory.success(
data=result,
message="success"
)
2025-01-15 20:26:58 +08:00
except Exception as e:
logger.exception(f"处理用户信息时发生异常: {str(e)}")
return ResponseFactory.error(
code=1,
message=str(e)
)
# 获取用户信息
async def get_user_info(uid: str, data: Dict[str, Any], db: Session) -> Dict[str, Any]:
"""获取用户详细信息"""
logger.debug(f"获取用户信息: uid={uid},data={data}")
if not uid:
raise ValueError("用户ID不存在")
# 从请求获取协议和域名
protocol = "https://" # 在实际部署中可能需要根据请求头判断
domain_name = "yourdomain.com" # 这里需要从请求中获取
http = f"{protocol}{domain_name}"
# 获取用户基本信息 - 已经使用CRUD方法
user = full_exam_user.get_by_fields(db, {"id": int(uid), "weid": settings.WECHAT_UNIACID, "istatus": 1})
if not user or user.istatus != 1:
raise ValueError("用户不存在或已被删除")
# 构建用户信息字典
info = {
"id": user.id,
"nickname": user.nickname,
"headimg": user.headimg,
"name": user.name,
"phone": user.phone,
"grade": user.gradeid,
"ismember": user.ismember,
"member_endtime": None,
"nativeplace": user.nativeplace,
"integral": user.integral,
"student_id": user.student_id,
"id_card": user.id_card,
"school": user.school,
"level": user.level,
"openid": user.openid
}
logger.debug(f"用户信息: {info}")
# 检查会员是否过期
current_time = int(datetime.now().timestamp())
if user.ismember == 1 and user.member_endtime and int(user.member_endtime) < current_time:
# 更新会员状态为非会员 - 使用CRUD方法
full_exam_user.update(db, db_obj=user, obj_in={"ismember": 2})
info["ismember"] = 2
# 格式化会员到期时间
if user.member_endtime:
info["member_endtime"] = datetime.fromtimestamp(int(user.member_endtime)).strftime("%Y-%m-%d")
# 获取系统设置 - 使用CRUD替代原始SQL
system_setting = setting.get_by_fields(db, {"weid": user.weid})
# 默认用户信息状态为完整
is_ok = 1
# 检查用户信息是否完整
if system_setting and system_setting.info_status == 1:
if not user.phone or not user.name:
is_ok = 2
# 添加系统设置信息
if system_setting:
info["IOS"] = system_setting.IOS
info["customer_service"] = system_setting.customer_service
# 检查会员系统是否开启 - 使用CRUD替代原始SQL
member_setting = user_member.get_by_fields(db, {
"weid": user.weid,
"istatus": 1
})
info["is_open"] = member_setting.status if member_setting else 0
# 用户信息完整性状态
info["is_ok"] = is_ok
# 默认学生信息
info["stu_have"] = 2
info["student_id"] = 0
info["xuesheng_id"] = 0
# 如果用户有姓名和手机号,检查是否是学生 - 使用CRUD替代原始SQL
if user.phone and user.name:
student = xueshen.get_by_fields(db, {
"weid": user.weid,
"name": user.name,
"phone": user.phone
})
if student:
info["student_id"] = student.student_name
info["xuesheng_id"] = student.xuesheng_id
info["stu_have"] = 1
# 检查是否绑定微信
info["is_bind"] = 1 if user.openid else 0
return {"info": info, "http": http}
# 检查学生信息
async def check_student(uid: str, data: Dict[str, Any], db: Session) -> Dict[str, Any]:
"""检查学生信息是否存在"""
phone = data.get("phone")
name = data.get("name")
if not phone or not name:
raise ValueError("传递的参数不存在")
user = full_exam_user.get(db, int(uid))
if not user:
raise ValueError("用户不存在")
student = xueshen.get_by_fields(db, {
"weid": user.weid,
"name": name,
"phone": phone
})
if not student:
raise ValueError("学生信息不存在")
# 将结果转换为字典
student_info = {
"xuesheng_id": student.xuesheng_id,
"name": student.name,
"phone": student.phone,
"student_name": student.student_name,
"sex": student.sex
}
return {"list": student_info}
# 获取验证码
async def get_verification_code(uid: str, data: Dict[str, Any], db: Session) -> Dict[str, Any]:
"""发送手机验证码"""
phone = data.get("phone")
# 验证手机号
if not validate_phone(phone):
raise ValueError("手机号有误")
user = full_exam_user.get(db, int(uid))
if not user:
raise ValueError("用户不存在")
# 使用CRUD替代SQL查询系统配置
system_config = setting.get_by_fields(db, {"weid": user.weid})
# 生成4位随机验证码
import random
code = ''.join(random.choices('0123456789', k=4))
# 发送验证码 - 这里需要替换为实际的短信发送逻辑
try:
# 假设这是成功发送
send_result = {"Code": "OK"}
if send_result["Code"] == "OK":
# 保存验证码记录
from mooc.schemas.goouc_fullexam import PhoneCodeCreate
phone_code_data = PhoneCodeCreate(
phone=phone,
code=int(code), # 确保代码和模型匹配
createtime=int(datetime.now().timestamp()),
weid=user.weid
)
phone_code.create(db, obj_in=phone_code_data)
return {"status": 1}
else:
raise ValueError(f"发送失败: {send_result.get('Message', '未知错误')}")
except Exception as e:
logger.error(f"发送验证码失败: {str(e)}")
raise ValueError(f"发送验证码失败: {str(e)}")
# 检查验证码
async def check_verification_code(uid: str, data: Dict[str, Any], db: Session) -> Dict[str, Any]:
"""验证手机验证码"""
input_code = data.get("code", "").strip()
phone = data.get("phone")
if not input_code or not phone:
raise ValueError("传递的参数不存在")
# 获取最新的验证码记录
from sqlalchemy import text
code_query = text("""
SELECT * FROM ims_goouc_fullexam_phonecode
WHERE weid = :weid AND phone = :phone
ORDER BY createtime DESC LIMIT 1
""")
user = full_exam_user.get(db, int(uid))
if not user:
raise ValueError("用户不存在")
code_record = db.execute(code_query, {
"weid": user.weid,
"phone": phone
}).fetchone()
if not code_record:
raise ValueError("验证码不存在")
# 检查验证码是否过期5分钟有效期
current_time = int(datetime.now().timestamp())
if code_record.createtime + 300 < current_time:
raise ValueError("验证码已过期")
# 验证码是否正确
if str(code_record.code) != input_code:
raise ValueError("验证码错误")
# 使用CRUD检查手机号是否已绑定其他账号
other_users = full_exam_user.get_multi_by_fields(db, {
"weid": user.weid,
"phone": phone
})
# 过滤掉当前用户
other_users = [u for u in other_users if u.id != int(uid)]
if other_users:
raise ValueError("该手机号已绑定其他账号!")
# 更新用户手机号
try:
# 使用CRUD更新用户手机号
full_exam_user.update(db, db_obj=user, obj_in={"phone": phone})
# 如果用户有姓名,尝试更新学生信息
if user.name:
# 查找学生记录
student = xueshen.get_by_fields(db, {
"weid": user.weid,
"name": user.name
})
# 如果找到学生记录,更新其手机号
if student:
xueshen.update(db, db_obj=student, obj_in={"phone": phone})
return {"status": "success"}
except Exception as e:
logger.error(f"更新手机号失败: {str(e)}")
db.rollback()
raise ValueError(f"更新手机号失败: {str(e)}")
# 更新用户信息
async def update_user_info(uid: str, data: Dict[str, Any], db: Session) -> Dict[str, Any]:
"""更新用户基本信息"""
if not uid:
raise ValueError("传递的参数不存在")
# 获取用户
user = full_exam_user.get_by_id_and_weid(db, int(uid), settings.WECHAT_UNIACID)
if not user or user.istatus != 1:
raise ValueError("非法用户ID")
# 需要更新的字段
update_data = {}
# 姓名字段
if "name" in data:
update_data["name"] = data.get("name", "").strip()
# 处理其他可能需要更新的字段
fields = ["student_id", "id_card", "school", "level", "phone"]
for field in fields:
if field in data and data.get(field) != "null":
update_data[field] = data.get(field)
# 如果没有要更新的数据
if not update_data:
return {"status": 0, "message": "没有提供要更新的数据"}
try:
# 更新用户信息
full_exam_user.update(db, db_obj=user, obj_in=update_data)
# 如果更新了姓名,并且用户有手机号,则尝试更新学生信息
if "name" in update_data and user.phone:
from sqlalchemy import text
update_student_query = text("""
UPDATE ims_goouc_fullexam_xuesheng
SET name = :name
WHERE weid = :weid AND phone = :phone
""")
db.execute(update_student_query, {
"name": update_data["name"],
"weid": user.weid,
"phone": user.phone
})
return {"status": 1, "message": "信息保存成功"}
except Exception as e:
logger.error(f"更新用户信息失败: {str(e)}")
db.rollback()
raise ValueError(f"信息保存失败: {str(e)}")
2025-01-15 20:26:58 +08:00
async def handle_exam_operation(
data: WxappRequest,
db: Session,
user_doexam: CRUDUserDoexam,
user_exam_answer: CRUDUserExamAnswer
):
"""处理考试相关操作"""
operations = {
"submit": submit_exam,
"get_history": get_exam_history,
"get_detail": get_exam_detail,
}
operation = operations.get(data.op)
if not operation:
return {
"code": 1,
"message": f"Unsupported operation: {data.op}"
2025-01-15 20:26:58 +08:00
}
try:
result = await operation(data.uid, data.data, db, user_doexam, user_exam_answer)
return {
"code": 0,
"data": result,
"message": "success"
2025-01-15 20:26:58 +08:00
}
except Exception as e:
return {
"code": 1,
"message": str(e)
2025-01-15 20:26:58 +08:00
}
async def handle_collection(
data: WxappRequest,
db: Session,
user_collection: CRUDUserCollectionPraction
):
"""处理收藏相关操作"""
operations = {
"add": add_collection,
"remove": remove_collection,
"list": list_collections,
}
operation = operations.get(data.op)
if not operation:
return {
"code": 1,
"message": f"Unsupported operation: {data.op}"
2025-01-15 20:26:58 +08:00
}
try:
result = await operation(data.uid, data.data, db, user_collection)
return {
"code": 0,
"data": result,
"message": "success"
2025-01-15 20:26:58 +08:00
}
except Exception as e:
return {
"code": 1,
"message": str(e)
2025-01-15 20:26:58 +08:00
}
async def handle_wrong_question(
data: WxappRequest,
db: Session,
user_wrong_praction: CRUDUserWrongPraction
):
"""处理错题相关操作"""
operations = {
"add": add_wrong_question,
"remove": remove_wrong_question,
"list": list_wrong_questions,
}
operation = operations.get(data.op)
if not operation:
return {
"code": 1,
"message": f"Unsupported operation: {data.op}"
2025-01-15 20:26:58 +08:00
}
try:
result = await operation(data.uid, data.data, db, user_wrong_praction)
return {
"code": 0,
"data": result,
"message": "success"
2025-01-15 20:26:58 +08:00
}
except Exception as e:
return {
"code": 1,
"message": str(e)
2025-01-15 20:26:58 +08:00
}
async def handle_login2(data: WxappRequest, db: Session):
"""处理微信小程序登录2"""
logger.info(f"处理登录请求: {data.uid}")
try:
# 修改这里查找code的位置
# 首先检查data.data中是否有code
code = data.data.get("code") if isinstance(data.data, dict) else None
# 如果在data.data中没有找到code尝试直接从data对象获取
if not code and hasattr(data, "code"):
code = data.code
logger.debug(f"登录请求数据: {data}")
if not code:
logger.warning(f"登录失败: code为空 - UID: {data.uid}")
return {
"code": 1,
"message": "code值获取失败",
"data": {"message": "code值获取失败"}
}
try:
# 调用微信API获取openid和session_key
logger.debug(f"开始调用微信API: code={code}")
wx_info = await wx_api.code2session(code)
logger.debug(f"微信API返回结果: {wx_info}")
except httpx.ConnectTimeout:
# 特别处理连接超时错误
logger.error("连接微信服务器超时,请检查网络连接")
return {
"code": 1,
"message": "连接微信服务器超时,请稍后再试",
"data": {"message": "网络连接问题,无法连接到微信服务器"}
}
except Exception as e:
logger.error(f"微信API调用失败: {str(e)}", exc_info=True)
return {
"code": 1,
"message": "获取用户信息失败",
"data": {"message": str(e)}
}
# 检查是否有错误
if wx_info.get("errcode") == 4001:
return {
"code": 1,
"message": "获取用户信息失败",
"data": {"message": wx_info.get("errmessage")}
}
# 确保weid是整数
try:
weid = int(settings.WECHAT_UNIACID)
except ValueError:
# 如果无法转换为整数,使用默认值
logger.warning(f"WECHAT_UNIACID '{settings.WECHAT_UNIACID}' 无法转换为整数使用默认值1")
weid = 1
# 查询用户 - 注意这里是用openid和weid查询
users = full_exam_user.get_by_openid_and_weid(
db,
wx_info["openid"],
weid # 使用整数weid
)
info_status = 2 # 默认状态
# 构建用户数据
current_time = int(datetime.now().timestamp())
user_data = {
"openid": wx_info["openid"],
"nickname": data.data.get("nickName"),
"nativeplace": f"{data.data.get('province')},{data.data.get('city')}",
"headimg": data.data.get("avatarUrl"),
"unionid": wx_info.get("unionid"),
"createtime": current_time,
"weid": weid, # 现在weid已定义
"last_login_time": current_time,
"h5_openid": wx_info["openid"]
}
if not users:
logger.debug(f"用户不存在,创建新用户: {user_data}")
# 创建新用户 - 使用CRUD实例
try:
logger.debug(f"创建新用户: {user_data}")
# 使用UserMemberCreate schema需要导入
from mooc.schemas.goouc_fullexam_user import FullExamUserCreate
# 转换为schema对象
user_create_schema = FullExamUserCreate(**user_data)
# 使用CRUD实例创建用户
new_user = full_exam_user.create(db, obj_in=user_create_schema)
uid = new_user.id
except Exception as e:
logger.error(f"创建新用户失败: {e}")
return {
"code": 1,
"message": "用户数据插入失败",
"data": str(e)
}
# 处理二维码生成
try:
logger.debug(f"开始生成二维码")
access_token = await wx_api.get_access_token()
# 创建二维码目录
qrcode_path = "../addons/goouc_fullexam/Qrcode/"
if not os.path.exists(qrcode_path):
os.makedirs(qrcode_path, mode=0o777, exist_ok=True)
filename = f"qrcode_{uid}.jpg"
# 生成二维码并保存
res = await wx_api.save_unlimited_qrcode(
uid,
qrcode_path,
filename,
access_token
)
if res:
# 更新用户二维码路径 - 使用CRUD实例
from mooc.schemas.goouc_fullexam_user import FullExamUserUpdate
update_data = {"qrcode": os.path.join(qrcode_path, filename)}
update_schema = FullExamUserUpdate(**update_data)
# 使用CRUD实例更新用户
full_exam_user.update(db, db_obj=new_user, obj_in=update_schema)
except Exception as e:
logger.error(f"生成二维码错误: {e}")
return {
"code": 1,
"message": "生成二维码错误了",
"data": str(e)
}
else:
logger.debug(f"用户存在,更新用户信息: {users}")
# 检查用户状态
if users.status != 1:
return {
"code": 1,
"message": "您已被禁用,请联系管理员处理~",
"data": users
}
# 更新用户信息 - 使用CRUD实例
try:
logger.debug(f"开始更新用户信息: {user_data}")
from mooc.schemas.goouc_fullexam_user import FullExamUserUpdate
update_schema = FullExamUserUpdate(**user_data)
full_exam_user.update(db, db_obj=users, obj_in=update_schema)
uid = users.id
if users.phone:
info_status = 1
except Exception as e:
logger.error(f"更新用户信息失败: {e}")
return {
"code": 1,
"message": "更新用户信息失败",
"data": str(e)
}
logger.info(f"登录成功用户ID: {uid}, 状态: {info_status}")
return ResponseFactory.success(
data={"uid": uid, "info_status": info_status, "info": {"uid": uid, "info_status": info_status}},
message="登录成功"
)
except Exception as e:
logger.exception(f"登录过程发生未处理异常: {str(e)}")
return ResponseFactory.error(
code=1,
message=f"登录失败: {str(e)}",
data=None
)