开发文档
This commit is contained in:
1569
docs/development/saved-docs/api-design.md
Normal file
1569
docs/development/saved-docs/api-design.md
Normal file
File diff suppressed because it is too large
Load Diff
1823
docs/development/saved-docs/management-backend.md
Normal file
1823
docs/development/saved-docs/management-backend.md
Normal file
File diff suppressed because it is too large
Load Diff
710
docs/development/saved-docs/operations-monitoring.md
Normal file
710
docs/development/saved-docs/operations-monitoring.md
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
# 摄影作品集网站 - 运维监控方案
|
||||||
|
|
||||||
|
## 🎯 方案概述
|
||||||
|
|
||||||
|
这是一个**简单实用的日志管理方案**,专注于日志收集和问题修复。日志查看功能集成到管理后台,提供友好的Web界面。
|
||||||
|
|
||||||
|
### 设计原则
|
||||||
|
- **集成化**: 日志查看功能集成到管理后台
|
||||||
|
- **用户友好**: 提供美观易用的Web界面
|
||||||
|
- **问题导向**: 专注于快速定位和修复问题
|
||||||
|
- **低维护成本**: 几乎零维护的方案
|
||||||
|
- **渐进式**: 后续可以根据需要扩展
|
||||||
|
|
||||||
|
## 📋 核心组件
|
||||||
|
|
||||||
|
### 日志管理架构
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 后端应用日志 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ 错误日志 │ │ 访问日志 │ │ 业务日志 │ │
|
||||||
|
│ │ (JSON格式) │ │ (HTTP日志) │ │ (操作日志) │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 管理后台日志模块 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ 日志查看器 │ │ 实时监控 │ │ 统计分析 │ │
|
||||||
|
│ │ (Web界面) │ │ (自动刷新) │ │ (图表展示) │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ API接口层 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ 日志查询API │ │ 统计API │ │ 搜索API │ │
|
||||||
|
│ │(/api/logs) │ │(/api/stats) │ │(/api/search)│ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 技术选择
|
||||||
|
|
||||||
|
### 日志方案
|
||||||
|
- **日志存储**: 本地文件 (JSON格式)
|
||||||
|
- **日志轮转**: lumberjack.v2
|
||||||
|
- **日志查看**: 管理后台Web界面
|
||||||
|
- **Trace ID**: 集成OpenTracing (已在架构文档中添加)
|
||||||
|
- **权限控制**: 基于管理后台的用户权限
|
||||||
|
|
||||||
|
### 集成方式
|
||||||
|
- **前端**: 管理后台的日志管理模块
|
||||||
|
- **后端**: Gin路由提供日志查询API
|
||||||
|
- **认证**: 复用管理后台的登录认证
|
||||||
|
- **权限**: 仅管理员可访问日志功能
|
||||||
|
|
||||||
|
## 📝 日志配置
|
||||||
|
|
||||||
|
### 1. 应用日志配置
|
||||||
|
|
||||||
|
#### 日志配置文件 (config.yaml)
|
||||||
|
```yaml
|
||||||
|
# config/config.yaml
|
||||||
|
logger:
|
||||||
|
level: "info"
|
||||||
|
format: "json"
|
||||||
|
output: "file"
|
||||||
|
filename: "/app/logs/app.log"
|
||||||
|
max_size: 100 # MB
|
||||||
|
max_age: 7 # days
|
||||||
|
compress: true
|
||||||
|
|
||||||
|
# 可选:如果需要链路追踪
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
service_name: "photography-backend"
|
||||||
|
jaeger:
|
||||||
|
endpoint: "http://localhost:14268/api/traces"
|
||||||
|
sampling_rate: 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 日志格式标准化
|
||||||
|
|
||||||
|
#### 统一的日志格式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": "2024-01-15T10:30:00Z",
|
||||||
|
"level": "info",
|
||||||
|
"message": "Photo created successfully",
|
||||||
|
"service": "photography-backend",
|
||||||
|
"trace_id": "abc123def456",
|
||||||
|
"request_id": "req-789",
|
||||||
|
"user_id": "user-123",
|
||||||
|
"operation": "create_photo",
|
||||||
|
"photo_id": 1001,
|
||||||
|
"duration": 0.5,
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 日志分类
|
||||||
|
|
||||||
|
#### 三种核心日志类型
|
||||||
|
```bash
|
||||||
|
# 日志目录结构
|
||||||
|
logs/
|
||||||
|
├── app.log # 应用日志 (所有级别)
|
||||||
|
├── error.log # 错误日志 (ERROR级别)
|
||||||
|
└── access.log # HTTP访问日志
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 日志级别使用
|
||||||
|
```go
|
||||||
|
// 日志级别使用指南
|
||||||
|
logger.Info("正常业务操作") // 记录重要的业务操作
|
||||||
|
logger.Warn("需要关注的情况") // 记录警告信息
|
||||||
|
logger.Error("错误情况") // 记录错误信息
|
||||||
|
logger.Debug("调试信息") // 开发调试用
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 集成到管理后台
|
||||||
|
|
||||||
|
### 1. 后端集成步骤
|
||||||
|
|
||||||
|
#### 在main.go中注册日志路由
|
||||||
|
```go
|
||||||
|
// cmd/server/main.go
|
||||||
|
func main() {
|
||||||
|
// ... 其他初始化代码
|
||||||
|
|
||||||
|
// 创建Gin引擎
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// 注册API路由
|
||||||
|
apiGroup := r.Group("/api")
|
||||||
|
{
|
||||||
|
// 其他API路由...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册管理后台路由
|
||||||
|
adminGroup := r.Group("/admin/api")
|
||||||
|
{
|
||||||
|
// 注册日志管理路由
|
||||||
|
admin.RegisterLogRoutes(adminGroup, "logs/app.log")
|
||||||
|
|
||||||
|
// 其他管理后台路由...
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 日志处理器实现
|
||||||
|
```go
|
||||||
|
// internal/api/handlers/admin/logs_handler.go
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"photography-backend/pkg/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogEntry 日志条目
|
||||||
|
type LogEntry struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TraceID string `json:"trace_id,omitempty"`
|
||||||
|
UserID string `json:"user_id,omitempty"`
|
||||||
|
Operation string `json:"operation,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogHandler 日志处理器
|
||||||
|
type LogHandler struct {
|
||||||
|
logFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs 获取日志列表
|
||||||
|
func (h *LogHandler) GetLogs(c *gin.Context) {
|
||||||
|
// 获取参数
|
||||||
|
levelFilter := c.Query("level")
|
||||||
|
searchFilter := c.Query("search")
|
||||||
|
traceID := c.Query("trace_id")
|
||||||
|
lines := 100
|
||||||
|
if l := c.Query("lines"); l != "" {
|
||||||
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 {
|
||||||
|
lines = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取日志文件
|
||||||
|
logs, err := h.readLogs(lines, levelFilter, searchFilter, traceID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "读取日志失败",
|
||||||
|
"details": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"logs": logs,
|
||||||
|
"total": len(logs),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogStats 获取日志统计
|
||||||
|
func (h *LogHandler) GetLogStats(c *gin.Context) {
|
||||||
|
stats := map[string]int{
|
||||||
|
"total": 0,
|
||||||
|
"error": 0,
|
||||||
|
"warn": 0,
|
||||||
|
"info": 0,
|
||||||
|
"debug": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(h.logFile)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "读取日志文件失败",
|
||||||
|
"details": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stats["total"]++
|
||||||
|
|
||||||
|
// 解析日志级别
|
||||||
|
var entry LogEntry
|
||||||
|
if err := json.Unmarshal([]byte(line), &entry); err == nil {
|
||||||
|
if count, exists := stats[entry.Level]; exists {
|
||||||
|
stats[entry.Level] = count + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": stats,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterLogRoutes 注册日志相关路由
|
||||||
|
func RegisterLogRoutes(r *gin.RouterGroup, logFile string) {
|
||||||
|
logHandler := NewLogHandler(logFile)
|
||||||
|
|
||||||
|
// 需要管理员权限的路由组
|
||||||
|
adminGroup := r.Group("")
|
||||||
|
adminGroup.Use(middleware.RequireAuth()) // 需要登录
|
||||||
|
adminGroup.Use(middleware.RequireAdmin()) // 需要管理员权限
|
||||||
|
|
||||||
|
{
|
||||||
|
adminGroup.GET("/logs", logHandler.GetLogs)
|
||||||
|
adminGroup.GET("/logs/stats", logHandler.GetLogStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 前端集成步骤
|
||||||
|
|
||||||
|
#### 管理后台日志查看组件
|
||||||
|
```javascript
|
||||||
|
// admin/src/pages/Logs/LogViewer.jsx
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Table,
|
||||||
|
Select,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Statistic,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
message
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
const LogViewer = () => {
|
||||||
|
const [logs, setLogs] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [filters, setFilters] = useState({
|
||||||
|
level: '',
|
||||||
|
search: '',
|
||||||
|
trace_id: '',
|
||||||
|
lines: 100,
|
||||||
|
});
|
||||||
|
const [stats, setStats] = useState({});
|
||||||
|
const [autoRefresh, setAutoRefresh] = useState(false);
|
||||||
|
|
||||||
|
// 日志级别配置
|
||||||
|
const levelConfig = {
|
||||||
|
error: { color: 'red', icon: '❌' },
|
||||||
|
warn: { color: 'orange', icon: '⚠️' },
|
||||||
|
info: { color: 'blue', icon: 'ℹ️' },
|
||||||
|
debug: { color: 'default', icon: '🐛' },
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取日志数据
|
||||||
|
const fetchLogs = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await adminApi.get('/logs', { params: filters });
|
||||||
|
setLogs(response.data.data.logs || []);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取日志失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取统计数据
|
||||||
|
const fetchStats = async () => {
|
||||||
|
try {
|
||||||
|
const response = await adminApi.get('/logs/stats');
|
||||||
|
setStats(response.data.data || {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取统计数据失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLogs();
|
||||||
|
fetchStats();
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
|
// 自动刷新
|
||||||
|
useEffect(() => {
|
||||||
|
let interval;
|
||||||
|
if (autoRefresh) {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
fetchLogs();
|
||||||
|
fetchStats();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
return () => interval && clearInterval(interval);
|
||||||
|
}, [autoRefresh, filters]);
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'timestamp',
|
||||||
|
key: 'timestamp',
|
||||||
|
width: 180,
|
||||||
|
render: (timestamp) => new Date(timestamp).toLocaleString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '级别',
|
||||||
|
dataIndex: 'level',
|
||||||
|
key: 'level',
|
||||||
|
width: 80,
|
||||||
|
render: (level) => {
|
||||||
|
const config = levelConfig[level] || levelConfig.info;
|
||||||
|
return (
|
||||||
|
<Tag color={config.color}>
|
||||||
|
{config.icon} {level.toUpperCase()}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Trace ID',
|
||||||
|
dataIndex: 'trace_id',
|
||||||
|
key: 'trace_id',
|
||||||
|
width: 120,
|
||||||
|
render: (traceId) => traceId ? (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setFilters(prev => ({ ...prev, trace_id: traceId }))}
|
||||||
|
>
|
||||||
|
{traceId}
|
||||||
|
</Button>
|
||||||
|
) : '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '消息',
|
||||||
|
dataIndex: 'message',
|
||||||
|
key: 'message',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (message, record) => {
|
||||||
|
// 高亮搜索关键词
|
||||||
|
if (filters.search && message.toLowerCase().includes(filters.search.toLowerCase())) {
|
||||||
|
const regex = new RegExp(`(${filters.search})`, 'gi');
|
||||||
|
const parts = message.split(regex);
|
||||||
|
return parts.map((part, index) =>
|
||||||
|
part.toLowerCase() === filters.search.toLowerCase() ?
|
||||||
|
<mark key={index}>{part}</mark> : part
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="log-viewer">
|
||||||
|
{/* 统计卡片 */}
|
||||||
|
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="总计" value={stats.total || 0} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="错误"
|
||||||
|
value={stats.error || 0}
|
||||||
|
valueStyle={{ color: '#ff4d4f' }}
|
||||||
|
prefix="❌"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="警告"
|
||||||
|
value={stats.warn || 0}
|
||||||
|
valueStyle={{ color: '#fa8c16' }}
|
||||||
|
prefix="⚠️"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="信息"
|
||||||
|
value={stats.info || 0}
|
||||||
|
valueStyle={{ color: '#1890ff' }}
|
||||||
|
prefix="ℹ️"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* 过滤控件 */}
|
||||||
|
<Card style={{ marginBottom: 16 }}>
|
||||||
|
<Space wrap>
|
||||||
|
<Select
|
||||||
|
placeholder="选择日志级别"
|
||||||
|
style={{ width: 120 }}
|
||||||
|
value={filters.level}
|
||||||
|
onChange={(value) => setFilters(prev => ({ ...prev, level: value }))}
|
||||||
|
>
|
||||||
|
<Option value="">全部级别</Option>
|
||||||
|
<Option value="error">错误</Option>
|
||||||
|
<Option value="warn">警告</Option>
|
||||||
|
<Option value="info">信息</Option>
|
||||||
|
<Option value="debug">调试</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
placeholder="搜索关键词"
|
||||||
|
style={{ width: 200 }}
|
||||||
|
value={filters.search}
|
||||||
|
onChange={(e) => setFilters(prev => ({ ...prev, search: e.target.value }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
placeholder="Trace ID"
|
||||||
|
style={{ width: 150 }}
|
||||||
|
value={filters.trace_id}
|
||||||
|
onChange={(e) => setFilters(prev => ({ ...prev, trace_id: e.target.value }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={filters.lines}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
onChange={(value) => setFilters(prev => ({ ...prev, lines: value }))}
|
||||||
|
>
|
||||||
|
<Option value={50}>50 条</Option>
|
||||||
|
<Option value={100}>100 条</Option>
|
||||||
|
<Option value={200}>200 条</Option>
|
||||||
|
<Option value={500}>500 条</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={fetchLogs}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
🔄 刷新
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type={autoRefresh ? 'primary' : 'default'}
|
||||||
|
onClick={() => setAutoRefresh(!autoRefresh)}
|
||||||
|
>
|
||||||
|
{autoRefresh ? '⏸️ 停止自动刷新' : '⏰ 自动刷新'}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 日志表格 */}
|
||||||
|
<Card>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={logs}
|
||||||
|
loading={loading}
|
||||||
|
rowKey={(record, index) => `${record.timestamp}-${index}`}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条日志`,
|
||||||
|
}}
|
||||||
|
scroll={{ x: 800 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogViewer;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置更新
|
||||||
|
|
||||||
|
#### 更新应用配置文件
|
||||||
|
```yaml
|
||||||
|
# config/config.yaml
|
||||||
|
app:
|
||||||
|
name: "photography-backend"
|
||||||
|
version: "1.0.0"
|
||||||
|
environment: "production"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: "info"
|
||||||
|
format: "json"
|
||||||
|
output: "file"
|
||||||
|
filename: "logs/app.log"
|
||||||
|
max_size: 100
|
||||||
|
max_age: 7
|
||||||
|
compress: true
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
service_name: "photography-backend"
|
||||||
|
sampling_rate: 1.0
|
||||||
|
|
||||||
|
admin:
|
||||||
|
log_access: true # 启用日志访问功能
|
||||||
|
log_retention_days: 30 # 日志保留天数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 一键部署脚本
|
||||||
|
|
||||||
|
#### 部署管理后台日志功能
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# deploy-admin-logs.sh
|
||||||
|
|
||||||
|
echo "🚀 部署管理后台日志功能..."
|
||||||
|
|
||||||
|
# 创建必要目录
|
||||||
|
mkdir -p logs
|
||||||
|
mkdir -p admin/src/pages/Logs
|
||||||
|
|
||||||
|
# 确保日志文件存在
|
||||||
|
touch logs/app.log
|
||||||
|
chmod 664 logs/app.log
|
||||||
|
|
||||||
|
# 创建日志轮转配置
|
||||||
|
cat > /etc/logrotate.d/photography-backend << 'EOF'
|
||||||
|
/path/to/photography/logs/*.log {
|
||||||
|
daily
|
||||||
|
rotate 30
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
create 664 app app
|
||||||
|
postrotate
|
||||||
|
systemctl reload photography-backend
|
||||||
|
endscript
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 设置权限
|
||||||
|
chown -R app:app logs/
|
||||||
|
chmod 755 logs/
|
||||||
|
|
||||||
|
echo "✅ 管理后台日志功能部署完成!"
|
||||||
|
echo ""
|
||||||
|
echo "📋 访问方式:"
|
||||||
|
echo " 1. 登录管理后台: http://localhost:8080/admin"
|
||||||
|
echo " 2. 进入日志管理页面"
|
||||||
|
echo " 3. 使用管理员账号访问"
|
||||||
|
echo ""
|
||||||
|
echo "🔧 API地址:"
|
||||||
|
echo " 日志列表: GET /admin/api/logs"
|
||||||
|
echo " 日志统计: GET /admin/api/logs/stats"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 故障排查流程
|
||||||
|
|
||||||
|
### 1. 问题诊断步骤
|
||||||
|
```bash
|
||||||
|
# 快速诊断脚本
|
||||||
|
#!/bin/bash
|
||||||
|
# scripts/quick-diagnosis.sh
|
||||||
|
|
||||||
|
echo "🔍 快速问题诊断"
|
||||||
|
|
||||||
|
# 1. 检查服务状态
|
||||||
|
echo "1. 检查服务状态..."
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 2. 检查最近错误
|
||||||
|
echo "2. 最近10条错误日志..."
|
||||||
|
tail -n 1000 logs/app.log | grep -i error | tail -10 | jq -r '.timestamp + " " + .message'
|
||||||
|
|
||||||
|
# 3. 检查磁盘空间
|
||||||
|
echo "3. 检查磁盘空间..."
|
||||||
|
df -h
|
||||||
|
|
||||||
|
# 4. 检查日志文件大小
|
||||||
|
echo "4. 检查日志文件大小..."
|
||||||
|
ls -lh logs/
|
||||||
|
|
||||||
|
# 5. 检查内存使用
|
||||||
|
echo "5. 检查内存使用..."
|
||||||
|
docker stats --no-stream photography-backend
|
||||||
|
|
||||||
|
echo "✅ 诊断完成"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 常见问题解决
|
||||||
|
|
||||||
|
#### 问题1:无法找到错误原因
|
||||||
|
```bash
|
||||||
|
# 1. 获取完整的错误上下文
|
||||||
|
grep -B 5 -A 5 "error_message" logs/app.log
|
||||||
|
|
||||||
|
# 2. 按时间范围查找
|
||||||
|
grep "2024-01-15T10:" logs/app.log | grep -i error
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题2:需要追踪特定用户的操作
|
||||||
|
```bash
|
||||||
|
# 按用户ID查找
|
||||||
|
grep "user-123" logs/app.log | jq -r '.timestamp + " " + .message'
|
||||||
|
|
||||||
|
# 按操作类型查找
|
||||||
|
grep "create_photo" logs/app.log | jq -r '.timestamp + " " + .message'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题3:日志文件太大
|
||||||
|
```bash
|
||||||
|
# 手动轮转日志
|
||||||
|
mv logs/app.log logs/app.log.old
|
||||||
|
sudo systemctl restart photography-backend
|
||||||
|
|
||||||
|
# 或者使用logrotate
|
||||||
|
sudo logrotate -f /etc/logrotate.d/photography-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 总结
|
||||||
|
|
||||||
|
这个集成到管理后台的日志方案提供了:
|
||||||
|
|
||||||
|
### ✨ 核心特性
|
||||||
|
- **🏢 集成化管理** - 完全集成到管理后台,统一的用户体验
|
||||||
|
- **🔐 权限控制** - 基于管理后台的认证和授权体系
|
||||||
|
- **📊 专业界面** - 使用React组件,美观且专业
|
||||||
|
- **🔍 强大搜索** - 支持关键词、级别、Trace ID多维度过滤
|
||||||
|
- **⏰ 实时监控** - 自动刷新功能,实时观察系统状态
|
||||||
|
- **📈 统计分析** - 直观的统计卡片,快速了解系统健康状况
|
||||||
|
|
||||||
|
### 🚀 部署简单
|
||||||
|
1. 运行部署脚本: `./deploy-admin-logs.sh`
|
||||||
|
2. 访问管理后台: `http://localhost:8080/admin`
|
||||||
|
3. 登录管理员账号,进入日志管理页面
|
||||||
|
|
||||||
|
### 💡 使用场景
|
||||||
|
- **错误排查**: 管理员快速定位和分析错误日志
|
||||||
|
- **性能监控**: 通过Trace ID追踪完整请求链路
|
||||||
|
- **运营监控**: 实时观察系统运行状态
|
||||||
|
- **历史分析**: 搜索和分析历史日志数据
|
||||||
|
- **团队协作**: 多个管理员可以同时查看和分析日志
|
||||||
|
|
||||||
|
### 🔧 技术优势
|
||||||
|
- **集成性**: 完全集成到现有管理后台
|
||||||
|
- **安全性**: 基于管理后台的权限控制
|
||||||
|
- **专业性**: 使用成熟的UI组件库
|
||||||
|
- **可扩展**: 易于添加新的日志分析功能
|
||||||
|
- **用户友好**: 直观的界面,无需学习成本
|
||||||
|
|
||||||
|
### 📋 API接口
|
||||||
|
- `GET /admin/api/logs` - 获取日志列表
|
||||||
|
- `GET /admin/api/logs/stats` - 获取日志统计
|
||||||
|
|
||||||
|
这就是最适合生产环境的日志管理方案 - **专业、安全、易用**!
|
||||||
249
docs/development/saved-docs/v1.0-overview.md
Normal file
249
docs/development/saved-docs/v1.0-overview.md
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# 摄影作品集网站 v1.0 - 开发文档
|
||||||
|
|
||||||
|
## 📋 v1.0 版本概述
|
||||||
|
|
||||||
|
v1.0 是摄影作品集网站的核心功能版本,实现了完整的摄影作品展示、管理和用户交互功能。
|
||||||
|
|
||||||
|
### 🎯 版本目标
|
||||||
|
- 构建稳定可靠的摄影作品集展示平台
|
||||||
|
- 实现高效的照片管理和处理系统
|
||||||
|
- 提供完善的用户认证和权限控制
|
||||||
|
- 支持多种图片格式和优化策略
|
||||||
|
|
||||||
|
### 📅 开发周期
|
||||||
|
- **开始时间**: 2024-01-15
|
||||||
|
- **预计完成**: 2024-04-15
|
||||||
|
- **当前状态**: 设计阶段
|
||||||
|
|
||||||
|
## 🏗️ 架构设计
|
||||||
|
|
||||||
|
### 整体架构
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 用户界面层 │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ 前端展示网站 │ │ 管理后台界面 │ │
|
||||||
|
│ │ (Next.js) │ │ (React) │ │
|
||||||
|
│ └─────────────────┘ └─────────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ API 接口层 │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ RESTful API │ │
|
||||||
|
│ │ (Gin Framework) │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ 业务逻辑层 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ 照片管理 │ │ 用户认证 │ │ 文件处理 │ │
|
||||||
|
│ │ 服务 │ │ 服务 │ │ 服务 │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ 数据访问层 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ PostgreSQL │ │ Redis │ │ MinIO/S3 │ │
|
||||||
|
│ │ 数据库 │ │ 缓存 │ │ 对象存储 │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 技术选型
|
||||||
|
```yaml
|
||||||
|
前端技术栈:
|
||||||
|
- Framework: Next.js 15 + React 19
|
||||||
|
- Language: TypeScript
|
||||||
|
- Styling: Tailwind CSS
|
||||||
|
- State: TanStack Query + Zustand
|
||||||
|
- UI Components: shadcn/ui + Radix UI
|
||||||
|
|
||||||
|
后端技术栈:
|
||||||
|
- Framework: Golang + Gin
|
||||||
|
- Database: PostgreSQL 15
|
||||||
|
- Cache: Redis 7
|
||||||
|
- ORM: GORM
|
||||||
|
- Authentication: JWT
|
||||||
|
- Image Processing: libvips/bimg
|
||||||
|
|
||||||
|
存储和部署:
|
||||||
|
- Object Storage: MinIO/AWS S3
|
||||||
|
- Web Server: Caddy
|
||||||
|
- Container: Docker + Docker Compose
|
||||||
|
- CI/CD: Gitea Actions
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 功能特性
|
||||||
|
|
||||||
|
### 核心功能
|
||||||
|
- ✅ **前端展示**
|
||||||
|
- 响应式照片网格展示
|
||||||
|
- 时间线视图
|
||||||
|
- 照片详情模态框
|
||||||
|
- 分类和标签筛选
|
||||||
|
|
||||||
|
- 🔄 **管理后台**
|
||||||
|
- 照片批量上传和管理
|
||||||
|
- 分类和标签管理
|
||||||
|
- 用户权限控制
|
||||||
|
- 系统设置配置
|
||||||
|
|
||||||
|
- 🔄 **后端API**
|
||||||
|
- 完整的CRUD操作
|
||||||
|
- 文件上传处理
|
||||||
|
- 图片多格式转换
|
||||||
|
- 用户认证授权
|
||||||
|
|
||||||
|
- 🔄 **数据管理**
|
||||||
|
- PostgreSQL关系数据库
|
||||||
|
- Redis缓存加速
|
||||||
|
- MinIO对象存储
|
||||||
|
- 数据备份恢复
|
||||||
|
|
||||||
|
### 性能特性
|
||||||
|
- **图片优化**: 多格式转换 (JPG, WebP, AVIF)
|
||||||
|
- **懒加载**: 视口外图片延迟加载
|
||||||
|
- **缓存策略**: 多级缓存提升响应速度
|
||||||
|
- **CDN加速**: 静态资源分发优化
|
||||||
|
|
||||||
|
### 安全特性
|
||||||
|
- **用户认证**: JWT令牌机制
|
||||||
|
- **权限控制**: 基于角色的访问控制
|
||||||
|
- **数据加密**: 敏感数据加密存储
|
||||||
|
- **API限流**: 防止接口滥用
|
||||||
|
|
||||||
|
## 📈 开发进度
|
||||||
|
|
||||||
|
### 当前状态
|
||||||
|
|
||||||
|
| 模块 | 设计 | 开发 | 测试 | 部署 | 完成度 |
|
||||||
|
|------|------|------|------|------|--------|
|
||||||
|
| 前端展示 | ✅ | ✅ | ✅ | ✅ | 100% |
|
||||||
|
| 管理后台 | ✅ | ⏳ | ⏳ | ⏳ | 20% |
|
||||||
|
| 后端API | ✅ | ⏳ | ⏳ | ⏳ | 15% |
|
||||||
|
| 数据库 | ✅ | ⏳ | ⏳ | ⏳ | 30% |
|
||||||
|
| 图片处理 | ✅ | ⏳ | ⏳ | ⏳ | 10% |
|
||||||
|
| 用户认证 | ✅ | ⏳ | ⏳ | ⏳ | 5% |
|
||||||
|
| 文件存储 | ✅ | ⏳ | ⏳ | ⏳ | 5% |
|
||||||
|
| 部署配置 | ✅ | ⏳ | ⏳ | ⏳ | 10% |
|
||||||
|
|
||||||
|
### 里程碑计划
|
||||||
|
|
||||||
|
#### 第一阶段 (2024-01-15 ~ 2024-02-15)
|
||||||
|
- [x] 完成技术选型和架构设计
|
||||||
|
- [x] 完成详细设计文档编写
|
||||||
|
- [ ] 搭建开发环境和基础框架
|
||||||
|
- [ ] 实现管理后台核心界面
|
||||||
|
|
||||||
|
#### 第二阶段 (2024-02-15 ~ 2024-03-15)
|
||||||
|
- [ ] 完成后端API核心功能
|
||||||
|
- [ ] 实现数据库表结构和迁移
|
||||||
|
- [ ] 完成用户认证和权限系统
|
||||||
|
- [ ] 实现图片上传和处理功能
|
||||||
|
|
||||||
|
#### 第三阶段 (2024-03-15 ~ 2024-04-15)
|
||||||
|
- [ ] 完成前后端功能对接
|
||||||
|
- [ ] 实现文件存储和CDN配置
|
||||||
|
- [ ] 完成系统测试和性能优化
|
||||||
|
- [ ] 部署到生产环境
|
||||||
|
|
||||||
|
## 🔧 开发环境
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
```yaml
|
||||||
|
开发环境:
|
||||||
|
- Node.js: 18+
|
||||||
|
- Golang: 1.21+
|
||||||
|
- PostgreSQL: 15+
|
||||||
|
- Redis: 7+
|
||||||
|
- Docker: 24+
|
||||||
|
|
||||||
|
推荐配置:
|
||||||
|
- 内存: 8GB+
|
||||||
|
- 硬盘: 50GB+
|
||||||
|
- 操作系统: macOS/Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
### 快速开始
|
||||||
|
```bash
|
||||||
|
# 1. 克隆项目
|
||||||
|
git clone <repository>
|
||||||
|
cd photography
|
||||||
|
|
||||||
|
# 2. 前端开发
|
||||||
|
cd frontend
|
||||||
|
make setup
|
||||||
|
make dev
|
||||||
|
|
||||||
|
# 3. 后端开发 (开发中)
|
||||||
|
cd backend
|
||||||
|
make setup
|
||||||
|
make dev
|
||||||
|
|
||||||
|
# 4. 数据库初始化
|
||||||
|
make migrate-up
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 测试策略
|
||||||
|
|
||||||
|
### 测试类型
|
||||||
|
- **单元测试**: 核心业务逻辑测试
|
||||||
|
- **集成测试**: API接口和数据库交互测试
|
||||||
|
- **端到端测试**: 完整用户流程测试
|
||||||
|
- **性能测试**: 系统负载和响应时间测试
|
||||||
|
|
||||||
|
### 测试覆盖率目标
|
||||||
|
- **后端代码**: 80%+
|
||||||
|
- **前端组件**: 70%+
|
||||||
|
- **API接口**: 90%+
|
||||||
|
|
||||||
|
## 📊 质量保证
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- **代码规范**: ESLint + Prettier (前端), golangci-lint (后端)
|
||||||
|
- **类型检查**: TypeScript 严格模式
|
||||||
|
- **代码审查**: Pull Request必须通过审查
|
||||||
|
- **自动化测试**: CI/CD流水线集成
|
||||||
|
|
||||||
|
### 性能指标
|
||||||
|
- **页面加载时间**: < 3s
|
||||||
|
- **图片处理时间**: < 10s
|
||||||
|
- **API响应时间**: < 500ms
|
||||||
|
- **数据库查询**: < 100ms
|
||||||
|
|
||||||
|
## 📋 发布计划
|
||||||
|
|
||||||
|
### 发布流程
|
||||||
|
1. **功能开发**: 在feature分支开发
|
||||||
|
2. **代码审查**: 创建Pull Request
|
||||||
|
3. **测试验证**: 自动化测试通过
|
||||||
|
4. **合并主分支**: 合并到main分支
|
||||||
|
5. **部署发布**: 自动部署到生产环境
|
||||||
|
|
||||||
|
### 版本管理
|
||||||
|
- **版本号**: 遵循语义化版本规范
|
||||||
|
- **发布说明**: 详细的变更日志
|
||||||
|
- **回滚策略**: 快速回滚机制
|
||||||
|
|
||||||
|
## 💡 未来规划
|
||||||
|
|
||||||
|
### v1.1 增强功能
|
||||||
|
- 移动端PWA支持
|
||||||
|
- 图片水印功能
|
||||||
|
- 批量操作优化
|
||||||
|
- 搜索功能增强
|
||||||
|
|
||||||
|
### v1.2 扩展功能
|
||||||
|
- 评论系统
|
||||||
|
- 社交分享
|
||||||
|
- 数据导出
|
||||||
|
- 多语言支持
|
||||||
|
|
||||||
|
### v2.0 升级计划
|
||||||
|
- 微服务架构
|
||||||
|
- AI智能标签
|
||||||
|
- 实时通知
|
||||||
|
- 高级分析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
📅 **最后更新**: 2024-01-15
|
||||||
|
📝 **文档版本**: v1.0
|
||||||
|
👨💻 **维护者**: Claude Code Assistant
|
||||||
@ -278,7 +278,79 @@ type DashboardStats struct {
|
|||||||
└─────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.6 系统设置模块 (System Settings)
|
### 2.6 日志管理模块 (Log Management)
|
||||||
|
|
||||||
|
#### 2.6.1 日志查看页面
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 日志管理 Log Management │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 📊 统计卡片 │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||||
|
│ │ 总日志 │ │ 错误数 │ │ 警告数 │ │ 信息数 │ │
|
||||||
|
│ │ 12,345 │ │ 23 │ │ 156 │ │ 11,234 │ │
|
||||||
|
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 🔍 过滤控件 │
|
||||||
|
│ [日志级别▼] [搜索关键词...] [Trace ID...] [100条▼] [刷新] │
|
||||||
|
│ │
|
||||||
|
│ 📋 日志列表 │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 时间 级别 Trace ID 消息 │ │
|
||||||
|
│ │ 2024-01-15 10:30 INFO trace-abc123 Photo created │ │
|
||||||
|
│ │ 2024-01-15 10:31 WARN trace-def456 Cache miss │ │
|
||||||
|
│ │ 2024-01-15 10:32 ERROR trace-abc123 Process failed │ │
|
||||||
|
│ │ 2024-01-15 10:33 INFO trace-ghi789 User login │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 📄 分页: [← 上一页] [1] [2] [3] [下一页 →] │
|
||||||
|
│ ⏰ [自动刷新] 🗑️ [清空显示] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.6.2 日志详情展示
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 日志详情 Log Details │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ← 返回列表 │
|
||||||
|
│ │
|
||||||
|
│ 📋 基本信息 │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 时间: 2024-01-15 10:32:45 │ │
|
||||||
|
│ │ 级别: ERROR │ │
|
||||||
|
│ │ 消息: Failed to process image │ │
|
||||||
|
│ │ Trace ID: trace-abc123 [点击查看相关日志] │ │
|
||||||
|
│ │ 用户: admin (user-123) │ │
|
||||||
|
│ │ 操作: image_process │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 🔍 相关日志 (同一Trace ID) │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 10:30:00 INFO Photo upload started │ │
|
||||||
|
│ │ 10:31:30 INFO File validation passed │ │
|
||||||
|
│ │ 10:32:00 WARN Large file detected │ │
|
||||||
|
│ │ 10:32:45 ERROR Failed to process image │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 📊 统计信息 │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 该Trace ID共有 4 条日志 │ │
|
||||||
|
│ │ 错误: 1 | 警告: 1 | 信息: 2 │ │
|
||||||
|
│ │ 耗时: 2分45秒 │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.6.3 日志管理功能
|
||||||
|
- **实时查看**: 支持自动刷新,实时监控系统状态
|
||||||
|
- **智能过滤**: 按级别、关键词、Trace ID、时间范围过滤
|
||||||
|
- **链路追踪**: 点击Trace ID查看完整请求链路
|
||||||
|
- **统计分析**: 显示各级别日志数量统计
|
||||||
|
- **搜索高亮**: 搜索关键词在日志中高亮显示
|
||||||
|
- **权限控制**: 仅管理员可访问日志管理功能
|
||||||
|
|
||||||
|
### 2.7 系统设置模块 (System Settings)
|
||||||
|
|
||||||
#### 2.6.1 基本设置页面
|
#### 2.6.1 基本设置页面
|
||||||
```
|
```
|
||||||
@ -335,6 +407,7 @@ backend/
|
|||||||
│ │ │ ├── tag.go # 标签管理
|
│ │ │ ├── tag.go # 标签管理
|
||||||
│ │ │ ├── timeline.go # 时间线管理
|
│ │ │ ├── timeline.go # 时间线管理
|
||||||
│ │ │ ├── settings.go # 系统设置
|
│ │ │ ├── settings.go # 系统设置
|
||||||
|
│ │ │ ├── logs.go # 日志管理
|
||||||
│ │ │ └── upload.go # 文件上传
|
│ │ │ └── upload.go # 文件上传
|
||||||
│ │ ├── middleware/ # 中间件
|
│ │ ├── middleware/ # 中间件
|
||||||
│ │ │ ├── auth.go # 认证中间件
|
│ │ │ ├── auth.go # 认证中间件
|
||||||
@ -351,6 +424,7 @@ backend/
|
|||||||
│ │ ├── tag_service.go # 标签服务
|
│ │ ├── tag_service.go # 标签服务
|
||||||
│ │ ├── timeline_service.go # 时间线服务
|
│ │ ├── timeline_service.go # 时间线服务
|
||||||
│ │ ├── upload_service.go # 上传服务
|
│ │ ├── upload_service.go # 上传服务
|
||||||
|
│ │ ├── logs_service.go # 日志服务
|
||||||
│ │ └── settings_service.go # 设置服务
|
│ │ └── settings_service.go # 设置服务
|
||||||
│ ├── repository/ # 数据访问层
|
│ ├── repository/ # 数据访问层
|
||||||
│ │ ├── photo_repo.go # 照片数据访问
|
│ │ ├── photo_repo.go # 照片数据访问
|
||||||
@ -445,6 +519,11 @@ admin/
|
|||||||
│ │ │ ├── category-tree.tsx
|
│ │ │ ├── category-tree.tsx
|
||||||
│ │ │ ├── category-form.tsx
|
│ │ │ ├── category-form.tsx
|
||||||
│ │ │ └── category-stats.tsx
|
│ │ │ └── category-stats.tsx
|
||||||
|
│ │ ├── logs/ # 日志管理组件
|
||||||
|
│ │ │ ├── log-viewer.tsx
|
||||||
|
│ │ │ ├── log-filter.tsx
|
||||||
|
│ │ │ ├── log-detail.tsx
|
||||||
|
│ │ │ └── log-stats.tsx
|
||||||
│ │ └── common/ # 通用组件
|
│ │ └── common/ # 通用组件
|
||||||
│ │ ├── loading.tsx
|
│ │ ├── loading.tsx
|
||||||
│ │ ├── error-boundary.tsx
|
│ │ ├── error-boundary.tsx
|
||||||
@ -460,22 +539,28 @@ admin/
|
|||||||
│ │ │ └── index.tsx
|
│ │ │ └── index.tsx
|
||||||
│ │ ├── tags/
|
│ │ ├── tags/
|
||||||
│ │ │ └── index.tsx
|
│ │ │ └── index.tsx
|
||||||
|
│ │ ├── logs/
|
||||||
|
│ │ │ ├── index.tsx
|
||||||
|
│ │ │ └── detail.tsx
|
||||||
│ │ └── settings/
|
│ │ └── settings/
|
||||||
│ │ └── index.tsx
|
│ │ └── index.tsx
|
||||||
│ ├── hooks/ # 自定义Hooks
|
│ ├── hooks/ # 自定义Hooks
|
||||||
│ │ ├── useAuth.ts
|
│ │ ├── useAuth.ts
|
||||||
│ │ ├── usePhotos.ts
|
│ │ ├── usePhotos.ts
|
||||||
│ │ ├── useCategories.ts
|
│ │ ├── useCategories.ts
|
||||||
|
│ │ ├── useLogs.ts
|
||||||
│ │ └── useUpload.ts
|
│ │ └── useUpload.ts
|
||||||
│ ├── services/ # API服务
|
│ ├── services/ # API服务
|
||||||
│ │ ├── api.ts
|
│ │ ├── api.ts
|
||||||
│ │ ├── auth.ts
|
│ │ ├── auth.ts
|
||||||
│ │ ├── photo.ts
|
│ │ ├── photo.ts
|
||||||
│ │ ├── category.ts
|
│ │ ├── category.ts
|
||||||
|
│ │ ├── logs.ts
|
||||||
│ │ └── upload.ts
|
│ │ └── upload.ts
|
||||||
│ ├── store/ # 状态管理
|
│ ├── store/ # 状态管理
|
||||||
│ │ ├── auth.ts
|
│ │ ├── auth.ts
|
||||||
│ │ ├── photo.ts
|
│ │ ├── photo.ts
|
||||||
|
│ │ ├── logs.ts
|
||||||
│ │ └── ui.ts
|
│ │ └── ui.ts
|
||||||
│ ├── utils/ # 工具函数
|
│ ├── utils/ # 工具函数
|
||||||
│ │ ├── format.ts
|
│ │ ├── format.ts
|
||||||
@ -484,6 +569,7 @@ admin/
|
|||||||
│ └── types/ # 类型定义
|
│ └── types/ # 类型定义
|
||||||
│ ├── api.ts
|
│ ├── api.ts
|
||||||
│ ├── photo.ts
|
│ ├── photo.ts
|
||||||
|
│ ├── logs.ts
|
||||||
│ └── user.ts
|
│ └── user.ts
|
||||||
├── public/ # 静态资源
|
├── public/ # 静态资源
|
||||||
├── package.json
|
├── package.json
|
||||||
|
|||||||
@ -36,6 +36,8 @@ joho/godotenv // 环境变量
|
|||||||
// 日志系统
|
// 日志系统
|
||||||
sirupsen/logrus // 结构化日志
|
sirupsen/logrus // 结构化日志
|
||||||
lumberjack.v2 // 日志轮转
|
lumberjack.v2 // 日志轮转
|
||||||
|
opentracing/opentracing-go // 链路追踪
|
||||||
|
jaegertracing/jaeger-client-go // Jaeger客户端
|
||||||
|
|
||||||
// 验证和工具
|
// 验证和工具
|
||||||
go-playground/validator // 数据验证
|
go-playground/validator // 数据验证
|
||||||
@ -64,6 +66,7 @@ photography-backend/
|
|||||||
│ ├── config/ # 配置管理
|
│ ├── config/ # 配置管理
|
||||||
│ ├── database/ # 数据库连接
|
│ ├── database/ # 数据库连接
|
||||||
│ ├── logger/ # 日志系统
|
│ ├── logger/ # 日志系统
|
||||||
|
│ ├── tracing/ # 链路追踪
|
||||||
│ ├── middleware/ # 中间件
|
│ ├── middleware/ # 中间件
|
||||||
│ ├── storage/ # 存储服务
|
│ ├── storage/ # 存储服务
|
||||||
│ ├── cache/ # 缓存服务
|
│ ├── cache/ # 缓存服务
|
||||||
@ -127,6 +130,7 @@ package domain
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Photo 照片领域实体
|
// Photo 照片领域实体
|
||||||
@ -462,8 +466,19 @@ func NewPhotoService(
|
|||||||
|
|
||||||
// GetPhotos 获取照片列表
|
// GetPhotos 获取照片列表
|
||||||
func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListRequest) (*dto.PhotoListResponse, error) {
|
func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListRequest) (*dto.PhotoListResponse, error) {
|
||||||
|
// 创建span用于链路追踪
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "PhotoService.GetPhotos")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// 获取trace ID
|
||||||
|
traceID := s.getTraceID(span)
|
||||||
|
logger := s.logger.WithField("trace_id", traceID)
|
||||||
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
if err := req.Validate(); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
|
span.SetTag("error", true)
|
||||||
|
span.LogKV("event", "validation_error", "error", err.Error())
|
||||||
|
logger.WithError(err).Error("Invalid request parameters")
|
||||||
return nil, fmt.Errorf("invalid request: %w", err)
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +497,9 @@ func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListReque
|
|||||||
// 从数据库查询
|
// 从数据库查询
|
||||||
photos, total, err := s.photoRepo.FindWithPagination(ctx, filter)
|
photos, total, err := s.photoRepo.FindWithPagination(ctx, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.WithError(err).Error("Failed to get photos from repository")
|
span.SetTag("error", true)
|
||||||
|
span.LogKV("event", "repository_error", "error", err.Error())
|
||||||
|
logger.WithError(err).Error("Failed to get photos from repository")
|
||||||
return nil, fmt.Errorf("failed to get photos: %w", err)
|
return nil, fmt.Errorf("failed to get photos: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,13 +526,50 @@ func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListReque
|
|||||||
s.cacheService.Set(ctx, cacheKey, data, 10*time.Minute)
|
s.cacheService.Set(ctx, cacheKey, data, 10*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录成功日志
|
||||||
|
span.SetTag("photos_count", len(photos))
|
||||||
|
span.SetTag("total_count", total)
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"photos_count": len(photos),
|
||||||
|
"total_count": total,
|
||||||
|
"page": req.Page,
|
||||||
|
"limit": req.Limit,
|
||||||
|
}).Info("Photos retrieved successfully")
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTraceID 获取trace ID
|
||||||
|
func (s *photoServiceImpl) getTraceID(span opentracing.Span) string {
|
||||||
|
if span == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从span context获取trace ID
|
||||||
|
spanContext := span.Context()
|
||||||
|
if jaegerSpanContext, ok := spanContext.(interface{ TraceID() string }); ok {
|
||||||
|
return jaegerSpanContext.TraceID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法获取trace ID,生成一个UUID作为fallback
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
// CreatePhoto 创建照片
|
// CreatePhoto 创建照片
|
||||||
func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoRequest) (*dto.PhotoResponse, error) {
|
func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoRequest) (*dto.PhotoResponse, error) {
|
||||||
|
// 创建span用于链路追踪
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "PhotoService.CreatePhoto")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// 获取trace ID
|
||||||
|
traceID := s.getTraceID(span)
|
||||||
|
logger := s.logger.WithField("trace_id", traceID)
|
||||||
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
if err := req.Validate(); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
|
span.SetTag("error", true)
|
||||||
|
span.LogKV("event", "validation_error", "error", err.Error())
|
||||||
|
logger.WithError(err).Error("Invalid request parameters")
|
||||||
return nil, fmt.Errorf("invalid request: %w", err)
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,7 +603,9 @@ func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoR
|
|||||||
|
|
||||||
// 保存照片到数据库
|
// 保存照片到数据库
|
||||||
if err := s.photoRepo.Create(ctx, tx, photo); err != nil {
|
if err := s.photoRepo.Create(ctx, tx, photo); err != nil {
|
||||||
s.logger.WithError(err).Error("Failed to save photo to database")
|
span.SetTag("error", true)
|
||||||
|
span.LogKV("event", "database_error", "error", err.Error())
|
||||||
|
logger.WithError(err).Error("Failed to save photo to database")
|
||||||
return nil, fmt.Errorf("failed to save photo: %w", err)
|
return nil, fmt.Errorf("failed to save photo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,6 +646,15 @@ func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoR
|
|||||||
// 清除相关缓存
|
// 清除相关缓存
|
||||||
s.invalidateCache(ctx, "photos:*", "categories:*", "stats:*")
|
s.invalidateCache(ctx, "photos:*", "categories:*", "stats:*")
|
||||||
|
|
||||||
|
// 记录成功日志
|
||||||
|
span.SetTag("photo_id", photo.ID.Value())
|
||||||
|
span.SetTag("photo_title", photo.Title)
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"photo_id": photo.ID.Value(),
|
||||||
|
"photo_title": photo.Title,
|
||||||
|
"photo_slug": photo.Slug,
|
||||||
|
}).Info("Photo created successfully")
|
||||||
|
|
||||||
return dto.NewPhotoResponse(photo), nil
|
return dto.NewPhotoResponse(photo), nil
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -1267,6 +1332,7 @@ type Config struct {
|
|||||||
Storage StorageConfig `mapstructure:"storage"`
|
Storage StorageConfig `mapstructure:"storage"`
|
||||||
JWT JWTConfig `mapstructure:"jwt"`
|
JWT JWTConfig `mapstructure:"jwt"`
|
||||||
Logger LoggerConfig `mapstructure:"logger"`
|
Logger LoggerConfig `mapstructure:"logger"`
|
||||||
|
Tracing TracingConfig `mapstructure:"tracing"`
|
||||||
Upload UploadConfig `mapstructure:"upload"`
|
Upload UploadConfig `mapstructure:"upload"`
|
||||||
Image ImageConfig `mapstructure:"image"`
|
Image ImageConfig `mapstructure:"image"`
|
||||||
}
|
}
|
||||||
@ -1367,6 +1433,18 @@ type LoggerConfig struct {
|
|||||||
Compress bool `mapstructure:"compress"`
|
Compress bool `mapstructure:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TracingConfig 链路追踪配置
|
||||||
|
type TracingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
ServiceName string `mapstructure:"service_name"`
|
||||||
|
Jaeger struct {
|
||||||
|
Endpoint string `mapstructure:"endpoint"`
|
||||||
|
Username string `mapstructure:"username"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
} `mapstructure:"jaeger"`
|
||||||
|
SamplingRate float64 `mapstructure:"sampling_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
// UploadConfig 上传配置
|
// UploadConfig 上传配置
|
||||||
type UploadConfig struct {
|
type UploadConfig struct {
|
||||||
MaxFileSize int64 `mapstructure:"max_file_size"`
|
MaxFileSize int64 `mapstructure:"max_file_size"`
|
||||||
@ -1486,6 +1564,12 @@ func setDefaults() {
|
|||||||
viper.SetDefault("logger.max_age", 30)
|
viper.SetDefault("logger.max_age", 30)
|
||||||
viper.SetDefault("logger.compress", true)
|
viper.SetDefault("logger.compress", true)
|
||||||
|
|
||||||
|
// 链路追踪默认配置
|
||||||
|
viper.SetDefault("tracing.enabled", true)
|
||||||
|
viper.SetDefault("tracing.service_name", "photography-backend")
|
||||||
|
viper.SetDefault("tracing.jaeger.endpoint", "http://localhost:14268/api/traces")
|
||||||
|
viper.SetDefault("tracing.sampling_rate", 1.0)
|
||||||
|
|
||||||
// 上传默认配置
|
// 上传默认配置
|
||||||
viper.SetDefault("upload.max_file_size", 52428800) // 50MB
|
viper.SetDefault("upload.max_file_size", 52428800) // 50MB
|
||||||
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/raw"})
|
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/raw"})
|
||||||
@ -1812,6 +1896,18 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- photography-network
|
- photography-network
|
||||||
|
|
||||||
|
# Jaeger链路追踪
|
||||||
|
jaeger:
|
||||||
|
image: jaegertracing/all-in-one:latest
|
||||||
|
ports:
|
||||||
|
- "16686:16686"
|
||||||
|
- "14268:14268"
|
||||||
|
environment:
|
||||||
|
- COLLECTOR_OTLP_ENABLED=true
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- photography-network
|
||||||
|
|
||||||
# Nginx反向代理(可选)
|
# Nginx反向代理(可选)
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
|
|||||||
1320
docs/v1/backend/运维监控方案.md
Normal file
1320
docs/v1/backend/运维监控方案.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user