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 (
|
import (
|
||||||
"NetworkAuth/config"
|
"NetworkAuth/config"
|
||||||
|
"NetworkAuth/utils"
|
||||||
"NetworkAuth/utils/logger"
|
"NetworkAuth/utils/logger"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -67,7 +68,7 @@ func setupLogrusForNonHTTP() {
|
|||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
config.Init(cfgFile)
|
config.Init(cfgFile)
|
||||||
} else {
|
} else {
|
||||||
config.Init("./config.json")
|
config.Init("config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据配置文件进一步配置logrus
|
// 根据配置文件进一步配置logrus
|
||||||
@@ -105,7 +106,11 @@ func setupLogrusFromConfig() {
|
|||||||
logFile := viper.GetString("log.file")
|
logFile := viper.GetString("log.file")
|
||||||
if logFile != "" {
|
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 {
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||||
logrus.WithError(err).Error("创建日志目录失败")
|
logrus.WithError(err).Error("创建日志目录失败")
|
||||||
return
|
return
|
||||||
@@ -113,7 +118,7 @@ func setupLogrusFromConfig() {
|
|||||||
|
|
||||||
// 配置lumberjack日志轮转
|
// 配置lumberjack日志轮转
|
||||||
lumberjackLogger := &lumberjack.Logger{
|
lumberjackLogger := &lumberjack.Logger{
|
||||||
Filename: logFile,
|
Filename: path,
|
||||||
MaxSize: viper.GetInt("log.max_size"), // MB
|
MaxSize: viper.GetInt("log.max_size"), // MB
|
||||||
MaxBackups: viper.GetInt("log.max_backups"), // 保留的旧日志文件数量
|
MaxBackups: viper.GetInt("log.max_backups"), // 保留的旧日志文件数量
|
||||||
MaxAge: viper.GetInt("log.max_age"), // 天数
|
MaxAge: viper.GetInt("log.max_age"), // 天数
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"NetworkAuth/utils"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@@ -107,7 +109,7 @@ func GetDefaultAppConfig() *AppConfig {
|
|||||||
MaxOpenConns: 100,
|
MaxOpenConns: 100,
|
||||||
},
|
},
|
||||||
SQLite: SQLiteConfig{
|
SQLite: SQLiteConfig{
|
||||||
Path: "./database.db",
|
Path: "database.db",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Redis: RedisConfig{
|
Redis: RedisConfig{
|
||||||
@@ -118,7 +120,7 @@ func GetDefaultAppConfig() *AppConfig {
|
|||||||
},
|
},
|
||||||
Log: LogConfig{
|
Log: LogConfig{
|
||||||
Level: "info",
|
Level: "info",
|
||||||
File: "./logs/app.log",
|
File: "logs/app.log",
|
||||||
MaxSize: 100,
|
MaxSize: 100,
|
||||||
MaxBackups: 5,
|
MaxBackups: 5,
|
||||||
MaxAge: 30,
|
MaxAge: 30,
|
||||||
@@ -128,6 +130,9 @@ func GetDefaultAppConfig() *AppConfig {
|
|||||||
|
|
||||||
// Init 初始化配置文件
|
// Init 初始化配置文件
|
||||||
func Init(cfgFilePath string) {
|
func Init(cfgFilePath string) {
|
||||||
|
if !filepath.IsAbs(cfgFilePath) {
|
||||||
|
cfgFilePath = filepath.Join(utils.GetRootDir(), cfgFilePath)
|
||||||
|
}
|
||||||
currentConfigFilePath = cfgFilePath
|
currentConfigFilePath = cfgFilePath
|
||||||
viper.SetConfigFile(cfgFilePath)
|
viper.SetConfigFile(cfgFilePath)
|
||||||
viper.SetConfigType("json")
|
viper.SetConfigType("json")
|
||||||
@@ -204,7 +209,10 @@ func SaveConfig(appConfig *AppConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if currentConfigFilePath == "" {
|
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 {
|
if err := os.MkdirAll(filepath.Dir(currentConfigFilePath), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"NetworkAuth/utils"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@@ -125,8 +127,13 @@ func validateSQLiteConfig(config *SQLiteConfig) error {
|
|||||||
return errors.New("SQLite数据库路径不能为空")
|
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.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
return fmt.Errorf("创建SQLite数据库目录失败: %w", err)
|
return fmt.Errorf("创建SQLite数据库目录失败: %w", err)
|
||||||
@@ -160,7 +167,11 @@ func validateLogConfig(config *LogConfig) error {
|
|||||||
|
|
||||||
// 检查日志文件目录(仅当日志文件路径不为空时)
|
// 检查日志文件目录(仅当日志文件路径不为空时)
|
||||||
if config.File != "" {
|
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.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
return fmt.Errorf("创建日志目录失败: %w", err)
|
return fmt.Errorf("创建日志目录失败: %w", err)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -103,7 +104,10 @@ func performInitFromViper() error {
|
|||||||
case "sqlite":
|
case "sqlite":
|
||||||
dbPath := cfg.Database.SQLite.Path
|
dbPath := cfg.Database.SQLite.Path
|
||||||
if dbPath == "" {
|
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) {
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||||
logrus.Info("SQLite 数据库文件不存在,系统尚未安装,跳过数据库连接")
|
logrus.Info("SQLite 数据库文件不存在,系统尚未安装,跳过数据库连接")
|
||||||
@@ -209,7 +213,10 @@ func buildGormLogger(level string) gLogger.Interface {
|
|||||||
func initSQLite(sqliteConfig *appconfig.SQLiteConfig, logLevel string) error {
|
func initSQLite(sqliteConfig *appconfig.SQLiteConfig, logLevel string) error {
|
||||||
path := sqliteConfig.Path
|
path := sqliteConfig.Path
|
||||||
if 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)
|
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)})
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: buildGormLogger(logLevel)})
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"NetworkAuth/public"
|
"NetworkAuth/public"
|
||||||
|
"NetworkAuth/utils"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -48,6 +50,9 @@ func registerFrontendRoutes(r *gin.Engine) {
|
|||||||
return // 反向代理接管了所有非 API 路由,直接返回
|
return // 反向代理接管了所有非 API 路由,直接返回
|
||||||
} else {
|
} else {
|
||||||
// 使用本地外部目录
|
// 使用本地外部目录
|
||||||
|
if !filepath.IsAbs(distConfig) {
|
||||||
|
distConfig = filepath.Join(utils.GetRootDir(), distConfig)
|
||||||
|
}
|
||||||
fileServer = http.FileServer(http.Dir(distConfig))
|
fileServer = http.FileServer(http.Dir(distConfig))
|
||||||
|
|
||||||
// 拦截并处理静态资源请求
|
// 拦截并处理静态资源请求
|
||||||
|
|||||||
@@ -74,11 +74,28 @@ func NewClient(baseURL string, proxyStr string, persistCookies bool, timeout int
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.SetTransport(bypass.Transport)
|
rc.SetTransport(&sanitizeTransport{t: bypass.Transport})
|
||||||
|
|
||||||
return &RestyClient{client: rc}
|
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 使用反射强制填充响应体
|
// fillResponseBody 使用反射强制填充响应体
|
||||||
// 当 Resty 因为重定向策略错误而提前返回时,它可能不会读取 Body
|
// 当 Resty 因为重定向策略错误而提前返回时,它可能不会读取 Body
|
||||||
// 此方法手动读取 RawResponse.Body 并回填到 resty.Response 的私有 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