文件夹挂载逻辑变动
This commit is contained in:
@@ -399,7 +399,8 @@ func getFrpcStatusHandler(c *gin.Context) {
|
|||||||
// ========== 日志 Handler ==========
|
// ========== 日志 Handler ==========
|
||||||
|
|
||||||
func getFrpcLogHandler(c *gin.Context) {
|
func getFrpcLogHandler(c *gin.Context) {
|
||||||
lines, err := readTailLog("./frpc.log", 200)
|
// 从 ./data/frpc.log 读取日志
|
||||||
|
lines, err := readTailLog("./data/frpc.log", 200)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
@@ -17,17 +17,7 @@ import (
|
|||||||
|
|
||||||
var DB *sql.DB
|
var DB *sql.DB
|
||||||
|
|
||||||
// ============================================================
|
const SchemaVersion = "v2"
|
||||||
// 数据库版本常量
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
const (
|
|
||||||
SchemaVersion = "v2" // 当前数据库 Schema 版本
|
|
||||||
)
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 数据模型
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
type GlobalConfig struct {
|
type GlobalConfig struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
@@ -61,10 +51,6 @@ type User struct {
|
|||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 数据库初始化
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func InitDB() error {
|
func InitDB() error {
|
||||||
// 确保 data 目录存在
|
// 确保 data 目录存在
|
||||||
if err := os.MkdirAll("./data", 0755); err != nil {
|
if err := os.MkdirAll("./data", 0755); err != nil {
|
||||||
@@ -94,12 +80,7 @@ func InitDB() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 建表
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func createTables() error {
|
func createTables() error {
|
||||||
// 用户表
|
|
||||||
_, err := DB.Exec(`
|
_, err := DB.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -113,7 +94,6 @@ func createTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局配置表
|
|
||||||
_, err = DB.Exec(`
|
_, err = DB.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS global_config (
|
CREATE TABLE IF NOT EXISTS global_config (
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
@@ -135,7 +115,6 @@ func createTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隧道表
|
|
||||||
_, err = DB.Exec(`
|
_, err = DB.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS proxies (
|
CREATE TABLE IF NOT EXISTS proxies (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -153,7 +132,6 @@ func createTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用配置表
|
|
||||||
_, err = DB.Exec(`
|
_, err = DB.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS app_config (
|
CREATE TABLE IF NOT EXISTS app_config (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
@@ -165,7 +143,6 @@ func createTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化默认配置
|
|
||||||
var count int
|
var count int
|
||||||
DB.QueryRow("SELECT COUNT(*) FROM global_config").Scan(&count)
|
DB.QueryRow("SELECT COUNT(*) FROM global_config").Scan(&count)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
@@ -185,10 +162,6 @@ func createTables() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 迁移引擎
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func getCurrentSchemaVersion() string {
|
func getCurrentSchemaVersion() string {
|
||||||
var version string
|
var version string
|
||||||
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'schema_version'").Scan(&version)
|
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'schema_version'").Scan(&version)
|
||||||
@@ -272,7 +245,6 @@ func runMigrations() error {
|
|||||||
log.Printf("📌 当前数据库 Schema: %s, 目标版本: %s", currentVer, targetVer)
|
log.Printf("📌 当前数据库 Schema: %s, 目标版本: %s", currentVer, targetVer)
|
||||||
|
|
||||||
if currentVer == targetVer {
|
if currentVer == targetVer {
|
||||||
// 检查数据库是否包含有效数据
|
|
||||||
var userCount int
|
var userCount int
|
||||||
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
|
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
|
||||||
if err != nil || userCount == 0 {
|
if err != nil || userCount == 0 {
|
||||||
@@ -302,7 +274,6 @@ func runMigrations() error {
|
|||||||
|
|
||||||
if currentSchema == nil || schemaVersionsEqual(currentSchema, targetSchema) {
|
if currentSchema == nil || schemaVersionsEqual(currentSchema, targetSchema) {
|
||||||
log.Println(" 迁移类型: 轻量复制(Schema 无变更)")
|
log.Println(" 迁移类型: 轻量复制(Schema 无变更)")
|
||||||
// 验证数据库是否有效
|
|
||||||
var userCount int
|
var userCount int
|
||||||
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
|
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
|
||||||
if err != nil || userCount == 0 {
|
if err != nil || userCount == 0 {
|
||||||
@@ -448,10 +419,6 @@ func buildInsertSQL(oldTable, newTable string, oldDef, newDef *SchemaVersionDef)
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// JWT 密钥管理
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func ensureJwtSecret() error {
|
func ensureJwtSecret() error {
|
||||||
var value string
|
var value string
|
||||||
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'jwt_secret'").Scan(&value)
|
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'jwt_secret'").Scan(&value)
|
||||||
@@ -484,10 +451,6 @@ func GetJwtSecret() (string, error) {
|
|||||||
return secret, nil
|
return secret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 全局配置 CRUD
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func GetGlobalConfig() (*GlobalConfig, error) {
|
func GetGlobalConfig() (*GlobalConfig, error) {
|
||||||
var cfg GlobalConfig
|
var cfg GlobalConfig
|
||||||
err := DB.QueryRow(`
|
err := DB.QueryRow(`
|
||||||
@@ -523,10 +486,6 @@ func UpdateGlobalConfig(cfg *GlobalConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 隧道 CRUD
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func GetProxies() ([]Proxy, error) {
|
func GetProxies() ([]Proxy, error) {
|
||||||
rows, err := DB.Query(`
|
rows, err := DB.Query(`
|
||||||
SELECT id, name, type, local_ip, local_port, remote_port, enabled
|
SELECT id, name, type, local_ip, local_port, remote_port, enabled
|
||||||
@@ -589,10 +548,6 @@ func DeleteProxy(id int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 用户 CRUD
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func GetUserByUsername(username string) (*User, error) {
|
func GetUserByUsername(username string) (*User, error) {
|
||||||
var u User
|
var u User
|
||||||
err := DB.QueryRow(`
|
err := DB.QueryRow(`
|
||||||
|
|||||||
@@ -431,11 +431,12 @@ do_deploy() {
|
|||||||
|
|
||||||
# ----- 启动新容器 -----
|
# ----- 启动新容器 -----
|
||||||
print_step "启动 frpc-console 容器..."
|
print_step "启动 frpc-console 容器..."
|
||||||
|
# 启动容器(只挂载数据目录)
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name frpc-console \
|
--name frpc-console \
|
||||||
--restart=always \
|
--restart=always \
|
||||||
--network host \
|
--network host \
|
||||||
-v ${DEPLOY_DIR}:/app \
|
-v ${DEPLOY_DIR}/data:/app/data \
|
||||||
-e PORT=${PORT} \
|
-e PORT=${PORT} \
|
||||||
-e TZ=Asia/Shanghai \
|
-e TZ=Asia/Shanghai \
|
||||||
${IMAGE_NAME}:${IMAGE_TAG}
|
${IMAGE_NAME}:${IMAGE_TAG}
|
||||||
@@ -449,7 +450,7 @@ do_deploy() {
|
|||||||
|
|
||||||
# ----- 写入版本文件 -----
|
# ----- 写入版本文件 -----
|
||||||
print_step "写入版本文件..."
|
print_step "写入版本文件..."
|
||||||
echo "$TARGET_VERSION" > "${DEPLOY_DIR}/version.ini"
|
echo "$TARGET_VERSION" > "${DEPLOY_DIR}/data/version.ini"
|
||||||
print_success "版本写入: ${TARGET_VERSION}"
|
print_success "版本写入: ${TARGET_VERSION}"
|
||||||
|
|
||||||
# ----- 检查 frpc 子进程 -----
|
# ----- 检查 frpc 子进程 -----
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -24,16 +25,12 @@ var FrpcTemplateContent string
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cachedFrpcPath string
|
cachedFrpcPath string
|
||||||
frpcPathMutex sync.Mutex
|
frpsPathMutex sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 获取 frpc 路径
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func getFrpcPath() (string, error) {
|
func getFrpcPath() (string, error) {
|
||||||
frpcPathMutex.Lock()
|
frpsPathMutex.Lock()
|
||||||
defer frpcPathMutex.Unlock()
|
defer frpsPathMutex.Unlock()
|
||||||
|
|
||||||
if cachedFrpcPath != "" {
|
if cachedFrpcPath != "" {
|
||||||
if _, err := os.Stat(cachedFrpcPath); err == nil {
|
if _, err := os.Stat(cachedFrpcPath); err == nil {
|
||||||
@@ -61,14 +58,12 @@ func getFrpcPath() (string, error) {
|
|||||||
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
|
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先级1:本地 ./bin/
|
|
||||||
localPath := filepath.Join(".", "bin", fileName)
|
localPath := filepath.Join(".", "bin", fileName)
|
||||||
if _, err := os.Stat(localPath); err == nil {
|
if _, err := os.Stat(localPath); err == nil {
|
||||||
cachedFrpcPath = localPath
|
cachedFrpcPath = localPath
|
||||||
return localPath, nil
|
return localPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先级2:embed 解压
|
|
||||||
data, err := embeddedFrpc.ReadFile("bin/" + fileName)
|
data, err := embeddedFrpc.ReadFile("bin/" + fileName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tmpPath := filepath.Join(os.TempDir(), "frpc")
|
tmpPath := filepath.Join(os.TempDir(), "frpc")
|
||||||
@@ -85,7 +80,6 @@ func getFrpcPath() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先级3:系统 PATH
|
|
||||||
path, err := exec.LookPath("frpc")
|
path, err := exec.LookPath("frpc")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cachedFrpcPath = path
|
cachedFrpcPath = path
|
||||||
@@ -95,10 +89,6 @@ func getFrpcPath() (string, error) {
|
|||||||
return "", fmt.Errorf("未找到 frpc 文件")
|
return "", fmt.Errorf("未找到 frpc 文件")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 生成 frpc.toml(v2.0 支持 wireProtocol)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func GenerateFrpcConfig() error {
|
func GenerateFrpcConfig() error {
|
||||||
cfg, err := GetGlobalConfig()
|
cfg, err := GetGlobalConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,24 +107,21 @@ func GenerateFrpcConfig() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建模板数据
|
|
||||||
data := struct {
|
data := struct {
|
||||||
*GlobalConfig
|
*GlobalConfig
|
||||||
Proxies []Proxy
|
Proxies []Proxy
|
||||||
WireProtocolLine string // v2 协议配置行(为空则不输出)
|
WireProtocolLine string
|
||||||
}{
|
}{
|
||||||
GlobalConfig: cfg,
|
GlobalConfig: cfg,
|
||||||
Proxies: activeProxies,
|
Proxies: activeProxies,
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2.0: 如果启用 v2 协议,生成配置行
|
|
||||||
if cfg.WireProtocolV2 {
|
if cfg.WireProtocolV2 {
|
||||||
data.WireProtocolLine = `wireProtocol = "v2"`
|
data.WireProtocolLine = `wireProtocol = "v2"`
|
||||||
} else {
|
} else {
|
||||||
data.WireProtocolLine = "" // 不输出,使用默认 v1
|
data.WireProtocolLine = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取模板
|
|
||||||
var tmplContent string
|
var tmplContent string
|
||||||
if _, err := os.Stat("frpc.tmpl"); err == nil {
|
if _, err := os.Stat("frpc.tmpl"); err == nil {
|
||||||
content, readErr := os.ReadFile("frpc.tmpl")
|
content, readErr := os.ReadFile("frpc.tmpl")
|
||||||
@@ -157,19 +144,20 @@ func GenerateFrpcConfig() error {
|
|||||||
return fmt.Errorf("渲染模板失败: %w", err)
|
return fmt.Errorf("渲染模板失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile("./frpc.toml", buf.Bytes(), 0644); err != nil {
|
// 所有文件写入 ./data/ 目录
|
||||||
|
if err := os.MkdirAll("./data", 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建 data 目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile("./data/frpc.toml", buf.Bytes(), 0644); err != nil {
|
||||||
return fmt.Errorf("写入配置文件失败: %w", err)
|
return fmt.Errorf("写入配置文件失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// frpc 进程管理
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func isFrpcRunning() bool {
|
func isFrpcRunning() bool {
|
||||||
pidData, err := os.ReadFile("./frpc.pid")
|
pidData, err := os.ReadFile("./data/frpc.pid")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -200,7 +188,11 @@ func StartFrpc() error {
|
|||||||
return fmt.Errorf("获取 frpc 路径失败: %w", err)
|
return fmt.Errorf("获取 frpc 路径失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) {
|
if err := os.MkdirAll("./data", 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建 data 目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat("./data/frpc.toml"); os.IsNotExist(err) {
|
||||||
if err := GenerateFrpcConfig(); err != nil {
|
if err := GenerateFrpcConfig(); err != nil {
|
||||||
return fmt.Errorf("生成配置文件失败: %w", err)
|
return fmt.Errorf("生成配置文件失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -210,13 +202,13 @@ func StartFrpc() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove("./frpc.pid")
|
os.Remove("./data/frpc.pid")
|
||||||
|
|
||||||
cmd := exec.Command(frpcPath, "-c", "./frpc.toml")
|
cmd := exec.Command(frpcPath, "-c", "./data/frpc.toml")
|
||||||
setWindowHide(cmd)
|
setWindowHide(cmd)
|
||||||
setSysProcAttr(cmd)
|
setSysProcAttr(cmd)
|
||||||
|
|
||||||
logFile, err := os.OpenFile("./frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
logFile, err := os.OpenFile("./data/frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("打开日志文件失败: %w", err)
|
return fmt.Errorf("打开日志文件失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -227,7 +219,7 @@ func StartFrpc() error {
|
|||||||
return fmt.Errorf("启动 frpc 失败: %w", err)
|
return fmt.Errorf("启动 frpc 失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile("./frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
|
if err := os.WriteFile("./data/frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
|
||||||
return fmt.Errorf("保存 PID 失败: %w", err)
|
return fmt.Errorf("保存 PID 失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,11 +232,11 @@ func StopFrpc() error {
|
|||||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
|
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
|
||||||
return fmt.Errorf("停止 frpc 失败: %w", err)
|
return fmt.Errorf("停止 frpc 失败: %w", err)
|
||||||
}
|
}
|
||||||
os.Remove("./frpc.pid")
|
os.Remove("./data/frpc.pid")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pidData, err := os.ReadFile("./frpc.pid")
|
pidData, err := os.ReadFile("./data/frpc.pid")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd := exec.Command("pkill", "-f", "frpc")
|
cmd := exec.Command("pkill", "-f", "frpc")
|
||||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
|
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
|
||||||
@@ -256,7 +248,7 @@ func StopFrpc() error {
|
|||||||
pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData)))
|
pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData)))
|
||||||
process, err := os.FindProcess(pid)
|
process, err := os.FindProcess(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove("./frpc.pid")
|
os.Remove("./data/frpc.pid")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +256,7 @@ func StopFrpc() error {
|
|||||||
return fmt.Errorf("杀死进程失败: %w", err)
|
return fmt.Errorf("杀死进程失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove("./frpc.pid")
|
os.Remove("./data/frpc.pid")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +275,7 @@ func ReloadFrpc() error {
|
|||||||
return fmt.Errorf("获取 frpc 路径失败: %w", err)
|
return fmt.Errorf("获取 frpc 路径失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(frpcPath, "reload", "-c", "./frpc.toml")
|
cmd := exec.Command(frpcPath, "reload", "-c", "./data/frpc.toml")
|
||||||
_, err = cmd.CombinedOutput()
|
_, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("⚠️ 热加载失败 (%v),自动降级为重启 frpc", err)
|
log.Printf("⚠️ 热加载失败 (%v),自动降级为重启 frpc", err)
|
||||||
@@ -297,3 +289,77 @@ func ReloadFrpc() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readTailLog 读取文件末尾 n 行
|
||||||
|
func readTailLog(filePath string, n int) ([]string, error) {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
info, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileSize := info.Size()
|
||||||
|
if fileSize == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkSize = 4096
|
||||||
|
var lines []string
|
||||||
|
var leftover []byte
|
||||||
|
offset := fileSize
|
||||||
|
|
||||||
|
for len(lines) < n && offset > 0 {
|
||||||
|
readSize := chunkSize
|
||||||
|
if offset < int64(chunkSize) {
|
||||||
|
readSize = int(offset)
|
||||||
|
}
|
||||||
|
offset -= int64(readSize)
|
||||||
|
|
||||||
|
buf := make([]byte, readSize)
|
||||||
|
_, err := file.ReadAt(buf, offset)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := append(buf, leftover...)
|
||||||
|
leftover = nil
|
||||||
|
|
||||||
|
start := 0
|
||||||
|
for i := len(data) - 1; i >= 0; i-- {
|
||||||
|
if data[i] == '\n' {
|
||||||
|
if i+1 < len(data) {
|
||||||
|
line := string(data[i+1:])
|
||||||
|
if line != "" {
|
||||||
|
lines = append([]string{line}, lines...)
|
||||||
|
if len(lines) >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) < n && start > 0 {
|
||||||
|
leftover = data[:start]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) < n && len(leftover) > 0 {
|
||||||
|
parts := strings.Split(string(leftover), "\n")
|
||||||
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
|
if parts[i] != "" {
|
||||||
|
lines = append([]string{parts[i]}, lines...)
|
||||||
|
if len(lines) >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 读取 version.ini 显示版本(如果存在)
|
// 确保 data 目录存在
|
||||||
if data, err := os.ReadFile("./version.ini"); err == nil {
|
if err := os.MkdirAll("./data", 0755); err != nil {
|
||||||
|
log.Printf("⚠️ 创建 data 目录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 version.ini 显示版本
|
||||||
|
if data, err := os.ReadFile("./data/version.ini"); err == nil {
|
||||||
version := strings.TrimSpace(string(data))
|
version := strings.TrimSpace(string(data))
|
||||||
log.Printf("📌 版本: %s", version)
|
log.Printf("📌 版本: %s", version)
|
||||||
} else {
|
} else {
|
||||||
@@ -43,6 +48,7 @@ func main() {
|
|||||||
func startWatchdog() {
|
func startWatchdog() {
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if !isFrpcRunning() {
|
if !isFrpcRunning() {
|
||||||
log.Println("⚠️ frpc 进程已停止,自动重启...")
|
log.Println("⚠️ frpc 进程已停止,自动重启...")
|
||||||
|
|||||||
Reference in New Issue
Block a user