main.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package main
  2. import (
  3. "bytes"
  4. "embed"
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "github.com/QuantumNous/new-api/common"
  13. "github.com/QuantumNous/new-api/constant"
  14. "github.com/QuantumNous/new-api/controller"
  15. "github.com/QuantumNous/new-api/logger"
  16. "github.com/QuantumNous/new-api/middleware"
  17. "github.com/QuantumNous/new-api/model"
  18. "github.com/QuantumNous/new-api/router"
  19. "github.com/QuantumNous/new-api/service"
  20. "github.com/QuantumNous/new-api/setting/ratio_setting"
  21. // Plugin System
  22. coreregistry "github.com/QuantumNous/new-api/core/registry"
  23. _ "github.com/QuantumNous/new-api/plugins/channels" // 自动注册channel插件
  24. _ "github.com/QuantumNous/new-api/plugins/hooks/web_search" // 自动注册web_search hook
  25. _ "github.com/QuantumNous/new-api/plugins/hooks/content_filter" // 自动注册content_filter hook
  26. relayhooks "github.com/QuantumNous/new-api/relay/hooks"
  27. "github.com/bytedance/gopkg/util/gopool"
  28. "github.com/gin-contrib/sessions"
  29. "github.com/gin-contrib/sessions/cookie"
  30. "github.com/gin-gonic/gin"
  31. "github.com/joho/godotenv"
  32. _ "net/http/pprof"
  33. )
  34. //go:embed web/dist
  35. var buildFS embed.FS
  36. //go:embed web/dist/index.html
  37. var indexPage []byte
  38. func main() {
  39. startTime := time.Now()
  40. err := InitResources()
  41. if err != nil {
  42. common.FatalLog("failed to initialize resources: " + err.Error())
  43. return
  44. }
  45. common.SysLog("New API " + common.Version + " started")
  46. if os.Getenv("GIN_MODE") != "debug" {
  47. gin.SetMode(gin.ReleaseMode)
  48. }
  49. if common.DebugEnabled {
  50. common.SysLog("running in debug mode")
  51. }
  52. defer func() {
  53. err := model.CloseDB()
  54. if err != nil {
  55. common.FatalLog("failed to close database: " + err.Error())
  56. }
  57. }()
  58. if common.RedisEnabled {
  59. // for compatibility with old versions
  60. common.MemoryCacheEnabled = true
  61. }
  62. if common.MemoryCacheEnabled {
  63. common.SysLog("memory cache enabled")
  64. common.SysLog(fmt.Sprintf("sync frequency: %d seconds", common.SyncFrequency))
  65. // Add panic recovery and retry for InitChannelCache
  66. func() {
  67. defer func() {
  68. if r := recover(); r != nil {
  69. common.SysLog(fmt.Sprintf("InitChannelCache panic: %v, retrying once", r))
  70. // Retry once
  71. _, _, fixErr := model.FixAbility()
  72. if fixErr != nil {
  73. common.FatalLog(fmt.Sprintf("InitChannelCache failed: %s", fixErr.Error()))
  74. }
  75. }
  76. }()
  77. model.InitChannelCache()
  78. }()
  79. go model.SyncChannelCache(common.SyncFrequency)
  80. }
  81. // 热更新配置
  82. go model.SyncOptions(common.SyncFrequency)
  83. // 数据看板
  84. go model.UpdateQuotaData()
  85. if os.Getenv("CHANNEL_UPDATE_FREQUENCY") != "" {
  86. frequency, err := strconv.Atoi(os.Getenv("CHANNEL_UPDATE_FREQUENCY"))
  87. if err != nil {
  88. common.FatalLog("failed to parse CHANNEL_UPDATE_FREQUENCY: " + err.Error())
  89. }
  90. go controller.AutomaticallyUpdateChannels(frequency)
  91. }
  92. go controller.AutomaticallyTestChannels()
  93. if common.IsMasterNode && constant.UpdateTask {
  94. gopool.Go(func() {
  95. controller.UpdateMidjourneyTaskBulk()
  96. })
  97. gopool.Go(func() {
  98. controller.UpdateTaskBulk()
  99. })
  100. }
  101. if os.Getenv("BATCH_UPDATE_ENABLED") == "true" {
  102. common.BatchUpdateEnabled = true
  103. common.SysLog("batch update enabled with interval " + strconv.Itoa(common.BatchUpdateInterval) + "s")
  104. model.InitBatchUpdater()
  105. }
  106. if os.Getenv("ENABLE_PPROF") == "true" {
  107. gopool.Go(func() {
  108. log.Println(http.ListenAndServe("0.0.0.0:8005", nil))
  109. })
  110. go common.Monitor()
  111. common.SysLog("pprof enabled")
  112. }
  113. // Initialize HTTP server
  114. server := gin.New()
  115. server.Use(gin.CustomRecovery(func(c *gin.Context, err any) {
  116. common.SysLog(fmt.Sprintf("panic detected: %v", err))
  117. c.JSON(http.StatusInternalServerError, gin.H{
  118. "error": gin.H{
  119. "message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
  120. "type": "new_api_panic",
  121. },
  122. })
  123. }))
  124. // This will cause SSE not to work!!!
  125. //server.Use(gzip.Gzip(gzip.DefaultCompression))
  126. server.Use(middleware.RequestId())
  127. middleware.SetUpLogger(server)
  128. // Initialize session store
  129. store := cookie.NewStore([]byte(common.SessionSecret))
  130. store.Options(sessions.Options{
  131. Path: "/",
  132. MaxAge: 2592000, // 30 days
  133. HttpOnly: true,
  134. Secure: false,
  135. SameSite: http.SameSiteStrictMode,
  136. })
  137. server.Use(sessions.Sessions("session", store))
  138. analyticsInjectBuilder := &strings.Builder{}
  139. if os.Getenv("UMAMI_WEBSITE_ID") != "" {
  140. umamiSiteID := os.Getenv("UMAMI_WEBSITE_ID")
  141. umamiScriptURL := os.Getenv("UMAMI_SCRIPT_URL")
  142. if umamiScriptURL == "" {
  143. umamiScriptURL = "https://analytics.umami.is/script.js"
  144. }
  145. analyticsInjectBuilder.WriteString("<script defer src=\"")
  146. analyticsInjectBuilder.WriteString(umamiScriptURL)
  147. analyticsInjectBuilder.WriteString("\" data-website-id=\"")
  148. analyticsInjectBuilder.WriteString(umamiSiteID)
  149. analyticsInjectBuilder.WriteString("\"></script>")
  150. }
  151. analyticsInject := analyticsInjectBuilder.String()
  152. indexPage = bytes.ReplaceAll(indexPage, []byte("<analytics></analytics>\n"), []byte(analyticsInject))
  153. router.SetRouter(server, buildFS, indexPage)
  154. var port = os.Getenv("PORT")
  155. if port == "" {
  156. port = strconv.Itoa(*common.Port)
  157. }
  158. // Log startup success message
  159. common.LogStartupSuccess(startTime, port)
  160. err = server.Run(":" + port)
  161. if err != nil {
  162. common.FatalLog("failed to start HTTP server: " + err.Error())
  163. }
  164. }
  165. func InitResources() error {
  166. // Initialize resources here if needed
  167. // This is a placeholder function for future resource initialization
  168. err := godotenv.Load(".env")
  169. if err != nil {
  170. if common.DebugEnabled {
  171. common.SysLog("No .env file found, using default environment variables. If needed, please create a .env file and set the relevant variables.")
  172. }
  173. }
  174. // 加载环境变量
  175. common.InitEnv()
  176. logger.SetupLogger()
  177. // Initialize model settings
  178. ratio_setting.InitRatioSettings()
  179. service.InitHttpClient()
  180. service.InitTokenEncoders()
  181. // Initialize SQL Database
  182. err = model.InitDB()
  183. if err != nil {
  184. common.FatalLog("failed to initialize database: " + err.Error())
  185. return err
  186. }
  187. model.CheckSetup()
  188. // Initialize options, should after model.InitDB()
  189. model.InitOptionMap()
  190. // 初始化模型
  191. model.GetPricing()
  192. // Initialize SQL Database
  193. err = model.InitLogDB()
  194. if err != nil {
  195. return err
  196. }
  197. // Initialize Redis
  198. err = common.InitRedisClient()
  199. if err != nil {
  200. return err
  201. }
  202. // Initialize Plugin System
  203. InitPluginSystem()
  204. return nil
  205. }
  206. // InitPluginSystem 初始化插件系统
  207. func InitPluginSystem() {
  208. common.SysLog("Initializing plugin system...")
  209. // 1. 加载插件配置
  210. // config.LoadPluginConfig() 会在各个插件的init()中自动调用
  211. // 2. 注册Channel插件
  212. // 注意:这会在 plugins/channels/registry.go 的 init() 中自动完成
  213. // 但为了确保加载,我们显式导入
  214. common.SysLog("Registering channel plugins...")
  215. // 3. 初始化Hook链
  216. common.SysLog("Initializing hook chain...")
  217. _ = relayhooks.GetGlobalChain()
  218. hookCount := coreregistry.HookCount()
  219. enabledHookCount := coreregistry.EnabledHookCount()
  220. common.SysLog(fmt.Sprintf("Plugin system initialized: %d hooks registered (%d enabled)",
  221. hookCount, enabledHookCount))
  222. channelCount := len(coreregistry.ListChannels())
  223. common.SysLog(fmt.Sprintf("Registered %d channel plugins", channelCount))
  224. }