宇恒一号官网
This commit is contained in:
218
server/main.go
Normal file
218
server/main.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yh_web/server/config"
|
||||
"yh_web/server/handlers"
|
||||
"yh_web/server/middleware"
|
||||
"yh_web/server/models"
|
||||
"yh_web/server/pkg/logger"
|
||||
"yh_web/server/pkg/schema"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// loadEnv 启动时自动加载 .env(在 server 目录或项目根/server 下),不覆盖已存在的环境变量
|
||||
func loadEnv() {
|
||||
wd, _ := os.Getwd()
|
||||
serverDir := wd
|
||||
if !strings.HasSuffix(filepath.Clean(wd), "server") {
|
||||
serverDir = filepath.Join(wd, "server")
|
||||
}
|
||||
envPath := filepath.Clean(filepath.Join(serverDir, ".env"))
|
||||
if _, err := os.Stat(envPath); err == nil {
|
||||
if err := godotenv.Load(envPath); err == nil {
|
||||
log.Printf("已加载配置: %s", envPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
loadEnv()
|
||||
|
||||
// 初始化根目录 logs/server(支持从项目根或 server 目录启动)
|
||||
wd, _ := os.Getwd()
|
||||
baseDir := filepath.Join(wd, "logs", "server")
|
||||
if strings.HasSuffix(filepath.Clean(wd), "server") {
|
||||
baseDir = filepath.Join(wd, "..", "logs", "server")
|
||||
}
|
||||
logger.Init(filepath.Clean(baseDir))
|
||||
|
||||
// 连接 MongoDB;URI 从环境变量 MONGODB_URI 读取,默认 mongodb://localhost:27017;SKIP_MONGODB=1 时可跳过
|
||||
if os.Getenv("SKIP_MONGODB") != "1" {
|
||||
mongoURI := os.Getenv("MONGODB_URI")
|
||||
if mongoURI == "" {
|
||||
mongoURI = "mongodb://localhost:27017"
|
||||
}
|
||||
if dbName := os.Getenv("MONGODB_DB"); dbName != "" {
|
||||
config.DBName = dbName
|
||||
}
|
||||
if err := config.ConnectMongoDB(mongoURI); err != nil {
|
||||
logger.Err("main", "MongoDB 连接失败: %v(若仅需健康检查可设置环境变量 SKIP_MONGODB=1 后启动)", err)
|
||||
log.Fatalf("MongoDB 连接失败: %v(若仅需健康检查可设置环境变量 SKIP_MONGODB=1 后启动)", err)
|
||||
}
|
||||
defer config.CloseMongoDB()
|
||||
|
||||
// 启动时获取线上表结构并生成 sql;缺失的集合在线上创建并生成 created_*.sql
|
||||
projectRoot := wd
|
||||
if strings.HasSuffix(filepath.Clean(wd), "server") {
|
||||
projectRoot = filepath.Join(wd, "..")
|
||||
}
|
||||
projectRoot = filepath.Clean(projectRoot)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
if err := schema.Sync(ctx, projectRoot); err != nil {
|
||||
logger.Err("main", "启动时同步表结构失败: %v", err)
|
||||
log.Printf("警告: 启动时同步表结构失败: %v", err)
|
||||
}
|
||||
cancel()
|
||||
} else {
|
||||
logger.Log("main", "已跳过 MongoDB 连接(SKIP_MONGODB=1),仅 /api/health 等不依赖数据库的接口可用")
|
||||
log.Println("已跳过 MongoDB 连接(SKIP_MONGODB=1),仅 /api/health 等不依赖数据库的接口可用")
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
r.Use(middleware.ErrorLogger())
|
||||
|
||||
// CORS(ALLOWED_ORIGINS 为空则允许所有来源;否则仅允许配置的域名)
|
||||
allowedOriginsEnv := os.Getenv("ALLOWED_ORIGINS")
|
||||
r.Use(func(c *gin.Context) {
|
||||
origin := c.GetHeader("Origin")
|
||||
if allowedOriginsEnv != "" {
|
||||
for _, o := range strings.Split(allowedOriginsEnv, ",") {
|
||||
if strings.TrimSpace(o) == origin {
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
// 未连接 MongoDB 时,/api/admin 下所有接口返回 503(健康检查不受影响)
|
||||
r.Use(func(c *gin.Context) {
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/api/admin") && config.MongoClient == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "服务暂不可用,数据库未连接"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
// 健康检查
|
||||
r.GET("/api/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// 登录、注册(无需鉴权)
|
||||
r.POST("/api/admin/login", handlers.Login)
|
||||
r.POST("/api/admin/send-code", handlers.SendCode)
|
||||
r.POST("/api/admin/register", handlers.Register)
|
||||
r.POST("/api/admin/reset-password", handlers.ResetPassword)
|
||||
|
||||
// 后台 API 路由组(需鉴权)
|
||||
admin := r.Group("/api/admin")
|
||||
admin.Use(handlers.AuthRequired())
|
||||
{
|
||||
admin.GET("/info", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "admin api"})
|
||||
})
|
||||
admin.GET("/my-permissions", handlers.GetMyPermissions)
|
||||
admin.GET("/db-structure", func(c *gin.Context) {
|
||||
structure, err := config.GetDBStructure()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, structure)
|
||||
})
|
||||
admin.GET("/stats", handlers.GetStats)
|
||||
|
||||
// 用户管理
|
||||
admin.GET("/users", handlers.RequirePermission(models.PermUserManage), handlers.GetUsers)
|
||||
admin.GET("/users/:id", handlers.RequirePermission(models.PermUserManage), handlers.GetUserByID)
|
||||
admin.POST("/users", handlers.RequirePermission(models.PermUserManage), handlers.CreateUser)
|
||||
admin.PUT("/users/:id", handlers.RequirePermission(models.PermUserManage), handlers.UpdateUser)
|
||||
admin.DELETE("/users/:id", handlers.RequirePermission(models.PermUserManage), handlers.DeleteUser)
|
||||
|
||||
// 工作空间
|
||||
admin.GET("/workspaces", handlers.RequirePermission(models.PermWorkspaceManage), handlers.GetWorkspaces)
|
||||
|
||||
// 对话
|
||||
admin.GET("/conversations", handlers.RequirePermission(models.PermConversationManage), handlers.GetConversations)
|
||||
|
||||
// 站点管理(带子路径的路由放前面;与 :site_id 统一,避免 Gin 路由冲突)
|
||||
admin.GET("/sites/:site_id/homepage/download", handlers.RequirePermission(models.PermHomepageEdit), handlers.DownloadHomepage)
|
||||
admin.GET("/sites/:site_id/homepage", handlers.RequirePermission(models.PermHomepageEdit), handlers.GetHomepage)
|
||||
admin.PUT("/sites/:site_id/homepage", handlers.RequirePermission(models.PermHomepageEdit), handlers.UpdateHomepage)
|
||||
admin.GET("/sites/:site_id/assets", handlers.RequirePermission(models.PermSiteManage), handlers.ListSiteAssets)
|
||||
admin.POST("/sites/:site_id/assets", handlers.RequirePermission(models.PermModuleUpload), handlers.UploadSiteAsset)
|
||||
admin.DELETE("/sites/:site_id/assets/:asset_id", handlers.RequirePermission(models.PermSiteManage), handlers.DeleteSiteAsset)
|
||||
admin.GET("/sites", handlers.RequirePermission(models.PermSiteManage), handlers.GetSites)
|
||||
admin.GET("/sites/:site_id", handlers.RequirePermission(models.PermSiteManage), handlers.GetSiteByID)
|
||||
admin.POST("/sites", handlers.RequirePermission(models.PermSiteManage), handlers.CreateSite)
|
||||
admin.PUT("/sites/:site_id", handlers.RequirePermission(models.PermSiteManage), handlers.UpdateSite)
|
||||
admin.DELETE("/sites/:site_id", handlers.RequirePermission(models.PermSiteManage), handlers.DeleteSite)
|
||||
admin.GET("/system/official-site", handlers.RequirePermission(models.PermSiteManage), handlers.GetOfficialSite)
|
||||
admin.PUT("/system/official-site", handlers.RequirePermission(models.PermSiteManage), handlers.SetOfficialSite)
|
||||
|
||||
// 角色权限管理
|
||||
admin.GET("/role-permissions", handlers.RequirePermission(models.PermRolePermission), handlers.GetRolePermissionsList)
|
||||
admin.PUT("/role-permissions/:role_id", handlers.RequirePermission(models.PermRolePermission), handlers.UpdateRolePermissions)
|
||||
|
||||
// 网页管理(按站点)
|
||||
admin.GET("/pages", handlers.RequirePermission(models.PermPageManage), handlers.GetPages)
|
||||
admin.GET("/pages/:id", handlers.RequirePermission(models.PermPageManage), handlers.GetPageByID)
|
||||
admin.POST("/pages", handlers.RequirePermission(models.PermPageManage), handlers.CreatePage)
|
||||
admin.PUT("/pages/:id", handlers.RequirePermission(models.PermPageManage), handlers.UpdatePage)
|
||||
admin.DELETE("/pages/:id", handlers.RequirePermission(models.PermPageManage), handlers.DeletePage)
|
||||
|
||||
// 短信平台配置
|
||||
smsConfig := admin.Group("/sms-config")
|
||||
smsConfig.Use(handlers.RequirePermission(models.PermSMSConfig))
|
||||
{
|
||||
smsConfig.GET("", handlers.GetSMSConfig)
|
||||
smsConfig.PUT("", handlers.UpdateSMSConfig)
|
||||
}
|
||||
|
||||
// 支付配置(微信、支付宝)
|
||||
paymentConfig := admin.Group("/payment-config")
|
||||
paymentConfig.Use(handlers.RequirePermission(models.PermPaymentConfig))
|
||||
{
|
||||
paymentConfig.GET("", handlers.GetPaymentConfig)
|
||||
paymentConfig.PUT("", handlers.UpdatePaymentConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// 官网站点首页(前台,无需鉴权)
|
||||
r.GET("/api/web/homepage", handlers.GetWebHomepage)
|
||||
|
||||
// 前台 API 路由组
|
||||
web := r.Group("/api/web")
|
||||
{
|
||||
web.GET("/info", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "web api"})
|
||||
})
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
r.Run(":" + port)
|
||||
}
|
||||
Reference in New Issue
Block a user