更新数据截止日2026.01.08

This commit is contained in:
lychang
2026-01-08 01:06:27 +08:00
commit ce02565b09
37 changed files with 12587 additions and 0 deletions

49
api/__init__.py Normal file
View File

@@ -0,0 +1,49 @@
from flask import Flask, request, render_template, Blueprint
from .v1 import v1
from .v2 import v2
from .v3 import v3
app = Flask(__name__)
app.register_blueprint(v1, url_prefix='/v1')
app.register_blueprint(v2, url_prefix='/v2')
app.register_blueprint(v3, url_prefix='/v3')
app.template_folder = '../templates'
app.static_folder = '../static'
# 页面路由
@app.route('/')
@app.route('/v1/')
@app.route('/v2/')
def index():
"""主页面 - 聊天界面"""
return render_template('index.html')
@app.route('/qa')
@app.route('/v1/qa')
@app.route('/v2/qa')
def qa_page():
"""问答管理页面"""
return render_template('qa.html')
@app.route('/weekly_report')
@app.route('/v1/weekly_report')
@app.route('/v2/weekly_report')
def weekly_report_page():
"""周报页面"""
return render_template('weekly_report.html')
@app.route('/daily_report')
@app.route('/v1/daily_report')
@app.route('/v2/daily_report')
def daily_report_page():
"""日报页面"""
return render_template('daily_report.html')
@app.route('/markdown')
def markdown_page():
"""markdown页面"""
return render_template('markdown.html')

40
api/db.py Normal file
View File

@@ -0,0 +1,40 @@
from . import app, api_response
import time
from lib import chat_session, db_manager
@app.route('/api/db/datetime')
def db_status():
return api_response(success=True, data={'message': chat_session.status()})
@app.route('/api/db/update')
def db_update():
current_time = time.localtime(time.time() - 24 * 60 * 60)
day = current_time.tm_mday
month = current_time.tm_mon
year = current_time.tm_year
if chat_session.status() == f"{year}-{month:02d}-{day:02d}-23":
return api_response(success=False, error="数据库已更新", status_code=201)
for hour in range(24):
chat_session.process_chat_session(year, month, day, hour)
start = f"{year}-{month:02d}-{day:02d} 00:00:00"
end = f"{year}-{month:02d}-{day:02d} 23:59:59"
chat_session.update_qa_data(start, end)
db_manager.upload_db()
return api_response(success=True, data={'message': '数据库更新成功'})
@app.route('/api/db/upload')
def db_upload():
db_manager.upload_db()
return api_response(success=True, data={'message': '数据库上传成功'})
@app.route('/api/db/download')
def db_download():
db_manager.download_db()
return api_response(success=True, data={'message': '数据库下载成功'})

645
api/lib.py Normal file
View File

