mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Fix the abnormal path retrieval issue in some systems.
This commit is contained in:
11
cmd/root.go
11
cmd/root.go
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"NetworkAuth/config"
|
||||
"NetworkAuth/utils"
|
||||
"NetworkAuth/utils/logger"
|
||||
"io"
|
||||
"os"
|
||||
@@ -67,7 +68,7 @@ func setupLogrusForNonHTTP() {
|
||||
if cfgFile != "" {
|
||||
config.Init(cfgFile)
|
||||
} else {
|
||||
config.Init("./config.json")
|
||||
config.Init("config.json")
|
||||
}
|
||||
|
||||
// 根据配置文件进一步配置logrus
|
||||
@@ -105,7 +106,11 @@ func setupLogrusFromConfig() {
|
||||
logFile := viper.GetString("log.file")
|
||||
if logFile != "" {
|
||||
// 确保日志目录存在
|
||||
logDir := filepath.Dir(logFile)
|
||||
path := logFile
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(utils.GetRootDir(), path)
|
||||
}
|
||||
logDir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
logrus.WithError(err).Error("创建日志目录失败")
|
||||
return
|
||||
@@ -113,7 +118,7 @@ func setupLogrusFromConfig() {
|
||||
|
||||
// 配置lumberjack日志轮转
|
||||
lumberjackLogger := &lumberjack.Logger{
|
||||
Filename: logFile,
|
||||
Filename: path,
|
||||
MaxSize: viper.GetInt("log.max_size"), // MB
|
||||
MaxBackups: viper.GetInt("log.max_backups"), // 保留的旧日志文件数量
|
||||
MaxAge: viper.GetInt("log.max_age"), // 天数
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"NetworkAuth/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -107,7 +109,7 @@ func GetDefaultAppConfig() *AppConfig {
|
||||
MaxOpenConns: 100,
|
||||
},
|
||||
SQLite: SQLiteConfig{
|
||||
Path: "./database.db",
|
||||
Path: "database.db",
|
||||
},
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
@@ -118,7 +120,7 @@ func GetDefaultAppConfig() *AppConfig {
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
File: "./logs/app.log",
|
||||
File: "logs/app.log",
|
||||
MaxSize: 100,
|
||||
MaxBackups: 5,
|
||||
MaxAge: 30,
|
||||
@@ -128,6 +130,9 @@ func GetDefaultAppConfig() *AppConfig {
|
||||
|
||||
// Init 初始化配置文件
|
||||
func Init(cfgFilePath string) {
|
||||
if !filepath.IsAbs(cfgFilePath) {
|
||||
cfgFilePath = filepath.Join(utils.GetRootDir(), cfgFilePath)
|
||||
}
|
||||
currentConfigFilePath = cfgFilePath
|
||||
viper.SetConfigFile(cfgFilePath)
|
||||
viper.SetConfigType("json")
|
||||
@@ -204,7 +209,10 @@ func SaveConfig(appConfig *AppConfig) error {
|
||||
return err
|
||||
}
|
||||
if currentConfigFilePath == "" {
|
||||
currentConfigFilePath = "./config.json"
|
||||
currentConfigFilePath = "config.json"
|
||||
}
|
||||
if !filepath.IsAbs(currentConfigFilePath) {
|
||||
currentConfigFilePath = filepath.Join(utils.GetRootDir(), currentConfigFilePath)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(currentConfigFilePath), 0755); err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"NetworkAuth/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -125,8 +127,13 @@ func validateSQLiteConfig(config *SQLiteConfig) error {
|
||||
return errors.New("SQLite数据库路径不能为空")
|
||||
}
|
||||
|
||||
path := config.Path
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(utils.GetRootDir(), path)
|
||||
}
|
||||
|
||||
// 检查目录是否存在,不存在则创建
|
||||
dir := filepath.Dir(config.Path)
|
||||
dir := filepath.Dir(path)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建SQLite数据库目录失败: %w", err)
|
||||
@@ -160,7 +167,11 @@ func validateLogConfig(config *LogConfig) error {
|
||||
|
||||
// 检查日志文件目录(仅当日志文件路径不为空时)
|
||||
if config.File != "" {
|
||||
dir := filepath.Dir(config.File)
|
||||
path := config.File
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(utils.GetRootDir(), path)
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建日志目录失败: %w", err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -103,7 +104,10 @@ func performInitFromViper() error {
|
||||
case "sqlite":
|
||||
dbPath := cfg.Database.SQLite.Path
|
||||
if dbPath == "" {
|
||||
dbPath = "./database.db"
|
||||
dbPath = "database.db"
|
||||
}
|
||||
if !filepath.IsAbs(dbPath) {
|
||||
dbPath = filepath.Join(utils.GetRootDir(), dbPath)
|
||||
}
|
||||
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||
logrus.Info("SQLite 数据库文件不存在,系统尚未安装,跳过数据库连接")
|
||||
@@ -209,7 +213,10 @@ func buildGormLogger(level string) gLogger.Interface {
|
||||
func initSQLite(sqliteConfig *appconfig.SQLiteConfig, logLevel string) error {
|
||||
path := sqliteConfig.Path
|
||||
if path == "" {
|
||||
path = "./database.db"
|
||||
path = "database.db"
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(utils.GetRootDir(), path)
|
||||
}
|
||||
dsn := fmt.Sprintf("file:%s?cache=shared&_busy_timeout=5000&_fk=1", path)
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: buildGormLogger(logLevel)})
|
||||
|
||||
@@ -2,12 +2,14 @@ package server
|
||||
|
||||
import (
|
||||
"NetworkAuth/public"
|
||||
"NetworkAuth/utils"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -48,6 +50,9 @@ func registerFrontendRoutes(r *gin.Engine) {
|
||||
return // 反向代理接管了所有非 API 路由,直接返回
|
||||
} else {
|
||||
// 使用本地外部目录
|
||||
if !filepath.IsAbs(distConfig) {
|
||||
distConfig = filepath.Join(utils.GetRootDir(), distConfig)
|
||||
}
|
||||
fileServer = http.FileServer(http.Dir(distConfig))
|
||||
|
||||
// 拦截并处理静态资源请求
|
||||
|
||||
@@ -74,11 +74,28 @@ func NewClient(baseURL string, proxyStr string, persistCookies bool, timeout int
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rc.SetTransport(bypass.Transport)
|
||||
rc.SetTransport(&sanitizeTransport{t: bypass.Transport})
|
||||
|
||||
return &RestyClient{client: rc}
|
||||
}
|
||||
|
||||
// sanitizeTransport 包装 http.RoundTripper 以修复底层库可能违背 Go 接口约定的行为
|
||||
type sanitizeTransport struct {
|
||||
t http.RoundTripper
|
||||
}
|
||||
|
||||
func (s *sanitizeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
resp, err := s.t.RoundTrip(req)
|
||||
// net/http 规定 RoundTripper 要么返回有效的 resp 和 nil error,要么返回 nil resp 和有效的 error。
|
||||
// 某些第三方库(如部分 tls-client 封装)在遇到网络小问题时会同时返回 resp 和 err。
|
||||
// 这会导致 net/http 打印 "RoundTripper returned a response & error; ignoring response" 并强制丢弃响应。
|
||||
// 在这里我们进行修正:如果已经拿到了响应(哪怕是不完整的),我们优先保留响应并将 err 置空,让上层通过读取 Body 自行发现错误。
|
||||
if resp != nil && err != nil {
|
||||
err = nil
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// fillResponseBody 使用反射强制填充响应体
|
||||
// 当 Resty 因为重定向策略错误而提前返回时,它可能不会读取 Body
|
||||
// 此方法手动读取 RawResponse.Body 并回填到 resty.Response 的私有 body 字段中
|
||||
|
||||
64
utils/path.go
Normal file
64
utils/path.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRootDir 获取当前程序运行的真实根目录
|
||||
// 能够智能、跨平台地识别是编译后的可执行文件运行,还是通过 `go run` 运行(通常在临时目录下)
|
||||
func GetRootDir() string {
|
||||
var baseDir string
|
||||
|
||||
// 首先尝试获取当前工作目录
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
workDir = "."
|
||||
}
|
||||
|
||||
// 获取程序可执行文件所在目录
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
// 如果获取可执行文件路径失败,使用当前工作目录
|
||||
return workDir
|
||||
}
|
||||
|
||||
// 解析软链接,获取真实物理路径(macOS 下 /tmp 经常是 /private/tmp 的软链)
|
||||
realExecPath, err := filepath.EvalSymlinks(execPath)
|
||||
if err == nil {
|
||||
execPath = realExecPath
|
||||
}
|
||||
execDir := filepath.Dir(execPath)
|
||||
|
||||
realTempDir, err := filepath.EvalSymlinks(os.TempDir())
|
||||
if err != nil {
|
||||
realTempDir = os.TempDir()
|
||||
}
|
||||
|
||||
// 跨平台安全地判断 execDir 是否在 realTempDir 内部
|
||||
// 使用 filepath.Rel 可以避免直接 HasPrefix 带来的大小写、路径分隔符以及部分目录名重合的问题
|
||||
rel, err := filepath.Rel(realTempDir, execDir)
|
||||
isGoRun := false
|
||||
if err == nil {
|
||||
// 如果 rel 不以 ".." 开头,说明 execDir 在 TempDir 内部,即为 go run 模式
|
||||
if rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
isGoRun = true
|
||||
}
|
||||
} else {
|
||||
// fallback: 如果 Rel 失败(例如跨盘符),则退回简单的 HasPrefix 判断(带上分隔符防误判)
|
||||
cleanTemp := filepath.Clean(realTempDir) + string(os.PathSeparator)
|
||||
cleanExec := filepath.Clean(execDir) + string(os.PathSeparator)
|
||||
if strings.HasPrefix(strings.ToLower(cleanExec), strings.ToLower(cleanTemp)) {
|
||||
isGoRun = true
|
||||
}
|
||||
}
|
||||
|
||||
if isGoRun {
|
||||
baseDir = workDir
|
||||
} else {
|
||||
baseDir = execDir
|
||||
}
|
||||
|
||||
return baseDir
|
||||
}
|
||||
Reference in New Issue
Block a user