feat: KDocs 上传增强 + 离线监控 + Bug修复

KDocs 上传功能增强:
- 搜索优化:只用姓名搜索 + C列验证,避免匹配到错误单元格
- 有效行范围:支持配置起始行/结束行,限制上传区域
- 图片覆盖:支持覆盖单元格已有图片(Escape + Delete)
- 配置持久化:kdocs_row_start/row_end 保存到数据库(v18迁移)

二次登录功能:
- 登录后立即再次登录,让"上次登录时间"显示为刚刚

KDocs 离线监控:
- 每5分钟检测金山文档登录状态
- 离线时发送邮件通知管理员(每次掉线只通知一次)
- 恢复在线后重置通知状态

Bug 修复:
- 任务日志搜索账号关键词报错500:添加异常处理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 23:40:46 +08:00
parent 13544867aa
commit b0fe325154
31 changed files with 604 additions and 141 deletions

View File

@@ -680,6 +680,8 @@ def update_system_config_api():
kdocs_image_column = data.get("kdocs_image_column")
kdocs_admin_notify_enabled = data.get("kdocs_admin_notify_enabled")
kdocs_admin_notify_email = data.get("kdocs_admin_notify_email")
kdocs_row_start = data.get("kdocs_row_start")
kdocs_row_end = data.get("kdocs_row_end")
if max_concurrent is not None:
if not isinstance(max_concurrent, int) or max_concurrent < 1:
@@ -787,6 +789,22 @@ def update_system_config_api():
if not is_valid:
return jsonify({"error": error_msg}), 400
if kdocs_row_start is not None:
try:
kdocs_row_start = int(kdocs_row_start)
except (ValueError, TypeError):
return jsonify({"error": "起始行必须是数字"}), 400
if kdocs_row_start < 0:
return jsonify({"error": "起始行不能为负数"}), 400
if kdocs_row_end is not None:
try:
kdocs_row_end = int(kdocs_row_end)
except (ValueError, TypeError):
return jsonify({"error": "结束行必须是数字"}), 400
if kdocs_row_end < 0:
return jsonify({"error": "结束行不能为负数"}), 400
old_config = database.get_system_config() or {}
if not database.update_system_config(
@@ -810,6 +828,8 @@ def update_system_config_api():
kdocs_image_column=kdocs_image_column,
kdocs_admin_notify_enabled=kdocs_admin_notify_enabled,
kdocs_admin_notify_email=kdocs_admin_notify_email,
kdocs_row_start=kdocs_row_start,
kdocs_row_end=kdocs_row_end,
):
return jsonify({"error": "更新失败"}), 400
@@ -1091,30 +1111,44 @@ def get_running_tasks_api():
@admin_required
def get_task_logs_api():
"""获取任务日志列表(支持分页和多种筛选)"""
limit = int(request.args.get("limit", 20))
offset = int(request.args.get("offset", 0))
try:
limit = int(request.args.get("limit", 20))
limit = max(1, min(limit, 200)) # 限制 1-200 条
except (ValueError, TypeError):
limit = 20
try:
offset = int(request.args.get("offset", 0))
offset = max(0, offset)
except (ValueError, TypeError):
offset = 0
date_filter = request.args.get("date")
status_filter = request.args.get("status")
source_filter = request.args.get("source")
user_id_filter = request.args.get("user_id")
account_filter = request.args.get("account")
account_filter = (request.args.get("account") or "").strip()
if user_id_filter:
try:
user_id_filter = int(user_id_filter)
except ValueError:
except (ValueError, TypeError):
user_id_filter = None
result = database.get_task_logs(
limit=limit,
offset=offset,
date_filter=date_filter,
status_filter=status_filter,
source_filter=source_filter,
user_id_filter=user_id_filter,
account_filter=account_filter,
)
return jsonify(result)
try:
result = database.get_task_logs(
limit=limit,
offset=offset,
date_filter=date_filter,
status_filter=status_filter,
source_filter=source_filter,
user_id_filter=user_id_filter,
account_filter=account_filter if account_filter else None,
)
return jsonify(result)
except Exception as e:
logger.error(f"获取任务日志失败: {e}")
return jsonify({"logs": [], "total": 0, "error": "查询失败"})
@admin_api_bp.route("/task/logs/clear", methods=["POST"])