@@ -0,0 +1,645 @@
import json
from chat_session import ChatSessionManager
from file import ObjectFileManager
from tools import from_string, from_datetime, format_timestamp, DatabaseManager
from flask import jsonify
class MessageRender:
"""消息处理类"""
@staticmethod
def format_timestamp(timestamp_str):
"""格式化时间戳"""
if not timestamp_str:
return '未知时间'
try:
timestamp = int(timestamp_str[:-3])
return format_timestamp(timestamp)
except ValueError:
return '未知时间'
@staticmethod
def process_message_content(message_data):
"""处理消息内容"""
content = message_data['content']
msg_type = message_data['type']
if msg_type == 'text':
message_data['display_content'] = content
elif msg_type in ['image', 'file', 'video', 'voice']:
message_data['display_content'] = f'[{msg_type}] {content}'
elif msg_type == 'mixed':
try:
mixed_content = json.loads(content)
display_parts = []
for item in mixed_content:
msg_type, item_content = item
if msg_type == 'text':
display_parts.append(item_content)
else:
display_parts.append(f'[{msg_type}]')
message_data['display_content'] = ' '.join(display_parts)
except ValueError:
message_data['display_content'] = '[混合消息]'
elif msg_type == 'chatrecord':
try:
chatrecord_data = json.loads(content)
if 'chatrecord' in chatrecord_data and 'title' in chatrecord_data['chatrecord']:
message_data['display_content'] = f'[聊天记录] {chatrecord_data["chatrecord"]["title"]}'
message_data['chatrecord_data'] = chatrecord_data
else:
message_data['display_content'] = '[聊天记录]'
except ValueError:
message_data['display_content'] = '[聊天记录]'
else:
message_data['display_content'] = f'[{msg_type}] {content}'
return message_data
class ReportService:
"""报告服务类"""
def __init__(self, db: DatabaseManager):
self.db = db
self.execute_query = self.db.execute_query
def get_message_count_by_time_range(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的消息数量"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM message
WHERE time >= ?
AND time <= ?
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_active_users_by_time_range(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的活跃用户数量"""
result = self.execute_query('''
SELECT COUNT(DISTINCT sender) as count
FROM message
WHERE time >= ?
AND time <= ?
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_active_sub_users_by_time_range(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的活跃用户数量"""
result = self.execute_query('''
SELECT COUNT(DISTINCT sender) as count
FROM message
WHERE time >= ?
AND time <= ?
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_qa_count_by_time_range(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的QA记录数量"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM qa
WHERE start_time >= ?
AND end_time <= ?
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_non_answer_qa_count_by_time_range(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的QA记录数量"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM qa
WHERE start_time >= ?
AND end_time <= ?
AND answer = '[]'
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_qa_count_by_time_range_sub(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的QA记录数量"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM qa as q
inner join subscribe as s on q.groupId = s.id
WHERE start_time >= ?
AND end_time <= ?
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_non_answer_qa_count_by_time_range_sub(self, start_timestamp, end_timestamp):
"""获取指定时间范围内的QA记录数量"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM qa as q
inner join subscribe as s on q.groupId = s.id
WHERE start_time >= ?
AND end_time <= ?
AND answer = '[]'
''', (f"{start_timestamp}000", f"{end_timestamp}999"), fetch_all=False)
return result["count"]
def get_group_activity_data(self, start_timestamp, end_timestamp):
"""获取群组活跃度数据"""
return self.execute_query('''
SELECT g.id, g.name, COUNT(m.id) as message_count
FROM "group" g
LEFT JOIN message m ON g.id = m.receiver AND m.time >= ? AND m.time <= ?
GROUP BY g.id, g.name
ORDER BY message_count DESC
''', (f"{start_timestamp}000", f"{end_timestamp}999"))
def get_subscribe_activity_data(self, start_timestamp, end_timestamp):
"""获取订阅活跃度数据"""
return self.execute_query('''
SELECT s.id, s.name, COUNT(m.id) as message_count
FROM subscribe s
LEFT JOIN message m ON s.id = m.receiver AND m.time >= ? AND m.time <= ?
GROUP BY s.id, s.name
ORDER BY message_count DESC
''', (f"{start_timestamp}000", f"{end_timestamp}999"))
def get_recent_messages_by_time_range(self, start_timestamp, end_timestamp, limit=100):
"""获取指定时间范围内的最近消息"""
return self.execute_query('''
SELECT content
FROM message
WHERE time >= ?
AND time <= ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY time
LIMIT ?
''', (f"{start_timestamp}000", f"{end_timestamp}999", limit))
def get_hourly_activity_data(self, start_timestamp, end_timestamp):
"""获取按小时统计的消息量"""
return self.execute_query('''
SELECT strftime('%H',
datetime(substr(time, 0, 11), 'unixepoch', '+8 hours')) as hour,
COUNT(*) as count
FROM message
WHERE time >= ?
AND time <= ?
GROUP BY hour
ORDER BY hour
''', (f"{start_timestamp}000", f"{end_timestamp}999"))
def get_group_count(self):
"""获取群组总数"""
result = self.execute_query('SELECT COUNT(*) as count FROM "group"', fetch_all=False)
return result["count"]
def get_member_count(self):
"""获取成员总数"""
result = self.execute_query('SELECT COUNT(*) as count FROM user', fetch_all=False)
return result["count"]
def get_sub_member_count(self):
"""获取成员总数"""
result = self.execute_query(
'SELECT COUNT(distinct m.userId) as count FROM member as m inner join subscribe as s on m.groupId = s.id ',
fetch_all=False)
return result["count"]
def get_subscribe_count(self):
"""获取订阅总数"""
result = self.execute_query('SELECT COUNT(*) as count FROM subscribe', fetch_all=False)
return result["count"]
def get_qa_count(self):
"""获取指定时间范围内的QA记录总数"""
result = self.execute_query('''
SELECT COUNT(*) as count
FROM qa
''', fetch_all=False)
return result["count"]
def get_weekly_messages(self, start_timestamp, end_timestamp, limit=200):
"""获取本周消息用于分类分析"""
return self.execute_query('''
SELECT content
FROM message
WHERE time >= ?
AND time <= ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY time
LIMIT ?
''', (f"{start_timestamp}000", f"{end_timestamp}999", limit))
# 通用数据查询方法 (整合剩余数据库查询)
def get_messages_by_ids(self, message_ids):
"""根据消息ID列表获取消息内容"""
if not message_ids:
return []
placeholders = ','.join(['?'] * len(message_ids))
query = f"""
SELECT m.id, m.content, m.type, m.sender, m.receiver,u.name as sender_name,
datetime(substr(time,0,11), 'unixepoch','+8 hours') as formatted_time
FROM message m
left join user u on m.sender = u.id
WHERE m.id IN ({placeholders})
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time
"""
return self.execute_query(query, message_ids)
def get_messages(self, limit=100, offset=0, group_id=None):
"""获取消息列表"""
query = '''
SELECT m.*,
u.name as sender_name,
g.name as group_name,
datetime(substr(time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
LEFT JOIN "group" g ON m.receiver = g.id
WHERE m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
AND m.toType = 1
'''
params = []
if group_id:
query += ' AND m.receiver = ?'
params.append(group_id)
query += ' ORDER BY formatted_time DESC LIMIT ? OFFSET ?'
params.extend([limit, offset])
return self.execute_query(query, params)
def get_all_users(self):
"""获取所有用户"""
return self.execute_query('SELECT * FROM user ORDER BY name')
def get_all_groups(self):
"""获取所有群组"""
return self.execute_query('SELECT * FROM "group" ORDER BY id')
def get_all_subscribes(self):
"""获取所有订阅"""
return self.execute_query('SELECT * FROM subscribe ORDER BY id')
def get_subscribe_messages(self, subscribe_id, limit=100, offset=0):
"""获取指定订阅的消息"""
if not subscribe_id:
query = '''
SELECT m.*,
u.name as sender_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
WHERE m.toType = 1
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT ? OFFSET ? \
'''
return self.execute_query(query, (limit, offset))
else:
query = '''
SELECT m.*,
u.name as sender_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
WHERE m.receiver = ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT ? OFFSET ? \
'''
return self.execute_query(query, (subscribe_id, limit, offset))
def get_subscribe_message_count(self, subscribe_id):
"""获取指定订阅的消息统计"""
subscribe_messages_total = self.execute_query('''
SELECT COUNT(*) as count
FROM message
WHERE receiver = ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
''', (subscribe_id,), fetch_all=False)['count']
lastest_time = self.execute_query('''
SELECT datetime(substr(time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message
WHERE receiver = ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT 1
''', (subscribe_id,), fetch_all=False)
lastest_time = lastest_time['formatted_time'] if lastest_time else ""
return {'total': subscribe_messages_total, 'lastest_time': lastest_time}
def get_group_messages(self, group_id, limit=100, offset=0):
"""获取指定群组的消息"""
if not group_id:
query = '''
SELECT m.*,
u.name as sender_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
WHERE m.toType = 1
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT ? OFFSET ? \
'''
return self.execute_query(query, (limit, offset))
else:
query = '''
SELECT m.*,
u.name as sender_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
WHERE m.receiver = ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT ? OFFSET ? \
'''
return self.execute_query(query, (group_id, limit, offset))
def get_group_message_count(self, group_id):
"""获取指定群组的消息"""
group_messages_total = self.execute_query('''
SELECT COUNT(*) as count
FROM message
WHERE receiver = ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
''', (group_id,), fetch_all=False)['count']
lastest_time = self.execute_query('''
SELECT datetime(substr(time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message
WHERE receiver = ?
AND type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time DESC
LIMIT 1
''', (group_id,), fetch_all=False)
lastest_time = lastest_time['formatted_time'] if lastest_time else ""
return {'total': group_messages_total, 'lastest_time': lastest_time}
def get_statistics(self):
"""获取统计数据"""
user_count = self.execute_query('SELECT COUNT(*) as count FROM user', fetch_all=False)['count']
group_count = self.execute_query('SELECT COUNT(*) as count FROM "group"', fetch_all=False)['count']
message_count = self.execute_query('SELECT COUNT(*) as count FROM message', fetch_all=False)['count']
file_count = self.execute_query('SELECT COUNT(*) as count FROM file', fetch_all=False)['count']
latest_time_result = self.execute_query('SELECT MAX(time) as latest_time FROM message', fetch_all=False)
latest_time = latest_time_result['latest_time'] if latest_time_result else None
return {
'user_count': user_count,
'group_count': group_count,
'message_count': message_count,
'file_count': file_count,
'latest_time': latest_time
}
def get_v2_statistics(self):
"""获取v2版本统计数据(包含订阅信息)"""
user_count = self.execute_query(
'SELECT COUNT(*) as count FROM user as u inner join member as m on u.id = m.userId inner join subscribe as s on m.groupId = s.id',
fetch_all=False)['count']
subscribe_count = self.execute_query('SELECT COUNT(*) as count FROM subscribe', fetch_all=False)['count']
message_count = \
self.execute_query(
'SELECT COUNT(*) as count FROM message as m inner join subscribe as s on m.receiver = s.id',
fetch_all=False)['count']
file_count = self.execute_query(
'SELECT COUNT(*) as count FROM file as f inner join message as m on f.id = m.content inner join subscribe as s on m.receiver = s.id',
fetch_all=False)['count']
latest_time_result = self.execute_query('SELECT MAX(time) as latest_time FROM message', fetch_all=False)
latest_time = latest_time_result['latest_time'] if latest_time_result else None
return {
'user_count': user_count,
'group_count': subscribe_count,
'message_count': message_count,
'file_count': file_count,
'latest_time': latest_time
}
def search_messages(self, keyword, limit=100, group_id=None):
"""搜索消息"""
if group_id:
query = '''
SELECT m.*,
u.name as sender_name,
g.name as group_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
LEFT JOIN "group" g ON m.receiver = g.id
WHERE m.content LIKE ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
AND m.receiver = ?
ORDER BY formatted_time
LIMIT ? \
'''
return self.execute_query(query, (f'%{keyword}%', group_id, limit))
else:
query = '''
SELECT m.*,
u.name as sender_name,
g.name as group_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
LEFT JOIN "group" g ON m.receiver = g.id
WHERE m.content LIKE ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time
LIMIT ? \
'''
return self.execute_query(query, (f'%{keyword}%', limit))
def search_subscribe_messages(self, keyword, limit=100, subscribe_id=None):
"""搜索订阅消息"""
if subscribe_id:
query = '''
SELECT m.*,
u.name as sender_name,
s.name as subscribe_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
LEFT JOIN subscribe s ON m.receiver = s.id
WHERE m.content LIKE ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
AND m.receiver = ?
ORDER BY formatted_time
LIMIT ? \
'''
return self.execute_query(query, (f'%{keyword}%', subscribe_id, limit))
else:
query = '''
SELECT m.*,
u.name as sender_name,
s.name as subscribe_name,
datetime(substr(m.time, 0, 11), 'unixepoch', '+8 hours') as formatted_time
FROM message m
LEFT JOIN user u ON m.sender = u.id
LEFT JOIN subscribe s ON m.receiver = s.id
WHERE m.content LIKE ?
AND m.type NOT IN ('disagree', 'agree', 'weapp', 'link')
ORDER BY formatted_time
LIMIT ? \
'''
return self.execute_query(query, (f'%{keyword}%', limit))
def get_all_qa(self):
"""获取所有QA记录"""
return self.execute_query('SELECT * FROM qa ORDER BY id DESC')
def get_qa_by_id(self, qa_id):
"""根据ID获取QA记录"""
return self.execute_query('SELECT * FROM qa WHERE id = ?', (qa_id,), fetch_all=False)
def check_qa_exists(self, qa_id):
"""检查QA记录是否存在"""
result = self.execute_query('SELECT id FROM qa WHERE id = ?', (qa_id,), fetch_all=False)
return result is not None
# 报告生成方法 (原ReportGenerator功能)
@staticmethod
def get_date_range(date_str):
"""获取日期范围"""
target_date = from_string(date_str).to_datetime()
start_timestamp = int(from_string(date_str).start_of_day().to_timestamp())
end_timestamp = int(from_string(date_str).end_of_day().to_timestamp())
return target_date, start_timestamp, end_timestamp
@staticmethod
def get_previous_day_range(target_date):
"""获取前一天日期范围"""
yesterday_date = from_datetime(target_date).days_ago(1)
yesterday_start = int(yesterday_date.start_of_day().to_timestamp())
yesterday_end = int(yesterday_date.end_of_day().to_timestamp())
return yesterday_start, yesterday_end
@staticmethod
def calculate_change_rate(current, previous):
"""计算变化率"""
if previous > 0:
return (current - previous) / previous * 100
return 0
@staticmethod
def analyze_hot_topics(messages):
"""分析热点话题"""
keyword_patterns = ['季度报告', 'ESG', '股东大会', '披露', '系统', '操作', '政策']
topic_counts = {}
for msg in messages:
content = msg['content']
for keyword in keyword_patterns:
if keyword in content:
topic_counts[keyword] = topic_counts.get(keyword, 0) + 1
# 排序并取前3个热点
sorted_topics = sorted(topic_counts.items(), key=lambda x: x[1], reverse=True)
return [f"{topic} (提及{count}次)" for topic, count in sorted_topics[:3]]
@staticmethod
def analyze_category_breakdown(messages):
"""分析问题分类"""
category_counts = {
'信息披露类': 0.0,
'公司治理与法规': 0.0,
'业务操作与系统': 0.0,
'其他': 0.0
}
category_keywords = {
'信息披露类': ['披露', '报告', '公告', '季报', '年报', 'ESG'],
'公司治理与法规': ['法规', '合规', '治理', '政策', '规定', '要求'],
'业务操作与系统': ['系统', '操作', '填报', '上传', '下载', '登录']
}
for msg in messages:
content = msg['content']
categorized = False
for category, keywords in category_keywords.items():
for keyword in keywords:
if keyword in content:
category_counts[category] += 1
categorized = True
break
if categorized:
break
if not categorized:
category_counts['其他'] += 1
# 计算百分比
total_categorized = sum(category_counts.values())
if total_categorized > 0:
for category in category_counts:
category_counts[category] = round(category_counts[category] / total_categorized * 100, 1)
return category_counts
@staticmethod
def generate_group_activity_chart(group_activity_data):
"""生成分群活跃度直方图"""
if not group_activity_data:
return "暂无数据"
# 获取前10个群组的数据
top_groups = group_activity_data[:10]
max_count = max(group['message_count'] for group in top_groups) if top_groups else 1
chart = ""
for group in top_groups:
group_name = group['name'] # 限制名称长度
count = group['message_count']
# 计算柱状图长度(最大50个字符)
bar_length = int((count / max_count) * 50) if max_count > 0 else 0
bar = "█" * bar_length
chart += f"{group_name:<15} | {bar} {count}\n"
return chart
def generate_hourly_activity_chart(self, start_timestamp, end_timestamp):
"""生成活跃时段直方图"""
# 获取按小时统计的消息量
hourly_data = self.get_hourly_activity_data(start_timestamp, end_timestamp)
if not hourly_data:
return "暂无数据"
# 创建24小时的完整数据
hour_counts = {str(i).zfill(2): 0 for i in range(24)}
for data in hourly_data:
hour_counts[data['hour']] = data['count']
max_count = max(hour_counts.values()) if hour_counts else 1
chart = ""
for hour in sorted(hour_counts.keys()):
count = hour_counts[hour]
# 计算柱状图长度(最大30个字符)
bar_length = int((count / max_count) * 30) if max_count > 0 else 0
bar = "█" * bar_length
chart += f"{hour}:00 | {bar} {count}\n"
return chart
def api_response(success=True, data=None, error=None, status_code=200, content=None):
"""统一的API响应格式"""
response = {'success': success}
if error:
response['error'] = str(error)
if data:
response.update(data)
if content:
response["data"] = content
return jsonify(response), status_code
ofm = ObjectFileManager()
db_manager = DatabaseManager('db/data', online_db_uri="zysl-log/wework/chat_msg/db/data.db")
report_service = ReportService(db_manager)
message_processor = MessageRender()
chat_session = ChatSessionManager(db_manager)

677
api/v1.py Normal file
View File

@@ -0,0 +1,677 @@
from datetime import datetime, timedelta
from api.lib import report_service, message_processor, db_manager
from . import request, Blueprint
from .lib import api_response
import json
from chat_session import agent
from tools import TimeUtils, now, from_datetime
v1 = Blueprint('v1', __name__)
# API路由 - 消息相关
@v1.route('/api/messages_by_ids', methods=['POST'])
def get_messages_by_ids():
"""根据消息ID列表获取消息内容"""
try:
data = request.get_json()
message_ids = data.get('message_ids', [])
if not message_ids:
return api_response(data={'messages': []})
messages_data = report_service.get_messages_by_ids(message_ids)
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/messages')
def get_messages():
"""获取消息列表API"""
try:
limit = request.args.get('limit', 100, type=int)
offset = request.args.get('offset', 0, type=int)
group_id = request.args.get('group_id', type=str)
messages_data = report_service.get_messages(limit, offset, group_id)
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
formatted_messages.append(message)
return api_response(data={
'messages': formatted_messages,
'total': len(formatted_messages)
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 用户和群组
@v1.route('/api/users')
def get_users():
"""获取用户列表API"""
try:
users_data = report_service.get_all_users()
users = [dict(user) for user in users_data]
return api_response(data={'users': users})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/groups')
def get_groups():
"""获取群组列表API"""
try:
groups_data = report_service.get_all_groups()
groups = [dict(group) for group in groups_data]
return api_response(data={'groups': groups})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/group_messages')
def get_group_messages():
"""获取指定群组的消息API"""
try:
group_id = request.args.get('group_id', type=str)
limit = request.args.get('limit', 100, type=int)
offset = request.args.get('offset', 0, type=int)
if not group_id:
return api_response(success=False, error='缺少群组ID参数', status_code=400)
messages_data = report_service.get_group_messages(group_id, limit, offset)
group_messages_info = report_service.get_group_message_count(group_id)
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages, **group_messages_info})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 统计信息
@v1.route('/api/statistics')
def get_statistics():
"""获取统计数据API"""
try:
statistics = report_service.get_statistics()
return api_response(data=statistics)
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 搜索
@v1.route('/api/search_messages')
def search_messages():
"""搜索消息API"""
try:
keyword = request.args.get('keyword', '')
limit = request.args.get('limit', 100, type=int)
search_type = request.args.get('search_type', 'all') # all, question, answer
if not keyword:
return api_response(success=False, error='请输入搜索关键词', status_code=400)
group_id = request.args.get('group_id', type=str)
# 智能搜索:支持模糊匹配和精确匹配
messages_data = report_service.search_messages(keyword, limit, group_id)
# 根据搜索类型过滤消息
if search_type == 'question':
messages_data = [msg for msg in messages_data if msg.get('type') in ['text', 'mixed']]
elif search_type == 'answer':
messages_data = [msg for msg in messages_data if msg.get('type') in ['text', 'mixed']]
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
# 添加搜索关键词高亮
if keyword:
content = message.get('content', '')
if keyword.lower() in content.lower():
# 简单的关键词高亮(前端可以进一步处理)
highlighted_content = content.replace(keyword, f'<mark>{keyword}</mark>')
message['highlighted_content'] = highlighted_content
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages, 'total': len(formatted_messages)})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 搜索建议
@v1.route('/api/search_suggestions')
def search_suggestions():
"""搜索建议API"""
try:
keyword = request.args.get('keyword', '')
if not keyword:
return api_response(data={'suggestions': []})
# 获取热门搜索关键词
suggestions = []
# 1. 从消息内容中提取相关关键词
try:
messages = report_service.search_messages(keyword, 10, None)
for msg in messages:
content = msg.get('content', '')
# 简单的关键词提取逻辑
words = content.split()
for word in words:
if len(word) > 2 and keyword.lower() in word.lower():
if word not in suggestions:
suggestions.append(word)
if len(suggestions) >= 5:
break
if len(suggestions) >= 5:
break
except:
pass
# 2. 添加常见搜索关键词
common_keywords = ['信息披露', '公司治理', '业务操作', '系统填报', '政策法规', '季度报告', '股东大会']
for kw in common_keywords:
if keyword.lower() in kw.lower() and kw not in suggestions:
suggestions.append(kw)
if len(suggestions) >= 8:
break
return api_response(data={'suggestions': suggestions[:8]})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - QA管理
@v1.route('/api/qa')
def get_qa_list():
"""获取QA列表API"""
try:
# 获取查询参数
group_id = request.args.get('group_id', '')
keyword = request.args.get('keyword', '')
start_time = request.args.get('start_time', '')
end_time = request.args.get('end_time', '')
# 构建查询条件
query_conditions = []
query_params = []
if group_id:
query_conditions.append("groupId = ?")
query_params.append(group_id)
if start_time and end_time:
try:
# 将时间字符串转换为时间戳(毫秒级)
start_timestamp = int(TimeUtils(start_time + ' 00:00:00').to_timestamp() * 1000)
end_timestamp = int(TimeUtils(end_time + ' 23:59:59').to_timestamp() * 1000)
query_conditions.append("start_time >= ? AND end_time <= ?")
query_params.extend([start_timestamp, end_timestamp])
except ValueError:
# 时间格式错误时,忽略时间条件
pass
# 构建查询语句
base_query = "SELECT * FROM qa"
if query_conditions:
base_query += " WHERE " + " AND ".join(query_conditions)
base_query += " ORDER BY id DESC"
# 执行查询
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute(base_query, query_params)
qa_data = cursor.fetchall()
conn.close()
qa_list = [dict(qa) for qa in qa_data]
# 如果有关键词搜索,进行内容过滤
if keyword:
filtered_qa_list = []
for qa in qa_list:
# 检查群组ID是否匹配
if group_id != qa.get('groupId') and group_id != "":
continue
# 检查问题内容是否匹配
try:
question_ids = json.loads(qa.get('question', '[]')) + json.loads(qa.get('answer', '[]'))
if question_ids:
# 获取问题消息内容进行匹配
messages = report_service.get_messages_by_ids(question_ids)
for msg in messages:
if keyword.lower() in (msg['content'] or '').lower():
filtered_qa_list.append(qa)
break
except Exception as e:
print(e)
pass
qa_list = filtered_qa_list
return api_response(data={'qa_list': qa_list, 'total': len(qa_list)})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/qa/<int:qa_id>')
def get_qa_detail(qa_id):
"""获取单个QA详情API"""
try:
qa_data = report_service.get_qa_by_id(qa_id)
if not qa_data:
return api_response(success=False, error='QA记录不存在', status_code=404)
qa_data = dict(qa_data)
try:
qa_data['question_list'] = json.loads(qa_data['question'])
qa_data['answer_list'] = json.loads(qa_data['answer'])
except:
qa_data['question_list'] = []
qa_data['answer_list'] = []
return api_response(data={'qa': qa_data})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/qa', methods=['POST'])
def create_qa():
"""创建QA记录API"""
try:
data = request.get_json()
group_id = data.get('groupId', '')
question_ids = data.get('question', [])
answer_ids = data.get('answer', [])
# 处理时间字符串,转换为时间戳(毫秒级)
start_time = data.get('start_time', '')
end_time = data.get('end_time', '')
try:
if start_time:
start_time = int(TimeUtils(start_time).to_timestamp() * 1000)
if end_time:
end_time = int(TimeUtils(end_time).to_timestamp() * 1000)
except ValueError:
# 时间格式错误时,使用空字符串
start_time = ''
end_time = ''
conn = db_manager.get_connection()
cursor = conn.cursor()
index_id = cursor.execute_query(f"SELECT id FROM qa order by id desc limit 1", fetch_all=False)[0]
index_id += 1
cursor.execute('''
INSERT INTO qa ("id", "groupId", "question", "answer", "start_time", "end_time")
VALUES (?, ?, ?, ?, ?, ?)
''', (index_id, group_id, json.dumps(question_ids), json.dumps(answer_ids),
start_time, end_time))
conn.commit()
qa_id = cursor.lastrowid
conn.close()
return api_response(data={
'id': qa_id,
'message': 'QA记录创建成功'
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/qa/<int:qa_id>', methods=['PUT'])
def update_qa(qa_id):
"""更新QA记录API"""
try:
data = request.get_json()
group_id = data.get('groupId', '')
question_ids = data.get('question', [])
answer_ids = data.get('answer', [])
# 处理时间字符串,转换为时间戳(毫秒级)
start_time = data.get('start_time', '')
end_time = data.get('end_time', '')
try:
if start_time:
start_time = int(TimeUtils(start_time).to_timestamp() * 1000)
if end_time:
end_time = int(TimeUtils(end_time).to_timestamp() * 1000)
except ValueError:
# 时间格式错误时,保持原值不更新
pass
# 检查记录是否存在
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error='QA记录不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
# 如果提供了时间参数,则更新时间字段
if start_time and end_time:
cursor.execute('''
UPDATE qa
SET groupId = ?,
question = ?,
answer = ?,
start_time = ?,
end_time = ?
WHERE id = ?
''',
(group_id, json.dumps(question_ids), json.dumps(answer_ids), start_time, end_time, qa_id))
else:
cursor.execute('''
UPDATE qa
SET groupId = ?,
question = ?,
answer = ?
WHERE id = ?
''', (group_id, json.dumps(question_ids), json.dumps(answer_ids), qa_id))
conn.commit()
conn.close()
return api_response(data={'message': 'QA记录更新成功'})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/qa/<int:qa_id>', methods=['DELETE'])
def delete_qa(qa_id):
"""删除单个QA记录API"""
try:
# 检查记录是否存在
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error='QA记录不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute('DELETE FROM qa WHERE id = ?', (qa_id,))
conn.commit()
conn.close()
return api_response(data={'message': 'QA记录删除成功'})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/qa/batch', methods=['DELETE'])
def batch_delete_qa():
"""批量删除QA记录API"""
try:
data = request.get_json()
qa_ids = data.get('qa_ids', [])
if not qa_ids:
return api_response(success=False, error='请提供要删除的QA记录ID列表', status_code=400)
# 检查所有记录是否存在
for qa_id in qa_ids:
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error=f'QA记录 {qa_id} 不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
# 构建批量删除语句
placeholders = ','.join(['?'] * len(qa_ids))
query = f'DELETE FROM qa WHERE id IN ({placeholders})'
cursor.execute(query, qa_ids)
conn.commit()
conn.close()
return api_response(status_code=404)
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 报告生成
@v1.route('/api/reports/daily')
def generate_daily_report():
"""生成日报API"""
try:
date_str = request.args.get('date', now().to_string(TimeUtils.FORMAT_DATE))
target_date, start_timestamp, end_timestamp = report_service.get_date_range(date_str)
yesterday_start, yesterday_end = report_service.get_previous_day_range(target_date)
# 使用ReportService类获取数据
today_message_count = report_service.get_message_count_by_time_range(start_timestamp, end_timestamp)
yesterday_message_count = report_service.get_message_count_by_time_range(yesterday_start, yesterday_end)
message_change_rate = report_service.calculate_change_rate(
today_message_count, yesterday_message_count
)
# 获取活跃用户数
today_active_users = report_service.get_active_users_by_time_range(start_timestamp, end_timestamp)
yesterday_active_users = report_service.get_active_users_by_time_range(yesterday_start, yesterday_end)
user_change_rate = report_service.calculate_change_rate(
today_active_users, yesterday_active_users
)
# 获取群组和成员数量
group_count = report_service.get_group_count()
member_count = report_service.get_member_count()
# 获取QA记录数量
today_qa_count = report_service.get_qa_count_by_time_range(start_timestamp, end_timestamp)
yesterday_qa_count = report_service.get_qa_count_by_time_range(yesterday_start, yesterday_end)
non_qa_count = report_service.get_non_answer_qa_count_by_time_range(start_timestamp, end_timestamp)
qa_change_rate = report_service.calculate_change_rate(
today_qa_count, yesterday_qa_count
)
# 获取群组活跃度
group_activity_data = report_service.get_group_activity_data(start_timestamp, end_timestamp)
# 获取热点话题
recent_messages = report_service.get_recent_messages_by_time_range(start_timestamp, end_timestamp, 100)
hot_topics = report_service.analyze_hot_topics(recent_messages)
# 生成直方图
group_activity_chart = report_service.generate_group_activity_chart(group_activity_data)
hourly_activity_chart = report_service.generate_hourly_activity_chart(start_timestamp, end_timestamp)
# 生成日报
report_date = from_datetime(target_date).format_custom('%Y年%m月%d日 (%A)')
daily_report = f"""# **致远速联高端社群运营日报**
**报告周期**:{report_date}
**覆盖范围**:{group_count}个群,总计成员 {member_count} 人
## **1. 核心数据概览**
| 指标 | 今日数据 | 昨日数据 | 变化 |
| :--- | :--- | :--- | :--- |
| 总消息量 | {today_message_count} 条 | {yesterday_message_count} 条 | {'↑' if message_change_rate >= 0 else '↓'}{abs(message_change_rate):.1f}% |
| 活跃人数 | {today_active_users} 人 | {yesterday_active_users} 人 | {'↑' if user_change_rate >= 0 else '↓'}{abs(user_change_rate):.1f}% |
| 问题新增记录 | {today_qa_count} 条 | {yesterday_qa_count} 条 | {'↑' if qa_change_rate >= 0 else '↓'}{abs(qa_change_rate):.1f}% |
## **2. 分群活跃度对比**
```
{group_activity_chart}
```
## **3. 今日热点与时段**
### **热点问题TOP 3**:
"""
for i, topic in enumerate(hot_topics, 1):
daily_report += f" {i}. {topic}\n"
daily_report += f"""### **活跃时段**:消息高峰分布
```
{hourly_activity_chart}
```
## **4. 待办事项**
{"有" + str(non_qa_count) + "条未被回答的问题,关注活跃群组的后续讨论,确保所有疑问被解答。" if non_qa_count > 0 else "无未被回答的问题,继续保持活跃群组的讨论。"}
明日重点关注:根据今日热点预测明日可能的问题。
"""
return api_response(data={
'report': daily_report,
'date': date_str,
'statistics': {
'today_message_count': today_message_count,
'yesterday_message_count': yesterday_message_count,
'message_change_rate': message_change_rate,
'today_active_users': today_active_users,
'yesterday_active_users': yesterday_active_users,
'user_change_rate': user_change_rate,
'today_qa_count': today_qa_count,
'yesterday_qa_count': yesterday_qa_count,
'qa_change_rate': qa_change_rate,
'group_count': group_count,
'member_count': member_count
}
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v1.route('/api/reports/weekly')
def generate_weekly_report():
"""生成周报API"""
try:
week_str = request.args.get('week', now().format_custom('%Y-%W'))
# 解析week_str参数,格式为YYYY-WW
year, week_num = map(int, week_str.split('-'))
# 根据周数计算时间范围
# 获取指定年份的第一天
first_day_of_year = datetime(year, 1, 1)
# 计算第一周的周一(ISO周从周一开始)
first_monday = first_day_of_year + timedelta(days=(7 - first_day_of_year.weekday()) % 7)
if first_day_of_year.weekday() <= 3: # 如果1月1日是周一到周四,则属于第一周
first_monday = first_day_of_year - timedelta(days=first_day_of_year.weekday())
# 计算指定周的周一
week_monday = first_monday + timedelta(weeks=week_num - 1)
# 计算指定周的周日
week_sunday = week_monday + timedelta(days=6)
start_date = week_monday.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = week_sunday.replace(hour=23, minute=59, second=59, microsecond=999999)
start_timestamp = int(from_datetime(start_date).to_timestamp())
end_timestamp = int(from_datetime(end_date).to_timestamp())
# 获取上周同期数据
last_week_start = start_date - timedelta(weeks=1)
last_week_end = end_date - timedelta(weeks=1)
last_week_start_ts = int(from_datetime(last_week_start).to_timestamp())
last_week_end_ts = int(from_datetime(last_week_end).to_timestamp())
# 使用ReportService类获取数据
week_message_count = report_service.get_message_count_by_time_range(start_timestamp, end_timestamp)
last_week_message_count = report_service.get_message_count_by_time_range(last_week_start_ts, last_week_end_ts)
message_change_rate = report_service.calculate_change_rate(
week_message_count, last_week_message_count
)
# 获取活跃用户数
week_active_users = report_service.get_active_users_by_time_range(start_timestamp, end_timestamp)
# 获取群组和成员数量
group_count = report_service.get_group_count()
member_count = report_service.get_member_count()
# 获取QA记录数量
qa_count = report_service.get_qa_count_by_time_range(start_timestamp, end_timestamp)
non_answer_qa_count = report_service.get_non_answer_qa_count_by_time_range(start_timestamp, end_timestamp)
# 获取本周消息用于分类分析
week_messages = report_service.get_weekly_messages(start_timestamp, end_timestamp, 200)
category_breakdown = report_service.analyze_category_breakdown(week_messages)
# 生成周报
week_period = f"{from_datetime(start_date).format_custom('%Y年%m月%d日')} - {from_datetime(end_date).format_custom('%Y年%m月%d日')} (第{week_str.split('-')[1]}周)"
weekly_report = f"""# **致远速联高端社群运营周报**
**报告周期**:{week_period}
## **1. 本周摘要**
- 本周社群总消息量 **{week_message_count}条**,{'环比上升' if message_change_rate >= 0 else '环比下降'} **{abs(message_change_rate):.1f}%**。
- 活跃用户数达到 **{week_active_users}人**,社群参与度良好。
- 新增问题解答记录 **{qa_count}条**,知识沉淀效果显著。
- 其中,有 **{non_answer_qa_count}条** 未被回答,值得注意。
## **2. 深度洞察:上市公司提问分类**
"""
# 添加问题分类信息
for category, percentage in category_breakdown.items():
weekly_report += f"- {category}:{percentage}%\n"
weekly_report += f"""\n**洞察**:问题集中在信披和法规,说明成员对合规性高度重视。
## **3. 内容价值与知识沉淀**
- **本周高价值问答**:系统自动识别并记录了{qa_count}条有价值的问答记录。
- **建议**:将高频问答整理至知识库,便于后续参考。
"""
result = agent.chat_deepseek(weekly_report,
"根据下面的内容给出总结和建议,格式如下:\n## **4. 总结与建议**\n**总结**:\n**建议**:\n")
return api_response(data={
'report': weekly_report + "\n" + result,
'week': week_str,
'statistics': {
'week_message_count': week_message_count,
'last_week_message_count': last_week_message_count,
'message_change_rate': message_change_rate,
'week_active_users': week_active_users,
'group_count': group_count,
'member_count': member_count,
'qa_count': qa_count,
'category_breakdown': category_breakdown
}
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)

641
api/v2.py Normal file
View File

@@ -0,0 +1,641 @@
from datetime import datetime, timedelta
from api.lib import report_service, message_processor, db_manager
from . import request, Blueprint
from .lib import api_response
import json
from chat_session import agent
from tools import TimeUtils, now, from_datetime
v2 = Blueprint('v2', __name__)
# API路由 - 消息相关
@v2.route('/api/messages_by_ids', methods=['POST'])
def get_messages_by_ids():
"""根据消息ID列表获取消息内容"""
try:
data = request.get_json()
message_ids = data.get('message_ids', [])
if not message_ids:
return api_response(data={'messages': []})
messages_data = report_service.get_messages_by_ids(message_ids)
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# v2 API路由 - 使用与v1相同命名但使用subscribe表
@v2.route('/api/groups')
def get_groups():
"""获取订阅列表API (v2版本,使用subscribe表)"""
try:
subscribes_data = report_service.get_all_subscribes()
groups = [dict(subscribe) for subscribe in subscribes_data]
return api_response(data={'groups': groups})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/group_messages')
def get_group_messages():
"""获取指定订阅的消息API (v2版本,使用subscribe表)"""
try:
group_id = request.args.get('group_id', type=str)
limit = request.args.get('limit', 100, type=int)
offset = request.args.get('offset', 0, type=int)
if not group_id:
return api_response(success=False, error='缺少群组ID参数', status_code=400)
messages_data = report_service.get_subscribe_messages(group_id, limit, offset)
group_messages_info = report_service.get_subscribe_message_count(group_id)
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages, **group_messages_info})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# v2 API路由 - 统计信息
@v2.route('/api/statistics')
def get_statistics():
"""获取统计数据API (v2版本,使用subscribe表)"""
try:
statistics = report_service.get_v2_statistics()
return api_response(data=statistics)
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# v2 API路由 - 搜索
@v2.route('/api/search_messages')
def search_messages():
"""搜索消息API (v2版本,使用subscribe表)"""
try:
keyword = request.args.get('keyword', '')
limit = request.args.get('limit', 100, type=int)
search_type = request.args.get('search_type', 'all') # all, question, answer
if not keyword:
return api_response(success=False, error='请输入搜索关键词', status_code=400)
group_id = request.args.get('group_id', type=str)
# 智能搜索:支持模糊匹配和精确匹配
messages_data = report_service.search_subscribe_messages(keyword, limit, group_id)
# 根据搜索类型过滤消息
if search_type == 'question':
messages_data = [msg for msg in messages_data if msg.get('type') in ['text', 'mixed']]
elif search_type == 'answer':
messages_data = [msg for msg in messages_data if msg.get('type') in ['text', 'mixed']]
formatted_messages = []
for msg in messages_data:
message = dict(msg)
message = message_processor.process_message_content(message)
# 添加搜索关键词高亮
if keyword:
content = message.get('content', '')
if keyword.lower() in content.lower():
# 简单的关键词高亮(前端可以进一步处理)
highlighted_content = content.replace(keyword, f'<mark>{keyword}</mark>')
message['highlighted_content'] = highlighted_content
formatted_messages.append(message)
return api_response(data={'messages': formatted_messages, 'total': len(formatted_messages)})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# API路由 - 搜索建议
@v2.route('/api/search_suggestions')
def search_suggestions():
"""搜索建议API"""
try:
keyword = request.args.get('keyword', '')
if not keyword:
return api_response(data={'suggestions': []})
# 获取热门搜索关键词
suggestions = []
# 1. 从消息内容中提取相关关键词
try:
messages = report_service.search_messages(keyword, 10, None)
for msg in messages:
content = msg.get('content', '')
# 简单的关键词提取逻辑
words = content.split()
for word in words:
if len(word) > 2 and keyword.lower() in word.lower():
if word not in suggestions:
suggestions.append(word)
if len(suggestions) >= 5:
break
if len(suggestions) >= 5:
break
except:
pass
# 2. 添加常见搜索关键词
common_keywords = ['信息披露', '公司治理', '业务操作', '系统填报', '政策法规', '季度报告', '股东大会']
for kw in common_keywords:
if keyword.lower() in kw.lower() and kw not in suggestions:
suggestions.append(kw)
if len(suggestions) >= 8:
break
return api_response(data={'suggestions': suggestions[:8]})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# v2 API路由 - QA管理 (使用subscribe表)
@v2.route('/api/qa')
def get_qa_list():
"""获取QA列表API (v2版本,使用subscribe表)"""
try:
# 获取查询参数
group_id = request.args.get('group_id', '')
keyword = request.args.get('keyword', '')
start_time = request.args.get('start_time', '')
end_time = request.args.get('end_time', '')
# 构建查询条件
query_conditions = []
query_params = []
if group_id:
query_conditions.append("a.groupId = ?")
query_params.append(group_id)
if start_time and end_time:
try:
# 将时间字符串转换为时间戳(毫秒级)
start_timestamp = int(TimeUtils(start_time + ' 00:00:00').to_timestamp() * 1000)
end_timestamp = int(TimeUtils(end_time + ' 23:59:59').to_timestamp() * 1000)
query_conditions.append("a.start_time >= ? AND a.end_time <= ?")
query_params.extend([start_timestamp, end_timestamp])
except ValueError:
# 时间格式错误时,忽略时间条件
pass
# 构建查询语句
base_query = "SELECT a.* FROM subscribe as s inner join qa as a on a.groupId = s.id"
if query_conditions:
base_query += " WHERE " + " AND ".join(query_conditions)
base_query += " ORDER BY a.id DESC"
# 执行查询
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute(base_query, query_params)
qa_data = cursor.fetchall()
conn.close()
qa_list = [dict(qa) for qa in qa_data]
# 如果有关键词搜索,进行内容过滤
if keyword:
filtered_qa_list = []
for qa in qa_list:
# 检查群组ID是否匹配
if group_id != qa.get('groupId') and group_id != "":
continue
# 检查问题内容是否匹配
try:
question_ids = json.loads(qa.get('question', '[]')) + json.loads(qa.get('answer', '[]'))
if question_ids:
# 获取问题消息内容进行匹配
messages = report_service.get_messages_by_ids(question_ids)
for msg in messages:
if keyword.lower() in (msg['content'] or '').lower():
filtered_qa_list.append(qa)
break
except Exception as e:
print(e)
pass
qa_list = filtered_qa_list
return api_response(data={'qa_list': qa_list, 'total': len(qa_list)})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/qa/<int:qa_id>')
def get_qa_detail(qa_id):
"""获取单个QA详情API (v2版本,使用subscribe表)"""
try:
qa_data = report_service.get_qa_by_id(qa_id)
if not qa_data:
return api_response(success=False, error='QA记录不存在', status_code=404)
qa_data = dict(qa_data)
try:
qa_data['question_list'] = json.loads(qa_data['question'])
qa_data['answer_list'] = json.loads(qa_data['answer'])
except:
qa_data['question_list'] = []
qa_data['answer_list'] = []
return api_response(data={'qa': qa_data})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/qa', methods=['POST'])
def create_qa():
"""创建QA记录API (v2版本,使用subscribe表)"""
try:
data = request.get_json()
group_id = data.get('groupId', '')
question_ids = data.get('question', [])
answer_ids = data.get('answer', [])
# 处理时间字符串,转换为时间戳(毫秒级)
start_time = data.get('start_time', '')
end_time = data.get('end_time', '')
try:
if start_time:
start_time = int(TimeUtils(start_time).to_timestamp() * 1000)
if end_time:
end_time = int(TimeUtils(end_time).to_timestamp() * 1000)
except ValueError:
# 时间格式错误时,使用空字符串
start_time = ''
end_time = ''
conn = db_manager.get_connection()
cursor = conn.cursor()
index_id = cursor.execute_query(f"SELECT id FROM qa order by id desc limit 1", fetch_all=False)[0]
index_id += 1
cursor.execute('''
INSERT INTO qa ("id", "groupId", "question", "answer", "start_time", "end_time")
VALUES (?, ?, ?, ?, ?, ?)
''', (index_id, group_id, json.dumps(question_ids), json.dumps(answer_ids),
start_time, end_time))
conn.commit()
qa_id = cursor.lastrowid
conn.close()
return api_response(data={
'id': qa_id,
'message': 'QA记录创建成功'
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/qa/<int:qa_id>', methods=['PUT'])
def update_qa(qa_id):
"""更新QA记录API (v2版本,使用subscribe表)"""
try:
data = request.get_json()
group_id = data.get('groupId', '')
question_ids = data.get('question', [])
answer_ids = data.get('answer', [])
# 处理时间字符串,转换为时间戳(毫秒级)
start_time = data.get('start_time', '')
end_time = data.get('end_time', '')
try:
if start_time:
start_time = int(TimeUtils(start_time).to_timestamp() * 1000)
if end_time:
end_time = int(TimeUtils(end_time).to_timestamp() * 1000)
except ValueError:
# 时间格式错误时,保持原值不更新
pass
# 检查记录是否存在
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error='QA记录不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
# 如果提供了时间参数,则更新时间字段
if start_time and end_time:
cursor.execute('''
UPDATE qa
SET groupId = ?,
question = ?,
answer = ?,
start_time = ?,
end_time = ?
WHERE id = ?
''',
(group_id, json.dumps(question_ids), json.dumps(answer_ids), start_time, end_time, qa_id))
else:
cursor.execute('''
UPDATE qa
SET groupId = ?,
question = ?,
answer = ?
WHERE id = ?
''', (group_id, json.dumps(question_ids), json.dumps(answer_ids), qa_id))
conn.commit()
conn.close()
return api_response(data={'message': 'QA记录更新成功'})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/qa/<int:qa_id>', methods=['DELETE'])
def delete_qa(qa_id):
"""删除单个QA记录API (v2版本,使用subscribe表)"""
try:
# 检查记录是否存在
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error='QA记录不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute('DELETE FROM qa WHERE id = ?', (qa_id,))
conn.commit()
conn.close()
return api_response(data={'message': 'QA记录删除成功'})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/qa/batch', methods=['DELETE'])
def batch_delete_qa():
"""批量删除QA记录API (v2版本,使用subscribe表)"""
try:
data = request.get_json()
qa_ids = data.get('qa_ids', [])
if not qa_ids:
return api_response(success=False, error='请提供要删除的QA记录ID列表', status_code=400)
# 检查所有记录是否存在
for qa_id in qa_ids:
if not report_service.check_qa_exists(qa_id):
return api_response(success=False, error=f'QA记录 {qa_id} 不存在', status_code=404)
conn = db_manager.get_connection()
cursor = conn.cursor()
# 构建批量删除语句
placeholders = ','.join(['?'] * len(qa_ids))
query = f'DELETE FROM qa WHERE id IN ({placeholders})'
cursor.execute(query, qa_ids)
conn.commit()
conn.close()
return api_response(data={'message': '批量删除QA记录成功'})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
# v2 API路由 - 报告生成
@v2.route('/api/reports/daily')
def generate_daily_report():
"""生成日报API (v2版本,使用subscribe表)"""
try:
date_str = request.args.get('date', now().to_string(TimeUtils.FORMAT_DATE))
target_date, start_timestamp, end_timestamp = report_service.get_date_range(date_str)
yesterday_start, yesterday_end = report_service.get_previous_day_range(target_date)
# 使用ReportService类获取数据
today_message_count = report_service.get_message_count_by_time_range(start_timestamp, end_timestamp)
yesterday_message_count = report_service.get_message_count_by_time_range(yesterday_start, yesterday_end)
message_change_rate = report_service.calculate_change_rate(
today_message_count, yesterday_message_count
)
# 获取活跃用户数
today_active_users = report_service.get_active_sub_users_by_time_range(start_timestamp, end_timestamp)
yesterday_active_users = report_service.get_active_sub_users_by_time_range(yesterday_start, yesterday_end)
user_change_rate = report_service.calculate_change_rate(
today_active_users, yesterday_active_users
)
# 获取订阅和成员数量
subscribe_count = report_service.get_subscribe_count()
member_count = report_service.get_member_count()
# 获取QA记录数量
today_qa_count = report_service.get_qa_count_by_time_range_sub(start_timestamp, end_timestamp)
yesterday_qa_count = report_service.get_qa_count_by_time_range_sub(yesterday_start, yesterday_end)
non_qa_count = report_service.get_non_answer_qa_count_by_time_range_sub(start_timestamp, end_timestamp)
qa_change_rate = report_service.calculate_change_rate(
today_qa_count, yesterday_qa_count
)
# 获取订阅活跃度
subscribe_activity_data = report_service.get_subscribe_activity_data(start_timestamp, end_timestamp)
# 获取热点话题
recent_messages = report_service.get_recent_messages_by_time_range(start_timestamp, end_timestamp, 100)
hot_topics = report_service.analyze_hot_topics(recent_messages)
# 生成直方图
subscribe_activity_chart = report_service.generate_group_activity_chart(subscribe_activity_data)
hourly_activity_chart = report_service.generate_hourly_activity_chart(start_timestamp, end_timestamp)
# 生成日报
report_date = from_datetime(target_date).format_custom('%Y年%m月%d日 (%A)')
daily_report = f"""# **致远速联高端社群运营日报**
**报告周期**:{report_date}
**覆盖范围**:{subscribe_count}个订阅,总计成员 {member_count} 人
## **1. 核心数据概览**
| 指标 | 今日数据 | 昨日数据 | 变化 |
| :--- | :--- | :--- | :--- |
| 总消息量 | {today_message_count} 条 | {yesterday_message_count} 条 | {'↑' if message_change_rate >= 0 else '↓'}{abs(message_change_rate):.1f}% |
| 活跃人数 | {today_active_users} 人 | {yesterday_active_users} 人 | {'↑' if user_change_rate >= 0 else '↓'}{abs(user_change_rate):.1f}% |
| 问题新增记录 | {today_qa_count} 条 | {yesterday_qa_count} 条 | {'↑' if qa_change_rate >= 0 else '↓'}{abs(qa_change_rate):.1f}% |
## **2. 分订阅活跃度对比**
```
{subscribe_activity_chart}
```
## **3. 今日热点与时段**
### **热点问题TOP 3**:
"""
for i, topic in enumerate(hot_topics, 1):
daily_report += f" {i}. {topic}\n"
daily_report += f"""### **活跃时段**:消息高峰分布
```
{hourly_activity_chart}
```
## **4. 待办事项**
{"有" + str(non_qa_count) + "条未被回答的问题,关注活跃订阅的后续讨论,确保所有疑问被解答。" if non_qa_count > 0 else "无未被回答的问题,继续保持活跃订阅的讨论。"}
明日重点关注:根据今日热点预测明日可能的问题。
"""
return api_response(data={
'report': daily_report,
'date': date_str,
'statistics': {
'today_message_count': today_message_count,
'yesterday_message_count': yesterday_message_count,
'message_change_rate': message_change_rate,
'today_active_users': today_active_users,
'yesterday_active_users': yesterday_active_users,
'user_change_rate': user_change_rate,
'today_qa_count': today_qa_count,
'yesterday_qa_count': yesterday_qa_count,
'qa_change_rate': qa_change_rate,
'group_count': subscribe_count,
'member_count': member_count
}
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)
@v2.route('/api/reports/weekly')
def generate_weekly_report():
"""生成周报API (v2版本,使用subscribe表)"""
try:
week_str = request.args.get('week', now().format_custom('%Y-%W'))
# 解析week_str参数,格式为YYYY-WW
year, week_num = map(int, week_str.split('-'))
# 根据周数计算时间范围
# 获取指定年份的第一天
first_day_of_year = datetime(year, 1, 1)
# 计算第一周的周一(ISO周从周一开始)
first_monday = first_day_of_year + timedelta(days=(7 - first_day_of_year.weekday()) % 7)
if first_day_of_year.weekday() <= 3: # 如果1月1日是周一到周四,则属于第一周
first_monday = first_day_of_year - timedelta(days=first_day_of_year.weekday())
# 计算指定周的周一
week_monday = first_monday + timedelta(weeks=week_num - 1)
# 计算指定周的周日
week_sunday = week_monday + timedelta(days=6)
start_date = week_monday.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = week_sunday.replace(hour=23, minute=59, second=59, microsecond=999999)
start_timestamp = int(from_datetime(start_date).to_timestamp())
end_timestamp = int(from_datetime(end_date).to_timestamp())
# 获取上周同期数据
last_week_start = start_date - timedelta(weeks=1)
last_week_end = end_date - timedelta(weeks=1)
last_week_start_ts = int(from_datetime(last_week_start).to_timestamp())
last_week_end_ts = int(from_datetime(last_week_end).to_timestamp())
# 使用ReportService类获取数据
week_message_count = report_service.get_message_count_by_time_range(start_timestamp, end_timestamp)
last_week_message_count = report_service.get_message_count_by_time_range(last_week_start_ts, last_week_end_ts)
message_change_rate = report_service.calculate_change_rate(
week_message_count, last_week_message_count
)
# 获取活跃用户数
week_active_users = report_service.get_active_sub_users_by_time_range(start_timestamp, end_timestamp)
# 获取订阅和成员数量
subscribe_count = report_service.get_subscribe_count()
member_count = report_service.get_member_count()
# 获取QA记录数量
qa_count = report_service.get_qa_count_by_time_range(start_timestamp, end_timestamp)
non_answer_qa_count = report_service.get_non_answer_qa_count_by_time_range(start_timestamp, end_timestamp)
# 获取本周消息用于分类分析
week_messages = report_service.get_weekly_messages(start_timestamp, end_timestamp, 200)
category_breakdown = report_service.analyze_category_breakdown(week_messages)
# 生成周报
week_period = f"{from_datetime(start_date).format_custom('%Y年%m月%d日')} - {from_datetime(end_date).format_custom('%Y年%m月%d日')} (第{week_str.split('-')[1]}周)"
weekly_report = f"""# **致远速联高端社群运营周报**
**报告周期**:{week_period}
## **1. 本周摘要**
- 本周社群总消息量 **{week_message_count}条**,{'环比上升' if message_change_rate >= 0 else '环比下降'} **{abs(message_change_rate):.1f}%**。
- 活跃用户数达到 **{week_active_users}人**,社群参与度良好。
- 新增问题解答记录 **{qa_count}条**,知识沉淀效果显著。
- 其中,有 **{non_answer_qa_count}条** 未被回答,值得注意。
## **2. 深度洞察:上市公司提问分类**
"""
# 添加问题分类信息
for category, percentage in category_breakdown.items():
weekly_report += f"- {category}:{percentage}%\n"
weekly_report += f"""\n**洞察**:问题集中在信披和法规,说明成员对合规性高度重视。
## **3. 内容价值与知识沉淀**
- **本周高价值问答**:系统自动识别并记录了{qa_count}条有价值的问答记录。
- **建议**:将高频问答整理至知识库,便于后续参考。
"""
result = agent.chat_deepseek(weekly_report,
"根据下面的内容给出总结和建议,格式如下:\n## **4. 总结与建议**\n**总结**:\n**建议**:\n")
return api_response(data={
'report': weekly_report + "\n" + result,
'week': week_str,
'statistics': {
'week_message_count': week_message_count,
'last_week_message_count': last_week_message_count,
'message_change_rate': message_change_rate,
'week_active_users': week_active_users,
'group_count': subscribe_count,
'member_count': member_count,
'qa_count': qa_count,
'category_breakdown': category_breakdown
}
})
except Exception as e:
return api_response(success=False, error=e, status_code=500)

276
api/v3.py Normal file
View File

@@ -0,0 +1,276 @@
"""
主程序入口 - 集成周报生成器
"""
from datetime import datetime, timedelta
from . import Blueprint
from api.lib import db_manager
from weekly_report_generator import WeeklyReportGenerator
from flask import request, jsonify, render_template
v3 = Blueprint('v3', __name__)
# 初始化周报生成器
weekly_report_generator = None
def init_weekly_report_generator():
"""初始化周报生成器"""
global weekly_report_generator
if weekly_report_generator is None:
weekly_report_generator = WeeklyReportGenerator(db_manager)
@v3.route('/api/weekly_report/generate', methods=['GET', 'POST'])
def generate_weekly_report_api():
"""生成周报API接口 (v3版本,基于需求文档样式)"""
try:
init_weekly_report_generator()
# 获取参数
if request.method == 'POST':
data = request.get_json()
week_str = data.get('week')
use_deepseek = data.get('use_deepseek', True)
else:
week_str = request.args.get('week')
use_deepseek = request.args.get('use_deepseek', 'true').lower() == 'true'
# 如果没有提供周次,使用当前周
if not week_str:
current_date = datetime.now()
year = current_date.isocalendar()[0]
week_num = current_date.isocalendar()[1]
week_str = f"{year}-{week_num:02d}"
# 生成周报
result = weekly_report_generator.generate_weekly_report(week_str, use_deepseek)
if result['success']:
return jsonify({
'success': True,
'data': {
'report': result['report'],
'week': result['week'],
'statistics': result.get('statistics', {})
}
})
else:
return jsonify({
'success': False,
'error': result['error']
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': f"生成周报失败: {str(e)}"
}), 500
@v3.route('/api/weekly_report/weeks', methods=['GET'])
def get_available_weeks_v3():
"""获取可用的周次列表 (v3版本)"""
try:
init_weekly_report_generator()
# 获取数据库中最早和最晚的消息时间
result = db_manager.execute_query('''
SELECT MIN(time) as min_time, MAX(time) as max_time
FROM message
WHERE LENGTH(time) >= 10
''', fetch_all=False)
if result and result['min_time'] and result['max_time']:
min_timestamp = int(result['min_time'][:10])
max_timestamp = int(result['max_time'][:10])
min_date = datetime.fromtimestamp(min_timestamp)
max_date = datetime.fromtimestamp(max_timestamp)
# 生成可用的周次列表
weeks = []
current_date = min_date
while current_date <= max_date:
year = current_date.isocalendar()[0]
week_num = current_date.isocalendar()[1]
week_str = f"{year}-{week_num:02d}"
# 计算周的开始和结束日期
start_of_week = current_date - timedelta(days=current_date.weekday())
end_of_week = start_of_week + timedelta(days=6)
weeks.append({
'week': week_str,
'start_date': start_of_week.strftime('%Y-%m-%d'),
'end_date': end_of_week.strftime('%Y-%m-%d'),
'display': f"{year}年第{week_num}周 ({start_of_week.strftime('%m.%d')}-{end_of_week.strftime('%m.%d')})"
})
current_date += timedelta(weeks=1)
return jsonify({
'success': True,
'data': {
'weeks': weeks[-20:] # 返回最近20周
}
})
else:
return jsonify({
'success': False,
'error': '无法获取时间范围数据'
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': f"获取周次列表失败: {str(e)}"
}), 500
@v3.route('/api/weekly_report/preview', methods=['POST'])
def preview_weekly_report():
"""预览周报API接口 (v3版本)"""
try:
init_weekly_report_generator()
data = request.get_json()
week_str = data.get('week')
if not week_str:
return jsonify({
'success': False,
'error': '请提供周次参数'
}), 400
# 生成预览数据(不调用DeepSeek API)
result = weekly_report_generator.generate_weekly_report(week_str, use_deepseek=False)
if result['success']:
return jsonify({
'success': True,
'data': {
'preview': result['report'],
'statistics': result.get('statistics', {})
}
})
else:
return jsonify({
'success': False,
'error': result['error']
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': f"预览周报失败: {str(e)}"
}), 500
@v3.route('/api/weekly_report/export', methods=['POST'])
def export_weekly_report():
"""导出周报API接口 (v3版本)"""
try:
init_weekly_report_generator()
data = request.get_json()
week_str = data.get('week')
export_format = data.get('format', 'markdown') # markdown, html, pdf
if not week_str:
return jsonify({
'success': False,
'error': '请提供周次参数'
}), 400
# 生成完整周报
result = weekly_report_generator.generate_weekly_report(week_str, use_deepseek=True)
if result['success']:
report_content = result['report']
# 根据格式处理内容
if export_format == 'html':
# 简单的Markdown转HTML(实际项目中可以使用专业库)
html_content = markdown_to_html(report_content)
return jsonify({
'success': True,
'data': {
'content': html_content,
'filename': f"周报_{week_str}.html",
'mime_type': 'text/html'
}
})
elif export_format == 'pdf':
# PDF导出(需要额外库支持)
return jsonify({
'success': False,
'error': 'PDF导出功能暂未实现'
}), 501
else: # markdown
return jsonify({
'success': True,
'data': {
'content': report_content,
'filename': f"周报_{week_str}.md",
'mime_type': 'text/markdown'
}
})
else:
return jsonify({
'success': False,
'error': result['error']
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': f"导出周报失败: {str(e)}"
}), 500
def markdown_to_html(markdown_text: str) -> str:
"""简单的Markdown转HTML函数"""
# 基本转换规则
html = markdown_text
# 标题转换
html = html.replace('\n# ', '\n<h1>').replace('\n## ', '\n<h2>').replace('\n### ', '\n<h3>')
html = html.replace('\n#### ', '\n<h4>').replace('\n##### ', '\n<h5>').replace('\n###### ', '\n<h6>')
# 表格处理
html = html.replace('\t', '</td><td>')
html = html.replace('\n', '</td></tr>\n<tr><td>')
# 基本的HTML包装
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>周报</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
</style>
</head>
<body>
{html}
</body>
</html>"""
return html
# v3周报页面路由
@v3.route('/weekly_report')
def weekly_report_page():
"""周报页面 v3版本"""
return render_template('weekly_report_v3.html')