from flask import request, jsonify from utils.database import get_db from utils.auth import get_user_id_from_token, build_location, get_admin_from_token from utils.helpers import build_search_conditions, error_response from utils.config import get_config_value from utils.sensitive_filter import check_sensitive_words import datetime def check_task_permission(task_id, user_id, require_owner=True): """ 检查用户是否有权限操作任务 返回 (has_permission, is_admin, error_message) """ conn = get_db() cursor = conn.cursor() try: cursor.execute('SELECT publisher_id FROM tasks WHERE id = ? AND deleted_at IS NULL', (task_id,)) task = cursor.fetchone() if not task: return False, False, '任务不存在' # 检查是否是管理员 is_admin = get_admin_from_token(None, user_id) # 如果是管理员,直接放行 if is_admin: return True, True, None # 如果需要所有者权限,检查是否是发布者 if require_owner and task['publisher_id'] != user_id: return False, False, '无权操作此任务' return True, False, None finally: conn.close() def get_bulk_publisher_rating_rates(user_ids): if not user_ids: return {} conn = get_db() cursor = conn.cursor() try: placeholders = ','.join(['?'] * len(user_ids)) cursor.execute(f''' SELECT t.publisher_id, COUNT(*) as total, SUM(CASE WHEN tr.rating = 'good' THEN 1 ELSE 0 END) as positive_count FROM task_ratings tr JOIN tasks t ON tr.task_id = t.id WHERE tr.rating_type = 'acceptor_to_publisher' AND t.publisher_id IN ({placeholders}) GROUP BY t.publisher_id ''', user_ids) rows = cursor.fetchall() rating_map = {} for row in rows: if row['total'] > 0: rate = round((row['positive_count'] / row['total']) * 100) rating_map[row['publisher_id']] = f'{rate}%' else: rating_map[row['publisher_id']] = '-' for user_id in user_ids: if user_id not in rating_map: rating_map[user_id] = '-' return rating_map finally: conn.close() def register_task_routes(app): @app.route('/api/tasks', methods=['GET']) def get_tasks(): status = request.args.get('status') type_param = request.args.get('type') search = request.args.get('search') page = int(request.args.get('page', 1)) limit = int(request.args.get('limit', 10)) offset = (page - 1) * limit conn = get_db() cursor = conn.cursor() try: query = ''' SELECT t.*, u.phone as publisher_phone FROM tasks t LEFT JOIN users u ON t.publisher_id = u.id WHERE t.deleted_at IS NULL ''' count_query = 'SELECT COUNT(*) FROM tasks t WHERE t.deleted_at IS NULL' params = [] count_params = [] if status and status != 'all': query += ' AND status = ?' count_query += ' AND status = ?' params.append(status) count_params.append(status) auth_header = request.headers.get('Authorization') if type_param and auth_header and auth_header.startswith('Bearer '): token = auth_header.split(' ')[1] current_user_id = get_user_id_from_token(token) if current_user_id: if type_param == 'published': query += ' AND publisher_id = ?' count_query += ' AND publisher_id = ?' params.append(current_user_id) count_params.append(current_user_id) elif type_param == 'accepted': query += ' AND acceptor_id = ?' count_query += ' AND acceptor_id = ?' params.append(current_user_id) count_params.append(current_user_id) query += ' AND status IN (?, ?)' count_query += ' AND status IN (?, ?)' params.extend(['in_progress', 'completed']) count_params.extend(['in_progress', 'completed']) if search: search_fields = ['t.title', 't.description', 't.province', 't.city', 't.district', 't.street'] search_cond, search_params = build_search_conditions(search_fields, search) if search_cond: query += search_cond count_query += search_cond params.extend(search_params) count_params.extend(search_params) cursor.execute(count_query, count_params) total = cursor.fetchone()[0] query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?' params.extend([limit, offset]) cursor.execute(query, params) rows = cursor.fetchall() publisher_ids = list(set(row['publisher_id'] for row in rows if row['publisher_id'])) publisher_rating_map = get_bulk_publisher_rating_rates(publisher_ids) items = [] for row in rows: task = dict(row) task['location'] = build_location(task, include_detail=True) task['publisher_rating_rate'] = publisher_rating_map.get(row['publisher_id'], '-') items.append(task) return jsonify({ 'items': items, 'total': total, 'page': page, 'limit': limit }) finally: conn.close() @app.route('/api/tasks/count', methods=['GET']) def get_task_count(): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return error_response('未登录', 401) token = auth_header.split(' ')[1] user_id = get_user_id_from_token(token) if not user_id: return error_response('登录已过期', 401) conn = get_db() cursor = conn.cursor() try: cursor.execute(''' SELECT COUNT(*) FROM tasks WHERE publisher_id = ? AND status IN ('pending', 'in_progress') AND deleted_at IS NULL ''', (user_id,)) current_count = cursor.fetchone()[0] max_publish = int(get_config_value('max_publish_tasks', '3')) return jsonify({'count': current_count, 'max': max_publish}) finally: conn.close() @app.route('/api/tasks/', methods=['GET']) def get_task(task_id): conn = get_db() cursor = conn.cursor() try: cursor.execute(''' SELECT t.*, u.nickname as user_nickname, u.phone as acceptor_phone, u.avatar as acceptor_avatar, pr.rating as publisher_rating, pr.comment as publisher_rating_comment, pr.created_at as publisher_rating_created_at, ar.rating as acceptor_rating, ar.comment as acceptor_rating_comment, ar.created_at as acceptor_rating_created_at FROM tasks t LEFT JOIN users u ON t.acceptor_id = u.id LEFT JOIN task_ratings pr ON t.id = pr.task_id AND pr.rating_type = 'publisher_to_acceptor' LEFT JOIN task_ratings ar ON t.id = ar.task_id AND ar.rating_type = 'acceptor_to_publisher' WHERE t.id = ? AND t.deleted_at IS NULL ''', (task_id,)) row = cursor.fetchone() if not row: return error_response('任务不存在') result = dict(row) result['location'] = build_location(result, include_detail=True) if result.get('user_nickname'): result['acceptor_name'] = result['user_nickname'] elif result.get('acceptor_phone'): result['acceptor_name'] = result['acceptor_phone'] return jsonify(result) finally: conn.close() def validate_task_time(task_time_str): try: task_time = datetime.datetime.strptime(task_time_str, '%Y-%m-%d %H:%M') current_time = datetime.datetime.now() return task_time > current_time except Exception: return False @app.route('/api/tasks', methods=['POST']) def create_task(): data = request.json conn = get_db() cursor = conn.cursor() try: task_time = data.get('time') if task_time: if not validate_task_time(task_time): return error_response('任务时间必须晚于当前时间') # 敏感词过滤 found, word = check_sensitive_words( data.get('title'), data.get('description'), data.get('detail_location'), data.get('reward') ) if found: return error_response('文字中存在敏感词,请修改后再发布') auth_header = request.headers.get('Authorization') publisher_id = None if auth_header and auth_header.startswith('Bearer '): token = auth_header.split(' ')[1] cursor.execute(''' SELECT u.id, u.enabled FROM users u JOIN sessions s ON u.id = s.user_id WHERE s.token = ? AND s.expires_at > datetime('now') AND u.deleted_at IS NULL ''', (token,)) user = cursor.fetchone() if user: if user['enabled'] != 1: return error_response('账户已被禁用,无法发布任务', 403) publisher_id = user['id'] cursor.execute(''' SELECT COUNT(*) FROM tasks WHERE publisher_id = ? AND status IN ('pending', 'in_progress') AND deleted_at IS NULL ''', (publisher_id,)) current_count = cursor.fetchone()[0] max_publish = int(get_config_value('max_publish_tasks', '3')) if current_count >= max_publish: return error_response(f'待处理+进行中的任务数最多为{max_publish}个,暂时无法发布') cursor.execute(''' INSERT INTO tasks (title, description, province, city, district, street, detail_location, time, reward, publisher_id, publisher_name, contact, status, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( data.get('title'), data.get('description'), data.get('province'), data.get('city'), data.get('district'), data.get('street'), data.get('detail_location'), data.get('time'), data.get('reward'), publisher_id, data.get('publisher_name'), data.get('contact'), data.get('status', 'pending'), data.get('type', 'published') )) task_id = cursor.lastrowid conn.commit() cursor.execute('SELECT * FROM tasks WHERE id = ?', (task_id,)) created_task = cursor.fetchone() result = dict(created_task) result['location'] = build_location(result, include_detail=True) return jsonify(result) except Exception as e: conn.rollback() raise e finally: conn.close() @app.route('/api/tasks/', methods=['PUT']) def update_task(task_id): # 权限验证 auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return error_response('请先登录', 401) token = auth_header.split(' ')[1] user_id = get_user_id_from_token(token) if not user_id: return error_response('登录已过期,请重新登录', 401) # 检查是否有权限更新任务(发布者或管理员) has_permission, is_admin, error_msg = check_task_permission(task_id, user_id, require_owner=True) if not has_permission: return error_response(error_msg, 403) data = request.json # 敏感词过滤 found, word = check_sensitive_words( data.get('title'), data.get('description'), data.get('detail_location'), data.get('reward') ) if found: return error_response('文字中存在敏感词,请修改后再发布') conn = get_db() cursor = conn.cursor() try: cursor.execute('SELECT * FROM tasks WHERE id = ?', (task_id,)) task = cursor.fetchone() if not task: return error_response('任务不存在') # 管理员可以更新所有字段,普通用户只能更新部分字段 update_fields = [] update_values = [] # 所有用户都可以更新的字段 if 'title' in data: update_fields.append('title = ?') update_values.append(data['title']) if 'description' in data: update_fields.append('description = ?') update_values.append(data['description']) if 'time' in data: update_fields.append('time = ?') update_values.append(data['time']) if 'reward' in data: update_fields.append('reward = ?') update_values.append(data['reward']) if 'contact' in data: update_fields.append('contact = ?') update_values.append(data['contact']) # 只有管理员可以更新地址信息 if is_admin: if 'province' in data: update_fields.append('province = ?') update_values.append(data['province']) if 'city' in data: update_fields.append('city = ?') update_values.append(data['city']) if 'district' in data: update_fields.append('district = ?') update_values.append(data['district']) if 'street' in data: update_fields.append('street = ?') update_values.append(data['street']) if 'detail_location' in data: update_fields.append('detail_location = ?') update_values.append(data['detail_location']) if 'status' in data: update_fields.append('status = ?') update_values.append(data['status']) if 'acceptor_id' in data: update_fields.append('acceptor_id = ?') update_values.append(data['acceptor_id']) # 承接任务 if data.get('type') == 'accepted': update_fields.append('acceptor_id = ?') update_values.append(user_id) update_fields.append('accept_time = datetime(\'now\')') if not update_fields: return error_response('没有需要更新的字段') update_values.append(task_id) cursor.execute(f'UPDATE tasks SET {", ".join(update_fields)} WHERE id = ?', update_values) conn.commit() if data.get('status') == 'completed' and 'rating' in data: if user_id == task['publisher_id'] and task['acceptor_id']: cursor.execute('SELECT * FROM task_ratings WHERE task_id = ? AND rating_type = ?', (task_id, 'publisher_to_acceptor')) existing_rating = cursor.fetchone() if not existing_rating: cursor.execute(''' INSERT INTO task_ratings (task_id, publisher_id, acceptor_id, rating, comment, rating_type) VALUES (?, ?, ?, ?, ?, ?) ''', (task_id, user_id, task['acceptor_id'], data.get('rating'), data.get('rating_comment', ''), 'publisher_to_acceptor')) conn.commit() cursor.execute('SELECT * FROM tasks WHERE id = ?', (task_id,)) updated_task = cursor.fetchone() if updated_task: result = dict(updated_task) result['location'] = build_location(result, include_detail=True) return jsonify(result) else: return error_response('任务不存在') except Exception as e: conn.rollback() raise e finally: conn.close() @app.route('/api/tasks/', methods=['DELETE']) def delete_task(task_id): # 权限验证 auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return error_response('请先登录', 401) token = auth_header.split(' ')[1] user_id = get_user_id_from_token(token) if not user_id: return error_response('登录已过期,请重新登录', 401) # 检查是否有权限删除任务(发布者或管理员) has_permission, is_admin, error_msg = check_task_permission(task_id, user_id, require_owner=True) if not has_permission: return error_response(error_msg, 403) conn = get_db() cursor = conn.cursor() try: cursor.execute('UPDATE tasks SET deleted_at = datetime(\'now\') WHERE id = ?', (task_id,)) conn.commit() return jsonify({'success': True, 'message': '删除成功'}) except Exception as e: conn.rollback() raise e finally: conn.close()