修复订阅无法正常获取的错误
This commit is contained in:
@@ -12,6 +12,5 @@ REDIS_DB=0
|
||||
JWT_SECRET=change_me_to_a_long_random_secret
|
||||
APP_PORT=8080
|
||||
APP_URL=http://127.0.0.1:8080
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Plugin source reference directory, only needed during development.
|
||||
PLUGIN_ROOT=reference/LDNET-GA-Theme/plugin
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -16,23 +17,42 @@ import (
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
configureRuntimeLogging()
|
||||
database.InitDB()
|
||||
database.InitCache()
|
||||
|
||||
router := gin.New()
|
||||
router.Use(gin.Logger(), gin.Recovery())
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
|
||||
router.Use(gin.Logger())
|
||||
}
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
api := router.Group("/api")
|
||||
registerV1(api.Group("/v1"))
|
||||
registerV2(api.Group("/v2"))
|
||||
registerWebRoutes(router)
|
||||
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
|
||||
log.Printf("server starting on port %s", config.AppConfig.AppPort)
|
||||
}
|
||||
if err := router.Run(":" + config.AppConfig.AppPort); err != nil {
|
||||
log.Fatalf("failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func configureRuntimeLogging() {
|
||||
if config.NormalizeLogLevel(config.AppConfig.LogLevel) == "debug" {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
if config.NormalizeLogLevel(config.AppConfig.LogLevel) == "silent" {
|
||||
gin.DefaultWriter = io.Discard
|
||||
gin.DefaultErrorWriter = io.Discard
|
||||
}
|
||||
}
|
||||
|
||||
func registerV1(v1 *gin.RouterGroup) {
|
||||
registerPassportRoutes(v1)
|
||||
registerGuestRoutes(v1)
|
||||
@@ -288,8 +308,10 @@ func registerWebRoutes(router *gin.Engine) {
|
||||
}
|
||||
|
||||
securePath := "/" + service.GetAdminSecurePath()
|
||||
subscribePath := "/" + service.GetSubscribePath()
|
||||
router.GET("/", handler.UserThemePage)
|
||||
router.GET("/dashboard", handler.UserThemePage)
|
||||
router.GET(subscribePath+"/:token", handler.Subscribe)
|
||||
router.GET(securePath, handler.AdminAppPage)
|
||||
router.GET(securePath+"/", handler.AdminAppPage)
|
||||
router.GET(securePath+"/plugin-panel/:kind", handler.AdminPluginPanelPage)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
@@ -21,6 +22,7 @@ type Config struct {
|
||||
JWTSecret string
|
||||
AppPort string
|
||||
AppURL string
|
||||
LogLevel string
|
||||
PluginRoot string
|
||||
}
|
||||
|
||||
@@ -45,10 +47,47 @@ func LoadConfig() {
|
||||
JWTSecret: getEnv("JWT_SECRET", "secret"),
|
||||
AppPort: getEnv("APP_PORT", "8080"),
|
||||
AppURL: getEnv("APP_URL", ""),
|
||||
LogLevel: NormalizeLogLevel(getEnv("LOG_LEVEL", "info")),
|
||||
PluginRoot: getEnv("PLUGIN_ROOT", "reference\\LDNET-GA-Theme\\plugin"),
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeLogLevel(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "debug":
|
||||
return "debug"
|
||||
case "warn", "warning":
|
||||
return "warn"
|
||||
case "error":
|
||||
return "error"
|
||||
case "silent":
|
||||
return "silent"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
}
|
||||
|
||||
func IsLogLevelEnabled(currentLevel, targetLevel string) bool {
|
||||
logLevelOrder := map[string]int{
|
||||
"debug": 0,
|
||||
"info": 1,
|
||||
"warn": 2,
|
||||
"error": 3,
|
||||
"silent": 4,
|
||||
}
|
||||
|
||||
currentRank, ok := logLevelOrder[NormalizeLogLevel(currentLevel)]
|
||||
if !ok {
|
||||
currentRank = logLevelOrder["info"]
|
||||
}
|
||||
targetRank, ok := logLevelOrder[NormalizeLogLevel(targetLevel)]
|
||||
if !ok {
|
||||
targetRank = logLevelOrder["info"]
|
||||
}
|
||||
|
||||
return currentRank <= targetRank
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
|
||||
@@ -30,7 +30,9 @@ var fallbackCache = &memoryCache{
|
||||
|
||||
func InitCache() {
|
||||
if config.AppConfig.RedisHost == "" {
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
|
||||
log.Printf("Redis host not configured, using in-memory cache")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,13 +48,17 @@ func InitCache() {
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
|
||||
log.Printf("Redis unavailable at %s, falling back to in-memory: %v", addr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Redis = client
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
|
||||
log.Printf("Redis connection established at %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func CacheSet(key string, value any, ttl time.Duration) error {
|
||||
payload, err := json.Marshal(value)
|
||||
|
||||
@@ -24,7 +24,7 @@ func InitDB() {
|
||||
|
||||
var err error
|
||||
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
Logger: logger.Default.LogMode(gormLogLevel(config.AppConfig.LogLevel)),
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
|
||||
@@ -40,5 +40,22 @@ func InitDB() {
|
||||
log.Fatalf("Failed to migrate database tables: %v", err)
|
||||
}
|
||||
|
||||
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
|
||||
log.Println("Database connection established")
|
||||
}
|
||||
}
|
||||
|
||||
func gormLogLevel(level string) logger.LogLevel {
|
||||
switch config.NormalizeLogLevel(level) {
|
||||
case "debug", "info":
|
||||
return logger.Info
|
||||
case "warn":
|
||||
return logger.Warn
|
||||
case "error":
|
||||
return logger.Error
|
||||
case "silent":
|
||||
return logger.Silent
|
||||
default:
|
||||
return logger.Info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ func getAllConfigMappings() gin.H {
|
||||
"show_protocol_to_server_enable": service.MustGetBool("show_protocol_to_server_enable", false),
|
||||
"default_remind_expire": service.MustGetBool("default_remind_expire", true),
|
||||
"default_remind_traffic": service.MustGetBool("default_remind_traffic", true),
|
||||
"subscribe_path": service.MustGetString("subscribe_path", "s"),
|
||||
"subscribe_path": service.GetSubscribePath(),
|
||||
},
|
||||
"subscribe_template": service.GetAllSubscribeTemplates(),
|
||||
"frontend": gin.H{
|
||||
|
||||
147
internal/handler/request_url.go
Normal file
147
internal/handler/request_url.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"xboard-go/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func requestBaseURL(c *gin.Context) string {
|
||||
if c != nil {
|
||||
if origin := requestOrigin(c.Request); origin != "" {
|
||||
return origin
|
||||
}
|
||||
}
|
||||
if appURL := service.GetAppURL(); appURL != "" {
|
||||
return strings.TrimRight(appURL, "/")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func requestOrigin(r *http.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
scheme, host := forwardedOrigin(r)
|
||||
if scheme != "" && host != "" {
|
||||
return scheme + "://" + host
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = firstHeaderValue(r.Header.Get("X-Forwarded-Host"))
|
||||
}
|
||||
if host == "" {
|
||||
host = strings.TrimSpace(r.Host)
|
||||
}
|
||||
if host == "" {
|
||||
host = originHostFallback(r)
|
||||
}
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if scheme == "" {
|
||||
scheme = firstHeaderValue(r.Header.Get("X-Forwarded-Proto"))
|
||||
}
|
||||
if scheme == "" {
|
||||
scheme = firstHeaderValue(r.Header.Get("X-Forwarded-Scheme"))
|
||||
}
|
||||
if scheme == "" {
|
||||
scheme = firstHeaderValue(r.Header.Get("X-Scheme"))
|
||||
}
|
||||
if scheme == "" && strings.EqualFold(strings.TrimSpace(r.Header.Get("Front-End-Https")), "on") {
|
||||
scheme = "https"
|
||||
}
|
||||
if scheme == "" && strings.EqualFold(strings.TrimSpace(r.Header.Get("X-Forwarded-Ssl")), "on") {
|
||||
scheme = "https"
|
||||
}
|
||||
if scheme == "" {
|
||||
switch strings.TrimSpace(firstHeaderValue(r.Header.Get("X-Forwarded-Port"))) {
|
||||
case "443":
|
||||
scheme = "https"
|
||||
case "80":
|
||||
scheme = "http"
|
||||
}
|
||||
}
|
||||
if scheme == "" {
|
||||
scheme = originSchemeFallback(r)
|
||||
}
|
||||
if scheme == "" {
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
} else {
|
||||
scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
if forwardedPort := strings.TrimSpace(firstHeaderValue(r.Header.Get("X-Forwarded-Port"))); forwardedPort != "" && !strings.Contains(host, ":") {
|
||||
defaultPort := map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}[scheme]
|
||||
if forwardedPort != defaultPort {
|
||||
host = net.JoinHostPort(host, forwardedPort)
|
||||
}
|
||||
}
|
||||
|
||||
return scheme + "://" + host
|
||||
}
|
||||
|
||||
func forwardedOrigin(r *http.Request) (scheme string, host string) {
|
||||
forwarded := strings.TrimSpace(firstHeaderValue(r.Header.Get("Forwarded")))
|
||||
if forwarded == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
for _, segment := range strings.Split(forwarded, ";") {
|
||||
parts := strings.SplitN(strings.TrimSpace(segment), "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(parts[0]))
|
||||
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
|
||||
switch key {
|
||||
case "proto":
|
||||
if value != "" {
|
||||
scheme = value
|
||||
}
|
||||
case "host":
|
||||
if value != "" {
|
||||
host = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scheme, host
|
||||
}
|
||||
|
||||
func firstHeaderValue(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(value, ",")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
func originHostFallback(r *http.Request) string {
|
||||
for _, raw := range []string{r.Header.Get("Origin"), r.Header.Get("Referer")} {
|
||||
if parsed, err := url.Parse(strings.TrimSpace(raw)); err == nil && parsed.Host != "" {
|
||||
return parsed.Host
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func originSchemeFallback(r *http.Request) string {
|
||||
for _, raw := range []string{r.Header.Get("Origin"), r.Header.Get("Referer")} {
|
||||
if parsed, err := url.Parse(strings.TrimSpace(raw)); err == nil && parsed.Scheme != "" {
|
||||
return parsed.Scheme
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -74,14 +74,8 @@ func UserGetSubscribe(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
baseURL := strings.TrimRight(service.GetAppURL(), "/")
|
||||
if baseURL == "" {
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
baseURL = scheme + "://" + c.Request.Host
|
||||
}
|
||||
baseURL := requestBaseURL(c)
|
||||
subscribePath := service.GetSubscribePath()
|
||||
|
||||
Success(c, gin.H{
|
||||
"plan_id": user.PlanID,
|
||||
@@ -96,7 +90,7 @@ func UserGetSubscribe(c *gin.Context) {
|
||||
"speed_limit": user.SpeedLimit,
|
||||
"next_reset_at": user.NextResetAt,
|
||||
"plan": user.Plan,
|
||||
"subscribe_url": strings.TrimRight(baseURL, "/") + "/s/" + user.Token,
|
||||
"subscribe_url": strings.TrimRight(baseURL, "/") + "/" + subscribePath + "/" + user.Token,
|
||||
"reset_day": nil,
|
||||
})
|
||||
}
|
||||
@@ -129,15 +123,8 @@ func UserResetSecurity(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := strings.TrimRight(service.GetAppURL(), "/")
|
||||
if baseURL == "" {
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
baseURL = scheme + "://" + c.Request.Host
|
||||
}
|
||||
Success(c, strings.TrimRight(baseURL, "/")+"/s/"+newToken)
|
||||
baseURL := requestBaseURL(c)
|
||||
Success(c, strings.TrimRight(baseURL, "/")+"/"+service.GetSubscribePath()+"/"+newToken)
|
||||
}
|
||||
|
||||
func UserUpdate(c *gin.Context) {
|
||||
|
||||
@@ -726,14 +726,10 @@ func quickLoginURL(c *gin.Context, userID int, redirectValues ...string) string
|
||||
}
|
||||
|
||||
func baseURL(c *gin.Context) string {
|
||||
if appURL := service.GetAppURL(); appURL != "" {
|
||||
return strings.TrimRight(appURL, "/")
|
||||
if baseURL := requestBaseURL(c); baseURL != "" {
|
||||
return baseURL
|
||||
}
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
return scheme + "://" + c.Request.Host
|
||||
return ""
|
||||
}
|
||||
|
||||
func generateTradeNo() string {
|
||||
|
||||
@@ -4,11 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"xboard-go/internal/service"
|
||||
|
||||
@@ -65,15 +63,10 @@ func UserThemePage(c *gin.Context) {
|
||||
|
||||
func AdminAppPage(c *gin.Context) {
|
||||
securePath := service.GetAdminSecurePath()
|
||||
baseURL := service.GetAppURL()
|
||||
if origin := requestOrigin(c.Request); origin != "" {
|
||||
baseURL = origin
|
||||
}
|
||||
|
||||
title := service.MustGetString("app_name", "XBoard") + " Admin"
|
||||
|
||||
settings := map[string]string{
|
||||
"base_url": baseURL,
|
||||
"base_url": requestBaseURL(c),
|
||||
"title": title,
|
||||
"version": service.MustGetString("app_version", "1.0.0"),
|
||||
"logo": service.MustGetString("logo", ""),
|
||||
@@ -113,38 +106,3 @@ func mustJSON(value any) template.JS {
|
||||
}
|
||||
return template.JS(payload)
|
||||
}
|
||||
|
||||
func requestOrigin(r *http.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
scheme := r.Header.Get("X-Forwarded-Proto")
|
||||
if scheme == "" {
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
} else {
|
||||
scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
host := r.Header.Get("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = r.Host
|
||||
}
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if forwardedPort := r.Header.Get("X-Forwarded-Port"); forwardedPort != "" && !strings.Contains(host, ":") {
|
||||
defaultPort := map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}[scheme]
|
||||
if forwardedPort != "" && forwardedPort != defaultPort {
|
||||
host = net.JoinHostPort(host, forwardedPort)
|
||||
}
|
||||
}
|
||||
|
||||
return scheme + "://" + host
|
||||
}
|
||||
|
||||
@@ -79,6 +79,13 @@ func GetAdminSecurePath() string {
|
||||
return "admin"
|
||||
}
|
||||
|
||||
func GetSubscribePath() string {
|
||||
if subscribePath := strings.Trim(normalizeWrappedString(MustGetString("subscribe_path", "")), "/"); subscribePath != "" {
|
||||
return subscribePath
|
||||
}
|
||||
return "s"
|
||||
}
|
||||
|
||||
func GetAppURL() string {
|
||||
if appURL := normalizeWrappedString(MustGetString("app_url", "")); appURL != "" {
|
||||
return strings.TrimRight(appURL, "/")
|
||||
|
||||
Reference in New Issue
Block a user