299 lines
12 KiB
Python
299 lines
12 KiB
Python
|
|
#!/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
|