Compare commits

...

4 Commits

Author SHA1 Message Date
yyhuni
cd54089c34 fix: 前端日志显示排序问题 2025-12-17 17:35:08 +08:00
yyhuni
8fcda537a3 优化,预拉取docker镜像 2025-12-17 17:14:50 +08:00
github-actions[bot]
3ca94be7b7 chore: bump version to v1.0.4 2025-12-17 08:50:19 +00:00
yyhuni
eb70692843 更新注释 2025-12-17 16:40:07 +08:00
7 changed files with 108 additions and 43 deletions

View File

@@ -1 +1 @@
v1.0.3
v1.0.4

View File

@@ -0,0 +1,10 @@
"""
通用服务模块
提供系统级别的公共服务,包括:
- SystemLogService: 系统日志读取服务
"""
from .system_log_service import SystemLogService
__all__ = ['SystemLogService']

View File

@@ -1,24 +1,43 @@
import glob
import json
"""
系统日志服务模块
提供系统日志的读取功能,支持:
- 从日志目录读取日志文件
- 限制返回行数,防止内存溢出
"""
import logging
import subprocess
from datetime import datetime
logger = logging.getLogger(__name__)
class SystemLogService:
"""
系统日志服务类
负责读取系统日志文件,支持从容器内路径或宿主机挂载路径读取日志。
"""
def __init__(self):
self.log_globs = [
"/app/backend/logs/*",
"/opt/xingrin/logs/*",
]
self.default_lines = 200
self.max_lines = 10000
self.timeout_seconds = 3
# 日志文件路径(容器内路径,通过 volume 挂载到宿主机 /opt/xingrin/logs
self.log_file = "/app/backend/logs/xingrin.log"
self.default_lines = 200 # 默认返回行数
self.max_lines = 10000 # 最大返回行数限制
self.timeout_seconds = 3 # tail 命令超时时间
def get_logs_content(self, lines: int | None = None) -> str:
"""
获取系统日志内容
Args:
lines: 返回的日志行数,默认 200 行,最大 10000 行
Returns:
str: 日志内容,每行以换行符分隔,保持原始顺序
"""
# 参数校验和默认值处理
if lines is None:
lines = self.default_lines
@@ -28,16 +47,8 @@ class SystemLogService:
if lines > self.max_lines:
lines = self.max_lines
files: list[str] = []
for pattern in self.log_globs:
matched = sorted(glob.glob(pattern))
if matched:
files = matched
break
if not files:
return ""
cmd = ["tail", "-q", "-n", str(lines), *files]
# 使用 tail 命令读取日志文件末尾内容
cmd = ["tail", "-n", str(lines), self.log_file]
result = subprocess.run(
cmd,
@@ -54,25 +65,5 @@ class SystemLogService:
(result.stderr or "").strip(),
)
raw = result.stdout or ""
raw_lines = [ln for ln in raw.splitlines() if ln.strip()]
parsed: list[tuple[datetime | None, int, str]] = []
for idx, line in enumerate(raw_lines):
ts: datetime | None = None
if line.startswith("{") and line.endswith("}"):
try:
obj = json.loads(line)
asctime = obj.get("asctime")
if isinstance(asctime, str):
ts = datetime.strptime(asctime, "%Y-%m-%d %H:%M:%S")
except Exception:
ts = None
parsed.append((ts, idx, line))
parsed.sort(key=lambda x: (x[0] is None, x[0] or datetime.min, x[1]))
sorted_lines = [x[2] for x in parsed]
if len(sorted_lines) > lines:
sorted_lines = sorted_lines[-lines:]
return "\n".join(sorted_lines) + ("\n" if sorted_lines else "")
# 直接返回原始内容,保持文件中的顺序
return result.stdout or ""

View File

@@ -1,13 +1,21 @@
"""
通用模块 URL 配置
路由说明:
- /api/auth/* 认证相关接口(登录、登出、用户信息)
- /api/system/* 系统管理接口(日志查看等)
"""
from django.urls import path
from .views import LoginView, LogoutView, MeView, ChangePasswordView, SystemLogsView
urlpatterns = [
# 认证相关
path('auth/login/', LoginView.as_view(), name='auth-login'),
path('auth/logout/', LogoutView.as_view(), name='auth-logout'),
path('auth/me/', MeView.as_view(), name='auth-me'),
path('auth/change-password/', ChangePasswordView.as_view(), name='auth-change-password'),
# 系统管理
path('system/logs/', SystemLogsView.as_view(), name='system-logs'),
]

View File

@@ -1,3 +1,11 @@
"""
通用模块视图导出
包含:
- 认证相关视图:登录、登出、用户信息、修改密码
- 系统日志视图:实时日志查看
"""
from .auth_views import LoginView, LogoutView, MeView, ChangePasswordView
from .system_log_views import SystemLogsView

View File

@@ -1,3 +1,9 @@
"""
系统日志视图模块
提供系统日志的 REST API 接口,供前端实时查看系统运行日志。
"""
import logging
from django.utils.decorators import method_decorator
@@ -15,6 +21,26 @@ logger = logging.getLogger(__name__)
@method_decorator(csrf_exempt, name="dispatch")
class SystemLogsView(APIView):
"""
系统日志 API 视图
GET /api/system/logs/
获取系统日志内容
Query Parameters:
lines (int, optional): 返回的日志行数,默认 200最大 10000
Response:
{
"content": "日志内容字符串..."
}
Note:
- 当前为开发阶段,暂时允许匿名访问
- 生产环境应添加管理员权限验证
"""
# TODO: 生产环境应改为 IsAdminUser 权限
authentication_classes = []
permission_classes = [AllowAny]
@@ -23,10 +49,17 @@ class SystemLogsView(APIView):
self.service = SystemLogService()
def get(self, request):
"""
获取系统日志
支持通过 lines 参数控制返回行数,用于前端分页或实时刷新场景。
"""
try:
# 解析 lines 参数
lines_raw = request.query_params.get("lines")
lines = int(lines_raw) if lines_raw is not None else None
# 调用服务获取日志内容
content = self.service.get_logs_content(lines=lines)
return Response({"content": content})
except ValueError:

View File

@@ -388,6 +388,21 @@ fi
# 准备 HTTPS 证书(无域名也可使用自签)
generate_self_signed_cert
# ==============================================================================
# 预拉取 Worker 镜像(避免扫描时等待)
# ==============================================================================
step "预拉取 Worker 镜像..."
DOCKER_USER=$(grep "^DOCKER_USER=" "$DOCKER_DIR/.env" 2>/dev/null | cut -d= -f2)
DOCKER_USER=${DOCKER_USER:-yyhuni}
WORKER_IMAGE="${DOCKER_USER}/xingrin-worker:${APP_VERSION}"
info "正在拉取: $WORKER_IMAGE"
if docker pull "$WORKER_IMAGE"; then
success "Worker 镜像拉取完成"
else
warn "Worker 镜像拉取失败,扫描时会自动重试拉取"
fi
# ==============================================================================
# 启动服务
# ==============================================================================