#!/usr/bin/env python # -*- coding: utf-8 -*- """ 认证接口 """ from flask import Blueprint, request, jsonify import logging from datetime import datetime from auth_utils import AuthUtils from database_config import get_db_manager, format_datetime_fields from log_utils import log_create, log_update, log_delete, log_error, log_operation logger = logging.getLogger('article_server') # 创建蓝图 auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth') @auth_bp.route('/login', methods=['POST']) def login(): """统一登录接口(支持企业主和员工)""" client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知')) logger.info(f"[用户登录] 开始处理登录请求, IP: {client_ip}") try: data = request.get_json() if not data: logger.warning(f"[用户登录] 请求参数为空, IP: {client_ip}") return jsonify({ 'code': 400, 'message': '请求参数错误', 'data': None }), 400 # 支持手机号或用户名登录 phone = data.get('phone') username = data.get('username') password = data.get('password') login_account = phone or username logger.info(f"[用户登录] 收到登录请求, 账号: {login_account}, IP: {client_ip}") if not login_account or not password: logger.warning(f"[用户登录] 账号或密码为空, 账号: {login_account}, IP: {client_ip}") return jsonify({ 'code': 400, 'message': '账号和密码不能为空', 'data': None }), 400 # 第一步:先在 ai_users 表查询用户 # 从ai_authors表获取xhs_account字段以优化性能 logger.info(f"[用户登录] 开始在ai_users表查询用户, 账号: {login_account}") db_manager = get_db_manager() # 支持手机号或用户名登录 sql = """ SELECT u.id, u.enterprise_id, u.enterprise_name, u.username, u.phone, u.password, u.real_name, u.role, u.status, u.is_bound_xhs, a.xhs_account FROM ai_users u LEFT JOIN ai_authors a ON u.id = a.created_user_id AND a.status = 'active' WHERE (u.phone = %s OR u.username = %s) AND u.status = 'active' """ result = db_manager.execute_query(sql, (login_account, login_account)) if not result: logger.warning(f"[用户登录失败] 用户不存在或已禁用: {login_account}, IP: {client_ip}") return jsonify({ 'code': 401, 'message': '账号或密码错误', 'data': None }), 401 user = result[0] logger.info(f"[用户登录] 查询到用户信息, 用户名: {user['username']}, 角色: {user['role']}, 企业: {user['enterprise_name']}, 企业ID: {user['enterprise_id']}") # 验证密码 logger.info(f"[用户登录] 开始验证密码, 账号: {login_account}") if not AuthUtils.verify_password(password, user['password']): logger.warning(f"[用户登录失败] 密码错误: {login_account}, 用户名: {user['username']}, IP: {client_ip}") return jsonify({ 'code': 401, 'message': '账号或密码错误', 'data': None }), 401 logger.info(f"[用户登录] 密码验证成功, 用户名: {user['username']}, 角色: {user['role']}") # 如果是企业角色,获取企业详细信息 enterprise_info = None if user['role'] == 'enterprise' and user['enterprise_id']: logger.info(f"[用户登录] 检测到企业角色, 开始获取企业详细信息, 企业ID: {user['enterprise_id']}") enterprise_sql = """ SELECT id, enterprise_ID, name, short_name, email, status, users_total, products_total, articles_total, published_total FROM ai_enterprises WHERE id = %s AND status = 'active' """ enterprise_result = db_manager.execute_query(enterprise_sql, (user['enterprise_id'],)) if enterprise_result: enterprise_info = enterprise_result[0] logger.info(f"[用户登录] 获取企业信息成功, 企业名称: {enterprise_info['name']}, 企业编号: {enterprise_info['enterprise_ID']}") else: logger.warning(f"[用户登录] 企业信息不存在或已禁用, 企业ID: {user['enterprise_id']}") # 生成token logger.info(f"[用户登录] 开始生成token, 用户ID: {user['id']}, 角色: {user['role']}") token = AuthUtils.generate_token( user['id'], user['phone'] or user['username'], user['role'], user['enterprise_id'] ) # 构造返回数据 user_info = { 'id': user['id'], 'name': user['real_name'] or user['username'], 'username': user['username'], 'phone': user['phone'], 'role': user['role'], 'enterpriseId': user['enterprise_id'], 'enterpriseName': user['enterprise_name'], 'isBoundXHS': bool(user.get('is_bound_xhs', 0)), 'xhsAccount': user.get('xhs_account', '') } # 如果是企业角色且有企业信息,添加企业详细信息 if enterprise_info: user_info.update({ 'enterpriseCode': enterprise_info['enterprise_ID'], 'enterpriseShortName': enterprise_info['short_name'], 'enterpriseEmail': enterprise_info['email'], 'enterprisePhone': user['phone'], # 企业管理员的手机号从user表获取 'usersTotal': enterprise_info['users_total'], 'productsTotal': enterprise_info['products_total'], 'articlesTotal': enterprise_info['articles_total'], 'publishedTotal': enterprise_info['published_total'] }) logger.info(f"[用户登录成功] Token生成成功, 用户: {user['username']}, 角色: {user['role']}, 企业: {user['enterprise_name']}, IP: {client_ip}") # 返回用户信息和token return jsonify({ 'code': 200, 'message': '登录成功', 'data': { 'token': token, 'userInfo': user_info }, 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"[用户登录处理] 处理请求时发生错误: {str(e)}", exc_info=True) return jsonify({ 'code': 500, 'message': '服务器内部错误', 'data': None }), 500 @auth_bp.route('/employee/login', methods=['POST']) def employee_login(): """员工登录""" client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知')) logger.info(f"[员工登录] 开始处理员工登录请求, IP: {client_ip}") try: data = request.get_json() if not data: logger.warning(f"[员工登录] 请求参数为空, IP: {client_ip}") return jsonify({ 'code': 400, 'message': '请求参数错误', 'data': None }), 400 phone = data.get('phone') password = data.get('password') logger.info(f"[员工登录] 收到登录请求, 手机号: {phone}, IP: {client_ip}") if not phone or not password: logger.warning(f"[员工登录] 手机号或密码为空, 手机号: {phone}, IP: {client_ip}") return jsonify({ 'code': 400, 'message': '手机号和密码不能为空', 'data': None }), 400 # 查询员工用户 # 从ai_authors表获取xhs_account字段以优化性能 logger.info(f"[员工登录] 开始查询员工信息, 手机号: {phone}") db_manager = get_db_manager() sql = """ SELECT u.id, u.enterprise_id, u.real_name, u.phone, u.password, u.role, u.status, u.is_bound_xhs, a.xhs_account, e.name as enterprise_name FROM ai_users u LEFT JOIN ai_enterprises e ON u.enterprise_id = e.id LEFT JOIN ai_authors a ON u.id = a.created_user_id AND a.status = 'active' WHERE u.phone = %s """ result = db_manager.execute_query(sql, (phone,)) if not result: logger.warning(f"[员工登录失败] 用户不存在: {phone}, IP: {client_ip}") return jsonify({ 'code': 401, 'message': '手机号或密码错误', 'data': None }), 401 user = result[0] logger.info(f"[员工登录] 查询到员工信息, 姓名: {user['real_name']}, 角色: {user['role']}, 企业: {user['enterprise_name']}, 状态: {user['status']}, 绑定小红书: {user.get('is_bound_xhs', 0)}") # 检查用户状态 if user['status'] != 'active': logger.warning(f"[员工登录失败] 用户状态异常: {phone}, 状态: {user['status']}, 姓名: {user['real_name']}, IP: {client_ip}") return jsonify({ 'code': 403, 'message': '用户已被禁用', 'data': None }), 403 # 验证密码 logger.info(f"[员工登录] 开始验证密码, 手机号: {phone}, 姓名: {user['real_name']}") if not AuthUtils.verify_password(password, user['password']): logger.warning(f"[员工登录失败] 密码错误: {phone}, 姓名: {user['real_name']}, IP: {client_ip}") return jsonify({ 'code': 401, 'message': '手机号或密码错误', 'data': None }), 401 # 生成token logger.info(f"[员工登录] 密码验证成功, 开始生成token, 姓名: {user['real_name']}, 角色: {user['role']}, 企业: {user['enterprise_name']}") token = AuthUtils.generate_token(user['id'], phone, user['role'], user['enterprise_id']) logger.info(f"[员工登录成功] Token生成成功, 姓名: {user['real_name']}, 手机号: {phone}, 角色: {user['role']}, 企业: {user['enterprise_name']}, IP: {client_ip}") # 返回用户信息和token return jsonify({ 'code': 200, 'message': '登录成功', 'data': { 'token': token, 'userInfo': { 'id': user['id'], 'name': user['real_name'], 'phone': user['phone'], 'role': user['role'], 'enterpriseId': user['enterprise_id'], 'enterpriseName': user['enterprise_name'], 'isBoundXHS': bool(user.get('is_bound_xhs', 0)), 'xhsAccount': user.get('xhs_account', '') } }, 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"[员工登录处理] 处理请求时发生错误: {str(e)}", exc_info=True) return jsonify({ 'code': 500, 'message': '服务器内部错误', 'data': None }), 500 @auth_bp.route('/logout', methods=['POST']) def logout(): """用户登出""" try: # 记录登出日志 auth_header = request.headers.get('Authorization') if auth_header: parts = auth_header.split() if len(parts) == 2: token = parts[1] payload = AuthUtils.verify_token(token) if payload: logger.info(f"[登出成功] 用户ID: {payload.get('user_id')}") return jsonify({ 'code': 200, 'message': '退出成功', 'data': None, 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"[登出处理] 处理请求时发生错误: {str(e)}", exc_info=True) return jsonify({ 'code': 500, 'message': '服务器内部错误', 'data': None }), 500