宇恒一号官网

This commit is contained in:
whm
2026-03-17 00:59:32 +08:00
commit eb56519df7
105 changed files with 10783 additions and 0 deletions

218
server/main.go Normal file
View 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))
// 连接 MongoDBURI 从环境变量 MONGODB_URI 读取,默认 mongodb://localhost:27017SKIP_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())
// CORSALLOWED_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)
}