2026-1-6
This commit is contained in:
1346
release/36/article_routes.py
Normal file
1346
release/36/article_routes.py
Normal file
File diff suppressed because it is too large
Load Diff
378
release/36/assign_authors_to_articles.py
Normal file
378
release/36/assign_authors_to_articles.py
Normal file
@@ -0,0 +1,378 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
文章自动分配作者脚本
|
||||
监控数据库中status为pending_review且author_id=0的文章,自动分配作者
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from typing import Dict, List
|
||||
import traceback
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from database_config import get_db_manager
|
||||
from log_config import setup_logger
|
||||
|
||||
# 配置日志记录器,支持按日期切割和控制台输出
|
||||
logger = setup_logger(
|
||||
name='assign_authors_to_articles',
|
||||
log_file='logs/assign_authors_to_articles.log',
|
||||
error_log_file='logs/assign_authors_to_articles_error.log',
|
||||
level=logging.INFO,
|
||||
console_output=True
|
||||
)
|
||||
|
||||
# 配置常量
|
||||
BASE_URL = "http://127.0.0.1:8216"
|
||||
SLEEP_INTERVAL = 5 # 监控间隔(秒),可配置
|
||||
|
||||
class AssignAuthorsToArticles:
|
||||
def __init__(self):
|
||||
# API配置
|
||||
self.base_url = BASE_URL
|
||||
|
||||
# 认证信息
|
||||
self.jwt_token = None
|
||||
|
||||
# 使用统一的数据库管理器
|
||||
self.db_manager = get_db_manager()
|
||||
|
||||
# 登录配置 - 使用雇员账号
|
||||
self.login_credentials = {
|
||||
'username': '13621242430',
|
||||
'password': 'admin123'
|
||||
}
|
||||
|
||||
# 禁用代理
|
||||
self.proxies = {
|
||||
'http': None,
|
||||
'https': None
|
||||
}
|
||||
|
||||
logger.info("AssignAuthorsToArticles 初始化完成")
|
||||
|
||||
def log_to_database(self, level: str, message: str, details: str = None):
|
||||
"""记录日志到数据库ai_logs表"""
|
||||
try:
|
||||
with self.db_manager.get_cursor() as cursor:
|
||||
# 映射日志级别到数据库状态
|
||||
status_map = {
|
||||
'INFO': 'success',
|
||||
'WARNING': 'warning',
|
||||
'ERROR': 'error'
|
||||
}
|
||||
status = status_map.get(level, 'success')
|
||||
|
||||
sql = """
|
||||
INSERT INTO ai_logs (user_id, action, description, status, error_message, created_at)
|
||||
VALUES (%s, %s, %s, %s, %s, NOW())
|
||||
"""
|
||||
cursor.execute(sql, (None, 'assign_authors', message, status, details))
|
||||
logger.info(f"日志已记录到数据库: {level} - {message}")
|
||||
except Exception as e:
|
||||
logger.error(f"记录日志到数据库失败: {e}")
|
||||
|
||||
def login_and_get_jwt_token(self) -> bool:
|
||||
"""登录获取JWT token"""
|
||||
try:
|
||||
login_url = f"{self.base_url}/api/auth/login"
|
||||
login_data = {
|
||||
"username": self.login_credentials['username'],
|
||||
"password": self.login_credentials['password']
|
||||
}
|
||||
|
||||
logger.info(f"尝试登录: {login_data['username']}")
|
||||
self.log_to_database('INFO', f"尝试登录用户: {login_data['username']}")
|
||||
|
||||
response = requests.post(
|
||||
login_url,
|
||||
json=login_data,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
proxies=self.proxies
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('code') == 200:
|
||||
self.jwt_token = result['data']['token']
|
||||
logger.info("JWT token获取成功")
|
||||
self.log_to_database('INFO', "JWT token获取成功")
|
||||
return True
|
||||
else:
|
||||
error_msg = f"登录失败: {result.get('message', '未知错误')}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, json.dumps(result))
|
||||
return False
|
||||
else:
|
||||
error_msg = f"登录请求失败: {response.status_code}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, response.text)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"登录异常: {e}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, traceback.format_exc())
|
||||
return False
|
||||
|
||||
def get_pending_articles(self) -> List[Dict]:
|
||||
"""获取待分配作者的文章:enterprise_id=1, status=pending_review, author_id=0"""
|
||||
try:
|
||||
with self.db_manager.get_cursor() as cursor:
|
||||
sql = """
|
||||
SELECT id, title, batch_id, enterprise_id, product_id, status,
|
||||
author_id, created_at, updated_at
|
||||
FROM ai_articles
|
||||
WHERE enterprise_id = 1
|
||||
AND status = 'pending_review'
|
||||
AND author_id = 0
|
||||
AND image_count > 0
|
||||
ORDER BY created_at ASC
|
||||
LIMIT 1000
|
||||
"""
|
||||
cursor.execute(sql)
|
||||
results = cursor.fetchall()
|
||||
|
||||
if results:
|
||||
logger.info(f"查询到 {len(results)} 篇待分配作者的文章")
|
||||
for result in results:
|
||||
logger.info(f"待分配文章 - ID: {result['id']}, 标题: {result['title']}, 状态: {result['status']}")
|
||||
else:
|
||||
logger.info("未查询到待分配作者的文章")
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"查询待分配文章异常: {e}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, traceback.format_exc())
|
||||
return []
|
||||
|
||||
def get_active_authors(self) -> List[Dict]:
|
||||
"""获取活跃作者:enterprise_id=1, status=active, phone!=空, created_user_id>0"""
|
||||
try:
|
||||
with self.db_manager.get_cursor() as cursor:
|
||||
sql = """
|
||||
SELECT id, author_name, phone, app_id, app_token,
|
||||
department_id, department_name, status, created_at
|
||||
FROM ai_authors
|
||||
WHERE enterprise_id = 1
|
||||
AND status = 'active'
|
||||
AND phone IS NOT NULL AND phone != ''
|
||||
AND created_user_id > 0
|
||||
ORDER BY created_at ASC
|
||||
LIMIT 1000
|
||||
"""
|
||||
cursor.execute(sql)
|
||||
results = cursor.fetchall()
|
||||
|
||||
if results:
|
||||
logger.info(f"查询到 {len(results)} 个活跃作者")
|
||||
for result in results:
|
||||
logger.info(f"活跃作者 - ID: {result['id']}, 姓名: {result['author_name']}, 手机: {result['phone']}")
|
||||
else:
|
||||
logger.info("未查询到活跃作者")
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"查询活跃作者异常: {e}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, traceback.format_exc())
|
||||
return []
|
||||
|
||||
def assign_authors_to_articles_batch(self, article_ids: List[int], author_ids: List[int]) -> bool:
|
||||
"""调用批量分配接口:/api/articles/batch-published-account-cycle"""
|
||||
try:
|
||||
if not article_ids or not author_ids:
|
||||
logger.warning("文章ID或作者ID列表为空,跳过分配")
|
||||
return False
|
||||
|
||||
# 确保有JWT token
|
||||
if not self.jwt_token:
|
||||
logger.warning("JWT token缺失,尝试重新登录")
|
||||
if not self.login_and_get_jwt_token():
|
||||
logger.error("重新登录失败")
|
||||
return False
|
||||
|
||||
assign_url = f"{self.base_url}/api/articles/batch-published-account-cycle"
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.jwt_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
data = {
|
||||
'article_ids': article_ids,
|
||||
'author_ids': author_ids
|
||||
}
|
||||
|
||||
logger.info(f"开始批量分配作者 - 文章数: {len(article_ids)}, 作者数: {len(author_ids)}")
|
||||
logger.info(f"文章ID: {article_ids}")
|
||||
logger.info(f"作者ID: {author_ids}")
|
||||
self.log_to_database('INFO', f"开始批量分配作者",
|
||||
f"文章数: {len(article_ids)}, 作者数: {len(author_ids)}")
|
||||
|
||||
response = requests.post(
|
||||
assign_url,
|
||||
json=data,
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
proxies=self.proxies
|
||||
)
|
||||
|
||||
logger.info(f"批量分配响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
logger.info(f"批量分配响应: {result}")
|
||||
|
||||
if result.get('code') == 200:
|
||||
data = result.get('data', {})
|
||||
success_msg = f"批量分配成功 - 成功: {data.get('published_count')}, 失败: {data.get('failed_count')}"
|
||||
logger.info(success_msg)
|
||||
self.log_to_database('INFO', success_msg, json.dumps(result, ensure_ascii=False))
|
||||
return True
|
||||
else:
|
||||
error_msg = f"批量分配失败: {result.get('message', '未知错误')}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, json.dumps(result, ensure_ascii=False))
|
||||
return False
|
||||
elif response.status_code == 401:
|
||||
# Token过期,重新登录后重试
|
||||
logger.warning("JWT token过期(401),尝试重新登录")
|
||||
if self.login_and_get_jwt_token():
|
||||
logger.info("重新登录成功,重试批量分配")
|
||||
headers['Authorization'] = f'Bearer {self.jwt_token}'
|
||||
|
||||
retry_response = requests.post(
|
||||
assign_url,
|
||||
json=data,
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
proxies=self.proxies
|
||||
)
|
||||
|
||||
if retry_response.status_code == 200:
|
||||
retry_result = retry_response.json()
|
||||
if retry_result.get('code') == 200:
|
||||
data = retry_result.get('data', {})
|
||||
success_msg = f"重试批量分配成功 - 成功: {data.get('published_count')}, 失败: {data.get('failed_count')}"
|
||||
logger.info(success_msg)
|
||||
self.log_to_database('INFO', success_msg, json.dumps(retry_result, ensure_ascii=False))
|
||||
return True
|
||||
|
||||
error_msg = f"重试批量分配失败: {retry_response.status_code}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, retry_response.text)
|
||||
return False
|
||||
else:
|
||||
logger.error("重新登录失败")
|
||||
return False
|
||||
else:
|
||||
error_msg = f"批量分配请求失败: {response.status_code}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, response.text)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"批量分配异常: {e}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
"""主运行逻辑"""
|
||||
try:
|
||||
logger.info("="*60)
|
||||
logger.info("开始执行文章自动分配作者任务")
|
||||
logger.info("="*60)
|
||||
self.log_to_database('INFO', "开始执行文章自动分配作者任务")
|
||||
|
||||
# 1. 获取待分配作者的文章
|
||||
logger.info("步骤1: 获取待分配作者的文章")
|
||||
articles = self.get_pending_articles()
|
||||
if not articles:
|
||||
logger.info("没有待分配作者的文章,任务结束")
|
||||
self.log_to_database('INFO', "没有待分配作者的文章,任务结束")
|
||||
return True
|
||||
|
||||
# 2. 获取活跃作者
|
||||
logger.info("步骤2: 获取活跃作者")
|
||||
authors = self.get_active_authors()
|
||||
if not authors:
|
||||
logger.error("没有活跃作者,无法分配")
|
||||
self.log_to_database('ERROR', "没有活跃作者,无法分配")
|
||||
return False
|
||||
|
||||
# 3. 批量分配作者
|
||||
logger.info("步骤3: 批量分配作者")
|
||||
article_ids = [article['id'] for article in articles]
|
||||
author_ids = [author['id'] for author in authors]
|
||||
|
||||
logger.info(f"准备分配 {len(article_ids)} 篇文章给 {len(author_ids)} 个作者")
|
||||
self.log_to_database('INFO', f"准备分配文章",
|
||||
f"文章数: {len(article_ids)}, 作者数: {len(author_ids)}")
|
||||
|
||||
success = self.assign_authors_to_articles_batch(article_ids, author_ids)
|
||||
|
||||
if success:
|
||||
logger.info("="*60)
|
||||
logger.info("文章自动分配作者任务完成")
|
||||
logger.info("="*60)
|
||||
self.log_to_database('INFO', "文章自动分配作者任务完成")
|
||||
return True
|
||||
else:
|
||||
logger.error("文章自动分配作者任务失败")
|
||||
self.log_to_database('ERROR', "文章自动分配作者任务失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"运行任务异常: {e}"
|
||||
logger.error(error_msg)
|
||||
self.log_to_database('ERROR', error_msg, traceback.format_exc())
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数 - 持续监控模式"""
|
||||
generator = AssignAuthorsToArticles()
|
||||
|
||||
logger.info("="*60)
|
||||
logger.info(f"启动文章自动分配作者监控服务")
|
||||
logger.info(f"监控间隔: {SLEEP_INTERVAL}秒")
|
||||
logger.info("="*60)
|
||||
|
||||
# 首次登录获取JWT token
|
||||
logger.info("首次登录获取JWT token")
|
||||
if not generator.login_and_get_jwt_token():
|
||||
logger.error("首次登录失败,程序退出")
|
||||
generator.log_to_database('ERROR', "首次登录失败,程序退出")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 执行文章自动分配作者任务
|
||||
generator.run()
|
||||
|
||||
# 等待下一次检查
|
||||
logger.info(f"等待 {SLEEP_INTERVAL} 秒后进行下一次检查...")
|
||||
time.sleep(SLEEP_INTERVAL)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("="*60)
|
||||
logger.info("收到中断信号,停止监控服务")
|
||||
logger.info("="*60)
|
||||
generator.log_to_database('INFO', '监控服务手动停止', 'KeyboardInterrupt')
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"程序运行异常: {e}")
|
||||
generator.log_to_database('ERROR', f'程序运行异常: {e}', traceback.format_exc())
|
||||
logger.info(f"等待 {SLEEP_INTERVAL} 秒后重试...")
|
||||
time.sleep(SLEEP_INTERVAL)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
609
release/36/user_routes.py
Normal file
609
release/36/user_routes.py
Normal file
@@ -0,0 +1,609 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用户管理接口
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from auth_utils import require_auth, require_role, 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')
|
||||
|
||||
# 创建蓝图
|
||||
user_bp = Blueprint('user', __name__, url_prefix='/api/users')
|
||||
|
||||
@user_bp.route('', methods=['GET'])
|
||||
@require_auth
|
||||
def get_users():
|
||||
"""获取用户列表"""
|
||||
try:
|
||||
# 获取查询参数
|
||||
page = int(request.args.get('page', 1))
|
||||
size = int(request.args.get('size', 10))
|
||||
role = request.args.get('role')
|
||||
status = request.args.get('status')
|
||||
|
||||
# 构建查询条件
|
||||
where_conditions = ["1=1"]
|
||||
params = []
|
||||
|
||||
if role:
|
||||
where_conditions.append("role = %s")
|
||||
params.append(role)
|
||||
|
||||
if status:
|
||||
where_conditions.append("status = %s")
|
||||
params.append(status)
|
||||
|
||||
where_clause = " AND ".join(where_conditions)
|
||||
|
||||
# 计算偏移量
|
||||
offset = (page - 1) * size
|
||||
|
||||
db_manager = get_db_manager()
|
||||
|
||||
# 查询总数
|
||||
count_sql = f"SELECT COUNT(*) as total FROM wht_admin_users WHERE {where_clause}"
|
||||
count_result = db_manager.execute_query(count_sql, params)
|
||||
total = count_result[0]['total']
|
||||
|
||||
# 查询用户列表
|
||||
sql = f"""
|
||||
SELECT id, username, real_name, email, phone, role, status, created_at, updated_at
|
||||
FROM wht_admin_users
|
||||
WHERE {where_clause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %s OFFSET %s
|
||||
"""
|
||||
params.extend([size, offset])
|
||||
users = db_manager.execute_query(sql, params)
|
||||
|
||||
# 格式化日期时间字段
|
||||
users = format_datetime_fields(users)
|
||||
|
||||
# 记录操作日志
|
||||
current_user = AuthUtils.get_current_user()
|
||||
log_sql = """
|
||||
INSERT INTO wht_logs (user_id, action, description, ip_address, user_agent, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
user_agent = request.headers.get('User-Agent', '未知')
|
||||
db_manager.execute_insert(log_sql, (
|
||||
current_user['user_id'],
|
||||
'get_users',
|
||||
f'获取用户列表,总数: {total}',
|
||||
client_ip,
|
||||
user_agent,
|
||||
'success'
|
||||
))
|
||||
|
||||
logger.info(f"获取用户列表成功,总数: {total}")
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': {
|
||||
'list': users,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'size': size
|
||||
},
|
||||
'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
|
||||
|
||||
@user_bp.route('/<int:user_id>', methods=['GET'])
|
||||
@require_auth
|
||||
def get_user(user_id):
|
||||
"""获取特定用户"""
|
||||
try:
|
||||
db_manager = get_db_manager()
|
||||
sql = """
|
||||
SELECT id, username, real_name, email, phone, role, status, created_at, updated_at
|
||||
FROM wht_admin_users
|
||||
WHERE id = %s AND status != 'deleted'
|
||||
"""
|
||||
result = db_manager.execute_query(sql, (user_id,))
|
||||
logger.info(f"SQL查询结果: {result}")
|
||||
|
||||
if not result:
|
||||
return jsonify({
|
||||
'code': 404,
|
||||
'message': '用户不存在',
|
||||
'data': None
|
||||
}), 404
|
||||
|
||||
user_data = result[0]
|
||||
|
||||
# 格式化日期时间字段
|
||||
user_data = format_datetime_fields(user_data)
|
||||
|
||||
# 记录操作日志
|
||||
current_user = AuthUtils.get_current_user()
|
||||
log_sql = """
|
||||
INSERT INTO wht_logs (user_id, action, target_type, target_id, description, ip_address, user_agent, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
user_agent = request.headers.get('User-Agent', '未知')
|
||||
db_manager.execute_insert(log_sql, (
|
||||
current_user['user_id'],
|
||||
'get_user',
|
||||
'user',
|
||||
user_id,
|
||||
f'获取用户信息: {user_data["username"]}',
|
||||
client_ip,
|
||||
user_agent,
|
||||
'success'
|
||||
))
|
||||
|
||||
logger.info(f"获取用户信息成功: {user_data['username']}")
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': user_data,
|
||||
'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
|
||||
|
||||
@user_bp.route('', methods=['POST'])
|
||||
@require_auth
|
||||
@require_role('admin')
|
||||
def create_user():
|
||||
"""创建用户"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '请求参数错误',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证必需字段
|
||||
required_fields = ['username', 'password', 'real_name', 'role']
|
||||
for field in required_fields:
|
||||
if not data.get(field):
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': f'缺少必需字段: {field}',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 数据验证
|
||||
import re
|
||||
|
||||
# 验证用户名长度
|
||||
if len(data['username']) > 50:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '用户名长度不能超过50个字符',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证密码长度
|
||||
if len(data['password']) < 6:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '密码长度不能少于6个字符',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证邮箱格式
|
||||
if data.get('email'):
|
||||
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
||||
if not re.match(email_pattern, data['email']):
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '邮箱格式不正确',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证角色
|
||||
valid_roles = ['admin', 'editor', 'reviewer', 'publisher']
|
||||
if data['role'] not in valid_roles:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '无效的用户角色',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 检查用户名是否已存在
|
||||
db_manager = get_db_manager()
|
||||
check_sql = "SELECT id FROM wht_admin_users WHERE username = %s"
|
||||
existing_user = db_manager.execute_query(check_sql, (data['username'],))
|
||||
|
||||
if existing_user:
|
||||
return jsonify({
|
||||
'code': 409,
|
||||
'message': '用户名已存在',
|
||||
'data': None
|
||||
}), 409
|
||||
|
||||
# 创建用户
|
||||
sql = """
|
||||
INSERT INTO wht_admin_users (username, password, real_name, email, phone, role, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
hashed_password = AuthUtils.hash_password(data['password'])
|
||||
user_id = db_manager.execute_insert(sql, (
|
||||
data['username'],
|
||||
hashed_password,
|
||||
data['real_name'],
|
||||
data.get('email'),
|
||||
data.get('phone'),
|
||||
data['role'],
|
||||
'active'
|
||||
))
|
||||
|
||||
# 记录操作日志
|
||||
current_user = AuthUtils.get_current_user()
|
||||
log_sql = """
|
||||
INSERT INTO wht_logs (user_id, action, target_type, target_id, description, ip_address, user_agent, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
user_agent = request.headers.get('User-Agent', '未知')
|
||||
db_manager.execute_insert(log_sql, (
|
||||
current_user['user_id'],
|
||||
'create_user',
|
||||
'user',
|
||||
user_id,
|
||||
f'创建用户: {data["username"]}',
|
||||
client_ip,
|
||||
user_agent,
|
||||
'success'
|
||||
))
|
||||
|
||||
logger.info(f"创建用户成功: {data['username']}, ID: {user_id}")
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': '创建成功',
|
||||
'data': {'id': user_id},
|
||||
'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
|
||||
|
||||
@user_bp.route('/<int:user_id>', methods=['PUT'])
|
||||
@require_auth
|
||||
@require_role('admin')
|
||||
def update_user(user_id):
|
||||
"""更新用户"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '请求参数错误',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 检查用户是否存在
|
||||
db_manager = get_db_manager()
|
||||
check_sql = "SELECT id, username FROM wht_admin_users WHERE id = %s"
|
||||
existing_user = db_manager.execute_query(check_sql, (user_id,))
|
||||
|
||||
if not existing_user:
|
||||
return jsonify({
|
||||
'code': 404,
|
||||
'message': '用户不存在',
|
||||
'data': None
|
||||
}), 404
|
||||
|
||||
# 构建更新字段
|
||||
update_fields = []
|
||||
params = []
|
||||
|
||||
if 'real_name' in data:
|
||||
update_fields.append("real_name = %s")
|
||||
params.append(data['real_name'])
|
||||
|
||||
if 'email' in data:
|
||||
update_fields.append("email = %s")
|
||||
params.append(data['email'])
|
||||
|
||||
if 'phone' in data:
|
||||
update_fields.append("phone = %s")
|
||||
params.append(data['phone'])
|
||||
|
||||
if 'role' in data:
|
||||
update_fields.append("role = %s")
|
||||
params.append(data['role'])
|
||||
|
||||
if 'status' in data:
|
||||
update_fields.append("status = %s")
|
||||
params.append(data['status'])
|
||||
|
||||
if not update_fields:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '没有要更新的字段',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 执行更新
|
||||
params.append(user_id)
|
||||
sql = f"UPDATE wht_admin_users SET {', '.join(update_fields)} WHERE id = %s"
|
||||
affected_rows = db_manager.execute_update(sql, params)
|
||||
|
||||
# 记录操作日志
|
||||
current_user = AuthUtils.get_current_user()
|
||||
log_sql = """
|
||||
INSERT INTO wht_logs (user_id, action, target_type, target_id, description, ip_address, user_agent, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
user_agent = request.headers.get('User-Agent', '未知')
|
||||
db_manager.execute_insert(log_sql, (
|
||||
current_user['user_id'],
|
||||
'update_user',
|
||||
'user',
|
||||
user_id,
|
||||
f'更新用户: {existing_user[0]["username"]}',
|
||||
client_ip,
|
||||
user_agent,
|
||||
'success'
|
||||
))
|
||||
|
||||
logger.info(f"更新用户成功: {existing_user[0]['username']}, 影响行数: {affected_rows}")
|
||||
|
||||
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
|
||||
|
||||
@user_bp.route('/<int:user_id>', methods=['DELETE'])
|
||||
@require_auth
|
||||
@require_role('admin')
|
||||
def delete_user(user_id):
|
||||
"""删除用户"""
|
||||
try:
|
||||
# 检查用户是否存在
|
||||
db_manager = get_db_manager()
|
||||
check_sql = "SELECT id, username FROM wht_admin_users WHERE id = %s"
|
||||
existing_user = db_manager.execute_query(check_sql, (user_id,))
|
||||
|
||||
if not existing_user:
|
||||
return jsonify({
|
||||
'code': 404,
|
||||
'message': '用户不存在',
|
||||
'data': None
|
||||
}), 404
|
||||
|
||||
# 软删除用户(设置状态为deleted)
|
||||
sql = "UPDATE wht_admin_users SET status = 'deleted' WHERE id = %s"
|
||||
affected_rows = db_manager.execute_update(sql, (user_id,))
|
||||
|
||||
# 记录操作日志
|
||||
current_user = AuthUtils.get_current_user()
|
||||
log_sql = """
|
||||
INSERT INTO wht_logs (user_id, action, target_type, target_id, description, ip_address, user_agent, status)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
user_agent = request.headers.get('User-Agent', '未知')
|
||||
db_manager.execute_insert(log_sql, (
|
||||
current_user['user_id'],
|
||||
'delete_user',
|
||||
'user',
|
||||
user_id,
|
||||
f'删除用户: {existing_user[0]["username"]}',
|
||||
client_ip,
|
||||
user_agent,
|
||||
'success'
|
||||
))
|
||||
|
||||
logger.info(f"删除用户成功: {existing_user[0]['username']}, 影响行数: {affected_rows}")
|
||||
|
||||
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
|
||||
|
||||
@user_bp.route('/info', methods=['GET'])
|
||||
@require_auth
|
||||
def get_user_info():
|
||||
"""获取当前用户信息(默认不包含password,企业主可通过action=need_password获取)"""
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', '未知'))
|
||||
logger.info(f"[获取用户信息] 开始处理请求, IP: {client_ip}")
|
||||
|
||||
try:
|
||||
current_user = AuthUtils.get_current_user()
|
||||
user_id = current_user.get('user_id')
|
||||
|
||||
if not user_id:
|
||||
logger.warning(f"[获取用户信息] 无法获取用户ID, IP: {client_ip}")
|
||||
return jsonify({'code': 400, 'message': '无法获取用户ID', 'data': None}), 400
|
||||
|
||||
# 获取action参数
|
||||
action = request.args.get('action', '').strip()
|
||||
|
||||
logger.info(f"[获取用户信息] 用户ID: {user_id}, action: {action}, IP: {client_ip}")
|
||||
|
||||
db_manager = get_db_manager()
|
||||
|
||||
# 查询用户信息
|
||||
# 从ai_authors表获取xhs_cookie, xhs_phone, xhs_account, bound_at字段以优化性能
|
||||
sql = """
|
||||
SELECT id, enterprise_id, enterprise_name, username, real_name, email, phone,
|
||||
wechat_openid, wechat_unionid, password,
|
||||
is_bound_xhs, department, role, status, created_at, updated_at
|
||||
FROM ai_users u
|
||||
WHERE id = %s AND status != 'deleted'
|
||||
"""
|
||||
result = db_manager.execute_query(sql, (user_id,))
|
||||
|
||||
if not result:
|
||||
logger.warning(f"[获取用户信息] 用户不存在, ID: {user_id}, IP: {client_ip}")
|
||||
return jsonify({'code': 404, 'message': '用户不存在', 'data': None}), 404
|
||||
|
||||
user_info = result[0]
|
||||
|
||||
# 记录操作日志
|
||||
log_operation(
|
||||
action='get_user_info',
|
||||
target_type='user',
|
||||
target_id=user_id,
|
||||
description=f"查询用户信息: {user_info.get('username')}, action={action}"
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': 'success',
|
||||
'data': 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
|
||||
|
||||
@user_bp.route('/add_employees', methods=['POST'])
|
||||
@require_auth
|
||||
@require_role('enterprise')
|
||||
def add_employees():
|
||||
"""添加员工"""
|
||||
try:
|
||||
current_user = AuthUtils.get_current_user()
|
||||
enterprise_id = current_user.get('enterprise_id')
|
||||
|
||||
if not enterprise_id:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '无法获取企业ID',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '请求参数错误',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证必需字段
|
||||
required_fields = ['name', 'phone', 'password', 'role']
|
||||
for field in required_fields:
|
||||
if not data.get(field):
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': f'缺少必需字段: {field}',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 验证角色
|
||||
valid_roles = ['editor', 'reviewer', 'publisher', 'each_title_reviewer']
|
||||
if data['role'] not in valid_roles:
|
||||
return jsonify({
|
||||
'code': 400,
|
||||
'message': '无效的角色',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
# 检查手机号是否已存在
|
||||
db_manager = get_db_manager()
|
||||
check_sql = "SELECT id FROM ai_users WHERE phone = %s AND enterprise_id = %s"
|
||||
existing = db_manager.execute_query(check_sql, (data['phone'], enterprise_id))
|
||||
|
||||
if existing:
|
||||
return jsonify({
|
||||
'code': 409,
|
||||
'message': '手机号已被使用',
|
||||
'data': None
|
||||
}), 409
|
||||
|
||||
# 创建员工
|
||||
# xhs_phone, xhs_account, bound_at字段已迁移到ai_authors表,不再在ai_users表中存储
|
||||
sql = """
|
||||
INSERT INTO ai_users
|
||||
(enterprise_id, enterprise_name, username, real_name, phone, password, role, department, status, is_bound_xhs)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
hashed_password = AuthUtils.hash_password(data['password'])
|
||||
# 获取企业名称
|
||||
ent_sql = "SELECT name FROM ai_enterprises WHERE id = %s"
|
||||
ent_result = db_manager.execute_query(ent_sql, (enterprise_id,))
|
||||
enterprise_name = ent_result[0]['name'] if ent_result else ''
|
||||
|
||||
user_id = db_manager.execute_insert(sql, (
|
||||
enterprise_id,
|
||||
enterprise_name,
|
||||
data['phone'], # 使用手机号作为username
|
||||
data['name'], # real_name
|
||||
data['phone'],
|
||||
hashed_password,
|
||||
data['role'],
|
||||
data.get('department', ''),
|
||||
'active',
|
||||
0 # is_bound_xhs
|
||||
))
|
||||
|
||||
# 更新企业员工总数
|
||||
update_sql = "UPDATE ai_enterprises SET users_total = users_total + 1 WHERE id = %s"
|
||||
db_manager.execute_update(update_sql, (enterprise_id,))
|
||||
|
||||
logger.info(f"添加员工成功: {data['name']}, ID: {user_id}")
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'message': '添加成功',
|
||||
'data': {'id': user_id},
|
||||
'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
|
||||
Reference in New Issue
Block a user