#!/usr/bin/env python # -*- coding: utf-8 -*- """作者管理接口""" from flask import Blueprint, request, jsonify, g import logging from datetime import datetime from auth_utils import require_auth, require_role, AuthUtils from database_config import get_db_manager # 导入统一日志配置 from log_config import setup_article_server_logger # 使用统一的日志记录器 logger = logging.getLogger('article_server') # 创建蓝图 author_bp = Blueprint('author', __name__, url_prefix='/api/authors') @author_bp.route('', methods=['GET']) @require_auth def get_authors(): """获取作者列表(分页)""" try: page = int(request.args.get('page', 1)) size = int(request.args.get('size', 10)) offset = (page - 1) * size # 搜索参数 search = request.args.get('search', '').strip() department = request.args.get('department', '').strip() status = request.args.get('status', '').strip() channel = request.args.get('channel', '').strip() db_manager = get_db_manager() # 构建查询条件 where_conditions = [] params = [] if search: where_conditions.append("(author_name LIKE %s OR hospital LIKE %s OR title LIKE %s OR id = %s)") search_param = f"%{search}%" params.extend([search_param, search_param, search_param, search]) if department: where_conditions.append("department = %s") params.append(department) if status: where_conditions.append("status = %s") params.append(status) if channel: where_conditions.append("channel = %s") params.append(channel) where_clause = "" if where_conditions: where_clause = "WHERE " + " AND ".join(where_conditions) # 查询科室总数(用于分页) dept_count_sql = f"SELECT COUNT(DISTINCT department_id) as total FROM ai_authors {where_clause}" dept_count_result = db_manager.execute_query(dept_count_sql, params) total_departments = dept_count_result[0]['total'] # 先获取分页的科室ID列表 dept_sql = f"""SELECT DISTINCT department_id, department_name FROM ai_authors {where_clause} ORDER BY department_id ASC LIMIT %s OFFSET %s""" dept_params = params + [size, offset] departments = db_manager.execute_query(dept_sql, dept_params) # 如果没有科室数据,直接返回空结果 if not departments: department_list = [] else: # 获取这些科室的所有作者 dept_ids = [str(dept['department_id']) for dept in departments] dept_id_placeholders = ','.join(['%s'] * len(dept_ids)) # 构建包含科室ID过滤的查询条件 author_where_conditions = where_conditions.copy() author_where_conditions.append(f"department_id IN ({dept_id_placeholders})") author_where_clause = "WHERE " + " AND ".join(author_where_conditions) author_sql = f"""SELECT id as author_id, author_name, department_id, department_name, toutiao_cookie, toutiao_images_cookie FROM ai_authors {author_where_clause} ORDER BY department_id ASC, id ASC""" author_params = params + dept_ids authors = db_manager.execute_query(author_sql, author_params) # 按科室分组 department_dict = {} for dept in departments: dept_id = dept['department_id'] department_dict[dept_id] = { 'department_id': dept_id, 'department_name': dept['department_name'], 'department_list': [] } # 将作者分配到对应科室 for author in authors: dept_id = author['department_id'] if dept_id in department_dict: department_dict[dept_id]['department_list'].append(author) # 转换为数组格式,保持科室顺序 department_list = [department_dict[dept['department_id']] for dept in departments] return jsonify({ 'code': 200, 'data': { 'list': department_list, 'page': page, 'size': size, 'total': total_departments }, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"获取作者列表失败: {e}") return jsonify({'code': 500, 'message': f'获取失败: {str(e)}'}), 500 @author_bp.route('/list', methods=['GET']) @require_auth def get_list_authors(): """获取作者列表(分页,基于用户关联关系)""" try: page = int(request.args.get('page', 1)) size = int(request.args.get('size', 10)) offset = (page - 1) * size # 搜索参数 search = request.args.get('search', '').strip() department = request.args.get('department', '').strip() status = request.args.get('status', '').strip() channel = request.args.get('channel', '').strip() db_manager = get_db_manager() # 获取当前登录用户 current_user = AuthUtils.get_current_user() logger.info(f"[get_authors] 获取到的用户信息: {current_user}") if not current_user: logger.error("[get_authors] 未获取到用户信息") return jsonify({'code': 401, 'message': '未登录或用户信息缺失'}), 401 user_id = current_user.get('user_id') logger.info(f"[get_authors] 提取的user_id: {user_id}") if not user_id: logger.error(f"[get_authors] user_id缺失,current_user内容: {current_user}") return jsonify({'code': 401, 'message': '用户ID缺失'}), 401 # Step 1: 先查出当前用户关联的作者ID user_authors_sql = "SELECT author_id FROM ai_user_authors WHERE user_id = %s" logger.info(f"[get_authors] 执行查询: {user_authors_sql}, 参数: {[user_id]}") user_authors = db_manager.execute_query(user_authors_sql, [user_id]) logger.info(f"[get_authors] 查询结果: {user_authors}") author_ids = [str(row['author_id']) for row in user_authors] logger.info(f"[get_authors] 提取的author_ids: {author_ids}") if not author_ids: # 用户没有绑定任何作者 logger.info(f"[get_authors] 用户{user_id}没有绑定任何作者") return jsonify({ 'code': 200, 'data': { 'list': [], 'page': page, 'size': size, 'total': 0 }, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) # Step 2: 构建查询条件(限定 author_id 范围) where_conditions = ["id IN (" + ",".join(["%s"] * len(author_ids)) + ")"] params = author_ids if search: where_conditions.append("(author_name LIKE %s OR hospital LIKE %s OR title LIKE %s)") search_param = f"%{search}%" params.extend([search_param, search_param, search_param]) if department: where_conditions.append("department = %s") params.append(department) if status: where_conditions.append("status = %s") params.append(status) if channel: where_conditions.append("channel = %s") params.append(channel) where_clause = "WHERE " + " AND ".join(where_conditions) # Step 3: 查询科室总数(用于分页) dept_count_sql = f"SELECT COUNT(DISTINCT department_id) as total FROM ai_authors {where_clause}" dept_count_result = db_manager.execute_query(dept_count_sql, params) total_departments = dept_count_result[0]['total'] if dept_count_result else 0 # Step 4: 获取分页的科室ID列表 dept_sql = f"""SELECT DISTINCT department_id, department_name FROM ai_authors {where_clause} ORDER BY department_id ASC LIMIT %s OFFSET %s""" dept_params = params + [size, offset] departments = db_manager.execute_query(dept_sql, dept_params) if not departments: department_list = [] else: # Step 5: 获取这些科室下的所有作者 dept_ids = [str(dept['department_id']) for dept in departments] dept_id_placeholders = ','.join(['%s'] * len(dept_ids)) author_where_conditions = where_conditions.copy() author_where_conditions.append(f"department_id IN ({dept_id_placeholders})") author_where_clause = "WHERE " + " AND ".join(author_where_conditions) author_sql = f"""SELECT id as author_id, author_name, department_id, department_name FROM ai_authors {author_where_clause} ORDER BY department_id ASC, id ASC""" author_params = params + dept_ids authors = db_manager.execute_query(author_sql, author_params) # Step 6: 按科室分组 department_dict = {} for dept in departments: dept_id = dept['department_id'] department_dict[dept_id] = { 'department_id': dept_id, 'department_name': dept['department_name'], 'department_list': [] } for author in authors: dept_id = author['department_id'] if dept_id in department_dict: department_dict[dept_id]['department_list'].append(author) department_list = [department_dict[dept['department_id']] for dept in departments] return jsonify({ 'code': 200, 'data': { 'list': department_list, 'page': page, 'size': size, 'total': total_departments }, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: import traceback logger.error(f"获取作者列表失败: {e}") logger.error(f"错误堆栈: {traceback.format_exc()}") return jsonify({'code': 500, 'message': f'获取失败: {str(e)}'}), 500 @author_bp.route('/', methods=['GET']) @require_auth def get_author(author_id): """获取单个作者详情""" try: db_manager = get_db_manager() sql = """SELECT id, author_name, department, department_name, title, hospital, specialty, toutiao_images_cookie, introduction, avatar_url, status, toutiao_cookie, created_at, updated_at FROM ai_authors WHERE id = %s""" author = db_manager.execute_query(sql, (author_id,)) if not author: return jsonify({'code': 404, 'message': '作者不存在'}), 404 return jsonify({ 'code': 200, 'data': author[0], 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"获取作者详情失败: {e}") return jsonify({'code': 500, 'message': f'获取失败: {str(e)}'}), 500 @author_bp.route('', methods=['POST']) @require_auth @require_role(['admin', 'editor']) def create_author(): """创建新作者""" try: data = request.get_json() logger.info(f"[创建作者] 接收到的数据: {data}") # 必填字段验证 author_name = data.get('author_name', '').strip() app_id = data.get('app_id', '').strip() app_token = data.get('app_token', '').strip() department_name = data.get('department_name', '').strip() toutiao_cookie = data.get('toutiao_cookie', '').strip() toutiao_images_cookie = data.get('toutiao_images_cookie', '').strip() channel = data.get('channel', 1) logger.info(f"[创建作者] 字段值 - author_name: '{author_name}', app_id: '{app_id}', app_token: '{app_token}', department_name: '{department_name}'") if not author_name: logger.warning(f"[创建作者] 作者姓名为空") return jsonify({'code': 400, 'message': '作者姓名不能为空'}), 400 if not app_id: logger.warning(f"[创建作者] app_id为空") return jsonify({'code': 400, 'message': 'app_id不能为空'}), 400 if not app_token: logger.warning(f"[创建作者] app_token为空") return jsonify({'code': 400, 'message': 'app_token不能为空'}), 400 if not department_name: logger.warning(f"[创建作者] department_name为空") return jsonify({'code': 400, 'message': '科室不能为空'}), 400 if channel == 2 and not toutiao_cookie: logger.warning(f"[创建作者] toutiao_cookie为空") return jsonify({'code': 400, 'message': 'toutiao_cookie不能为空'}), 400 if channel == 2 and not toutiao_images_cookie: logger.warning(f"[创建作者] toutiao_images_cookie为空") return jsonify({'code': 400, 'message': 'toutiao_images_cookie不能为空'}), 400 # 兼容旧字段 department = data.get('department', department_name).strip() # 可选字段 title = data.get('title', '').strip() hospital = data.get('hospital', '').strip() specialty = data.get('specialty', '').strip() introduction = data.get('introduction', '').strip() avatar_url = data.get('avatar_url', '').strip() status = data.get('status', 'active') # 状态验证 if status not in ['active', 'inactive']: return jsonify({'code': 400, 'message': '状态值无效'}), 400 db_manager = get_db_manager() # (1) 判断author_name唯一,否则报错 check_author_name_sql = "SELECT id FROM ai_authors WHERE author_name = %s" existing_author_name = db_manager.execute_query(check_author_name_sql, (author_name,)) if existing_author_name: logger.warning(f"[创建作者] 作者姓名已存在: {author_name}") return jsonify({'code': 400, 'message': f'作者姓名 "{author_name}" 已存在'}), 400 # (2) 判断app_id唯一,否则报错 check_app_id_sql = "SELECT id FROM ai_authors WHERE app_id = %s" existing_app_id = db_manager.execute_query(check_app_id_sql, (app_id,)) if existing_app_id: logger.warning(f"[创建作者] app_id已存在: {app_id}") return jsonify({'code': 400, 'message': f'app_id "{app_id}" 已存在'}), 400 # (3) 判断app_token唯一,否则报错 check_app_token_sql = "SELECT id FROM ai_authors WHERE app_token = %s" existing_app_token = db_manager.execute_query(check_app_token_sql, (app_token,)) if existing_app_token: logger.warning(f"[创建作者] app_token已存在: {app_token}") return jsonify({'code': 400, 'message': f'app_token "{app_token}" 已存在'}), 400 # (4) department_name处理逻辑 # 查询ai_departments表中是否存在该科室 check_dept_sql = "SELECT id FROM ai_departments WHERE department_name = %s" existing_dept = db_manager.execute_query(check_dept_sql, (department_name,)) if existing_dept: # 科室存在,获取department_id department_id = existing_dept[0]['id'] logger.info(f"[创建作者] 科室已存在,使用现有ID: {department_id}") else: # 科室不存在,插入新科室 insert_dept_sql = "INSERT INTO ai_departments (department_name) VALUES (%s)" department_id = db_manager.execute_insert(insert_dept_sql, (department_name,)) logger.info(f"[创建作者] 创建新科室,ID: {department_id}") # 创建作者 insert_sql = """INSERT INTO ai_authors (author_name, app_id, app_token, department_name, department, department_id, title, hospital, specialty, toutiao_cookie, introduction, avatar_url, status, channel, toutiao_images_cookie) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""" author_id = db_manager.execute_insert(insert_sql, (author_name, app_id, app_token, department_name, department, department_id, title, hospital, specialty, toutiao_cookie, introduction, avatar_url, status, channel, toutiao_images_cookie)) # 查询新作者 select_sql = """SELECT id, author_name, app_id, app_token, department_name, department, department_id, title, hospital, specialty, toutiao_cookie, introduction, avatar_url, status, created_at, updated_at, channel FROM ai_authors WHERE id = %s""" new_author = db_manager.execute_query(select_sql, (author_id,)) logger.info(f"[创建作者] 成功创建作者,ID: {author_id}, 科室ID: {department_id}") return jsonify({ 'code': 200, 'data': new_author[0], 'message': '创建成功', 'timestamp': int(datetime.now().timestamp() * 1000) }), 201 except Exception as e: logger.error(f"创建作者失败: {e}") return jsonify({'code': 500, 'message': f'创建失败: {str(e)}'}), 500 @author_bp.route('/', methods=['PUT']) @require_auth @require_role(['admin', 'editor']) def update_author(author_id): """更新作者信息""" try: data = request.get_json() # 必填字段验证 author_name = data.get('author_name', '').strip() department_name = data.get('department_name', '').strip() if not author_name: return jsonify({'code': 400, 'message': '作者姓名不能为空'}), 400 if not department_name: return jsonify({'code': 400, 'message': '科室不能为空'}), 400 # 可选字段 title = data.get('title', '').strip() hospital = data.get('hospital', '').strip() specialty = data.get('specialty', '').strip() introduction = data.get('introduction', '').strip() avatar_url = data.get('avatar_url', '').strip() status = data.get('status', 'active') toutiao_cookie = data.get('toutiao_cookie', '') toutiao_images_cookie = data.get('toutiao_images_cookie', '') # 状态验证 if status not in ['active', 'inactive']: return jsonify({'code': 400, 'message': '状态值无效'}), 400 db_manager = get_db_manager() # 检查存在 check_sql = "SELECT id FROM ai_authors WHERE id = %s" existing = db_manager.execute_query(check_sql, (author_id,)) if not existing: return jsonify({'code': 404, 'message': '作者不存在'}), 404 # 检查重复(同科室同姓名,排除自己) duplicate_sql = "SELECT id FROM ai_authors WHERE author_name = %s AND department_name = %s AND id != %s" duplicate = db_manager.execute_query(duplicate_sql, (author_name, department_name, author_id)) if duplicate: return jsonify({'code': 400, 'message': '该科室已存在同名作者'}), 400 # (4) department_name处理逻辑 # 查询ai_departments表中是否存在该科室 check_dept_sql = "SELECT id FROM ai_departments WHERE department_name = %s" existing_dept = db_manager.execute_query(check_dept_sql, (department_name,)) if existing_dept: # 科室存在,获取department_id department_id = existing_dept[0]['id'] logger.info(f"[更新作者] 科室已存在,使用现有ID: {department_id}") else: # 科室不存在,插入新科室 insert_dept_sql = "INSERT INTO ai_departments (department_name) VALUES (%s)" department_id = db_manager.execute_insert(insert_dept_sql, (department_name,)) logger.info(f"[更新作者] 创建新科室,ID: {department_id}") # 更新作者 update_sql = """UPDATE ai_authors SET author_name = %s, department_name = %s, department_id = %s, title = %s, hospital = %s, specialty = %s, introduction = %s, avatar_url = %s, status = %s, toutiao_cookie = %s, toutiao_images_cookie = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s""" db_manager.execute_update(update_sql, (author_name, department_name, department_id, title, hospital, specialty, introduction, avatar_url, status, toutiao_cookie, toutiao_images_cookie, author_id)) # 查询更新后作者 select_sql = """SELECT id, author_name, department_name, title, hospital, specialty, introduction, toutiao_cookie, toutiao_images_cookie, avatar_url, status, created_at, updated_at FROM ai_authors WHERE id = %s""" updated_author = db_manager.execute_query(select_sql, (author_id,)) return jsonify({ 'code': 200, 'data': updated_author[0], 'message': '更新成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"更新作者失败: {e}") return jsonify({'code': 500, 'message': f'更新失败: {str(e)}'}), 500 @author_bp.route('/', methods=['DELETE']) @require_auth @require_role(['admin']) def delete_author(author_id): """删除作者""" try: db_manager = get_db_manager() # 检查存在 check_sql = "SELECT id, author_name FROM ai_authors WHERE id = %s" existing = db_manager.execute_query(check_sql, (author_id,)) if not existing: return jsonify({'code': 404, 'message': '作者不存在'}), 404 author_name = existing[0]['author_name'] # 检查引用(检查是否有文章引用该作者) article_check_sql = "SELECT COUNT(*) as count FROM ai_articles WHERE author_name = %s" article_count = db_manager.execute_query(article_check_sql, (author_name,)) if article_count[0]['count'] > 0: return jsonify({'code': 400, 'message': f'作者被文章引用,无法删除'}), 400 # 删除作者 delete_sql = "DELETE FROM ai_authors WHERE id = %s" db_manager.execute_update(delete_sql, (author_id,)) return jsonify({ 'code': 200, 'message': '删除成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"删除作者失败: {e}") return jsonify({'code': 500, 'message': f'删除失败: {str(e)}'}), 500 @author_bp.route('/departments', methods=['GET']) @require_auth def get_author_departments(): """获取所有作者科室列表""" try: db_manager = get_db_manager() sql = "SELECT DISTINCT department FROM ai_authors WHERE department IS NOT NULL AND department != '' ORDER BY department" departments = db_manager.execute_query(sql) department_list = [dept['department'] for dept in departments] return jsonify({ 'code': 200, 'data': department_list, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"获取科室列表失败: {e}") return jsonify({'code': 500, 'message': f'获取失败: {str(e)}'}), 500 @author_bp.route('/search', methods=['GET']) @require_auth def search_authors(): """搜索作者(用于下拉选择等)""" try: keyword = request.args.get('keyword', '').strip() limit = int(request.args.get('limit', 20)) if not keyword: return jsonify({ 'code': 200, 'data': [], 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) db_manager = get_db_manager() sql = """SELECT id, author_name, department, title, hospital FROM ai_authors WHERE status = 'active' AND (author_name LIKE %s OR department LIKE %s) ORDER BY author_name LIMIT %s""" search_param = f"%{keyword}%" authors = db_manager.execute_query(sql, (search_param, search_param, limit)) return jsonify({ 'code': 200, 'data': authors, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: logger.error(f"搜索作者失败: {e}") return jsonify({'code': 500, 'message': f'搜索失败: {str(e)}'}), 500 @author_bp.route('/detail_list', methods=['GET']) @require_auth def get_detail_list_authors(): """获取作者详细列表(分页,直接查询ai_authors表,不返回app_token字段)""" try: page = int(request.args.get('page', 1)) size = int(request.args.get('size', 10)) offset = (page - 1) * size # 搜索参数 search = request.args.get('search', '').strip() department = request.args.get('department', '').strip() status = request.args.get('status', '').strip() channel = request.args.get('channel', '') db_manager = get_db_manager() # 获取当前登录用户(仅用于权限验证) current_user = AuthUtils.get_current_user() logger.info(f"[get_detail_list_authors] 获取到的用户信息: {current_user}") if not current_user: logger.error("[get_detail_list_authors] 未获取到用户信息") return jsonify({'code': 401, 'message': '未登录或用户信息缺失'}), 401 # 构建查询条件 where_conditions = [] params = [] if search: where_conditions.append("(author_name LIKE %s OR hospital LIKE %s OR title LIKE %s OR department_name LIKE %s)") search_param = f"%{search}%" params.extend([search_param, search_param, search_param, search_param]) if department: where_conditions.append("department = %s") params.append(department) if status: where_conditions.append("status = %s") params.append(status) if channel: where_conditions.append("channel = %s") params.append(channel) # 构建WHERE子句 where_clause = "" if where_conditions: where_clause = "WHERE " + " AND ".join(where_conditions) # 查询作者总数(用于分页) count_sql = f"SELECT COUNT(*) as total FROM ai_authors {where_clause}" logger.info(f"[get_detail_list_authors] 执行计数查询: {count_sql}, 参数: {params}") count_result = db_manager.execute_query(count_sql, params) total_authors = count_result[0]['total'] if count_result else 0 logger.info(f"[get_detail_list_authors] 查询到的总数: {total_authors}") # 查询作者详细信息,排除app_token字段 author_sql = f"""SELECT id as author_id, author_name, app_id, department_id, department_name, department, toutiao_cookie, title, hospital, specialty, introduction, avatar_url, status, created_at, updated_at FROM ai_authors {where_clause} ORDER BY id DESC LIMIT %s OFFSET %s""" author_params = params + [size, offset] logger.info(f"[get_detail_list_authors] 执行作者查询: {author_sql}, 参数: {author_params}") authors = db_manager.execute_query(author_sql, author_params) logger.info(f"[get_detail_list_authors] 查询到的作者数量: {len(authors)}") # 处理时间戳格式 for author in authors: # 确保时间戳格式正确 if author.get('created_at'): author['created_at'] = int(author['created_at'].timestamp() * 1000) if author.get('updated_at'): author['updated_at'] = int(author['updated_at'].timestamp() * 1000) return jsonify({ 'code': 200, 'data': { 'list': authors, 'page': page, 'size': size, 'total': total_authors }, 'message': '获取成功', 'timestamp': int(datetime.now().timestamp() * 1000) }) except Exception as e: import traceback logger.error(f"获取作者详细列表失败: {e}") logger.error(f"错误堆栈: {traceback.format_exc()}") return jsonify({'code': 500, 'message': f'获取失败: {str(e)}'}), 500