2023-06-15 09:41:30 +08:00
|
|
|
|
package core
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"chatplus/core/types"
|
2023-07-15 21:52:30 +08:00
|
|
|
|
"chatplus/service/function"
|
2023-06-15 09:41:30 +08:00
|
|
|
|
"chatplus/store/model"
|
|
|
|
|
"chatplus/utils"
|
|
|
|
|
"chatplus/utils/resp"
|
|
|
|
|
"context"
|
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
|
|
|
"github.com/gin-contrib/sessions/cookie"
|
2023-06-27 18:29:46 +08:00
|
|
|
|
"github.com/gin-contrib/sessions/memstore"
|
|
|
|
|
"github.com/gin-contrib/sessions/redis"
|
2023-06-15 09:41:30 +08:00
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"runtime/debug"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AppServer struct {
|
2023-06-27 12:11:55 +08:00
|
|
|
|
Debug bool
|
2023-07-02 20:51:13 +08:00
|
|
|
|
Config *types.AppConfig
|
2023-06-15 09:41:30 +08:00
|
|
|
|
Engine *gin.Engine
|
2023-07-15 18:00:40 +08:00
|
|
|
|
ChatContexts *types.LMap[string, []interface{}] // 聊天上下文 Map [chatId] => []Message
|
2023-07-31 06:56:28 +08:00
|
|
|
|
|
|
|
|
|
ChatConfig *types.ChatConfig // chat config cache
|
|
|
|
|
SysConfig *types.SystemConfig // system config cache
|
2023-06-15 09:41:30 +08:00
|
|
|
|
|
|
|
|
|
// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
|
|
|
|
|
// 防止第三方直接连接 socket 调用 OpenAI API
|
2023-06-16 15:32:11 +08:00
|
|
|
|
ChatSession *types.LMap[string, types.ChatSession] //map[sessionId]UserId
|
2023-07-10 18:59:53 +08:00
|
|
|
|
ChatClients *types.LMap[string, *types.WsClient] // map[sessionId]Websocket 连接集合
|
2023-06-16 15:32:11 +08:00
|
|
|
|
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
2023-07-15 21:52:30 +08:00
|
|
|
|
Functions map[string]function.Function
|
2023-06-15 09:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-15 21:52:30 +08:00
|
|
|
|
func NewServer(
|
|
|
|
|
appConfig *types.AppConfig,
|
|
|
|
|
funZaoBao function.FuncZaoBao,
|
|
|
|
|
funZhiHu function.FuncHeadlines,
|
|
|
|
|
funWeibo function.FuncWeiboHot) *AppServer {
|
2023-06-15 09:41:30 +08:00
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
|
gin.DefaultWriter = io.Discard
|
|
|
|
|
return &AppServer{
|
2023-06-27 12:11:55 +08:00
|
|
|
|
Debug: false,
|
2023-07-02 20:51:13 +08:00
|
|
|
|
Config: appConfig,
|
2023-06-15 09:41:30 +08:00
|
|
|
|
Engine: gin.Default(),
|
2023-07-15 18:00:40 +08:00
|
|
|
|
ChatContexts: types.NewLMap[string, []interface{}](),
|
2023-06-16 15:32:11 +08:00
|
|
|
|
ChatSession: types.NewLMap[string, types.ChatSession](),
|
|
|
|
|
ChatClients: types.NewLMap[string, *types.WsClient](),
|
|
|
|
|
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
2023-07-15 21:52:30 +08:00
|
|
|
|
Functions: map[string]function.Function{
|
|
|
|
|
types.FuncZaoBao: funZaoBao,
|
|
|
|
|
types.FuncWeibo: funWeibo,
|
|
|
|
|
types.FuncHeadLine: funZhiHu,
|
|
|
|
|
},
|
2023-06-15 09:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *AppServer) Init(debug bool) {
|
|
|
|
|
if debug { // 调试模式允许跨域请求 API
|
2023-06-27 12:11:55 +08:00
|
|
|
|
s.Debug = debug
|
2023-06-15 09:41:30 +08:00
|
|
|
|
logger.Info("Enabled debug mode")
|
|
|
|
|
}
|
2023-07-31 06:56:28 +08:00
|
|
|
|
s.Engine.Use(corsMiddleware())
|
2023-07-02 20:51:13 +08:00
|
|
|
|
s.Engine.Use(sessionMiddleware(s.Config))
|
2023-06-15 09:41:30 +08:00
|
|
|
|
s.Engine.Use(authorizeMiddleware(s))
|
|
|
|
|
s.Engine.Use(errorHandler)
|
2023-06-26 18:18:45 +08:00
|
|
|
|
// 添加静态资源访问
|
2023-07-02 20:51:13 +08:00
|
|
|
|
s.Engine.Static("/static", s.Config.StaticDir)
|
2023-06-15 09:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *AppServer) Run(db *gorm.DB) error {
|
|
|
|
|
// load chat config from database
|
2023-07-31 08:13:20 +08:00
|
|
|
|
var chatConfig model.Config
|
|
|
|
|
res := db.Where("marker", "chat").First(&chatConfig)
|
2023-06-15 09:41:30 +08:00
|
|
|
|
if res.Error != nil {
|
|
|
|
|
return res.Error
|
|
|
|
|
}
|
2023-07-31 08:13:20 +08:00
|
|
|
|
err := utils.JsonDecode(chatConfig.Config, &s.ChatConfig)
|
2023-06-15 09:41:30 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-07-31 06:56:28 +08:00
|
|
|
|
// load system configs
|
2023-07-31 08:13:20 +08:00
|
|
|
|
var sysConfig model.Config
|
|
|
|
|
res = db.Where("marker", "system").First(&sysConfig)
|
2023-07-31 06:56:28 +08:00
|
|
|
|
if res.Error != nil {
|
|
|
|
|
return res.Error
|
|
|
|
|
}
|
2023-07-31 08:13:20 +08:00
|
|
|
|
err = utils.JsonDecode(sysConfig.Config, &s.SysConfig)
|
2023-07-31 06:56:28 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-07-02 20:51:13 +08:00
|
|
|
|
logger.Infof("http://%s", s.Config.Listen)
|
|
|
|
|
return s.Engine.Run(s.Config.Listen)
|
2023-06-15 09:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 全局异常处理
|
|
|
|
|
func errorHandler(c *gin.Context) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
2023-07-02 20:51:13 +08:00
|
|
|
|
logger.Errorf("Handler Panic: %v", r)
|
2023-06-15 09:41:30 +08:00
|
|
|
|
debug.PrintStack()
|
|
|
|
|
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: types.ErrorMsg})
|
|
|
|
|
c.Abort()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
//加载完 defer recover,继续后续接口调用
|
|
|
|
|
c.Next()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 会话处理
|
|
|
|
|
func sessionMiddleware(config *types.AppConfig) gin.HandlerFunc {
|
|
|
|
|
// encrypt the cookie
|
2023-06-27 18:29:46 +08:00
|
|
|
|
var store sessions.Store
|
|
|
|
|
var err error
|
|
|
|
|
switch config.Session.Driver {
|
|
|
|
|
case types.SessionDriverMem:
|
|
|
|
|
store = memstore.NewStore([]byte(config.Session.SecretKey))
|
|
|
|
|
break
|
|
|
|
|
case types.SessionDriverRedis:
|
|
|
|
|
store, err = redis.NewStore(10, "tcp", config.Redis.Url(), config.Redis.Password, []byte(config.Session.SecretKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case types.SessionDriverCookie:
|
|
|
|
|
store = cookie.NewStore([]byte(config.Session.SecretKey))
|
|
|
|
|
break
|
|
|
|
|
default:
|
2023-06-28 05:51:55 +08:00
|
|
|
|
config.Session.Driver = types.SessionDriverCookie
|
2023-06-27 18:29:46 +08:00
|
|
|
|
store = cookie.NewStore([]byte(config.Session.SecretKey))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.Info("Session driver: ", config.Session.Driver)
|
|
|
|
|
|
2023-06-15 09:41:30 +08:00
|
|
|
|
store.Options(sessions.Options{
|
|
|
|
|
Path: config.Session.Path,
|
|
|
|
|
Domain: config.Session.Domain,
|
|
|
|
|
MaxAge: config.Session.MaxAge,
|
|
|
|
|
Secure: config.Session.Secure,
|
|
|
|
|
HttpOnly: config.Session.HttpOnly,
|
|
|
|
|
SameSite: config.Session.SameSite,
|
|
|
|
|
})
|
|
|
|
|
return sessions.Sessions(config.Session.Name, store)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 跨域中间件设置
|
|
|
|
|
func corsMiddleware() gin.HandlerFunc {
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
method := c.Request.Method
|
|
|
|
|
origin := c.Request.Header.Get("Origin")
|
|
|
|
|
if origin != "" {
|
|
|
|
|
// 设置允许的请求源
|
|
|
|
|
c.Header("Access-Control-Allow-Origin", origin)
|
|
|
|
|
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
|
|
|
|
//允许跨域设置可以返回其他子段,可以自定义字段
|
2023-06-19 07:06:59 +08:00
|
|
|
|
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, ChatGPT-TOKEN, ADMIN-SESSION-TOKEN")
|
2023-06-15 09:41:30 +08:00
|
|
|
|
// 允许浏览器(客户端)可以解析的头部 (重要)
|
|
|
|
|
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
|
|
|
|
|
//设置缓存时间
|
|
|
|
|
c.Header("Access-Control-Max-Age", "172800")
|
|
|
|
|
//允许客户端传递校验信息比如 cookie (重要)
|
|
|
|
|
c.Header("Access-Control-Allow-Credentials", "true")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if method == http.MethodOptions {
|
|
|
|
|
c.JSON(http.StatusOK, "ok!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
|
logger.Info("Panic info is: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
c.Next()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用户授权验证
|
|
|
|
|
func authorizeMiddleware(s *AppServer) gin.HandlerFunc {
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
if c.Request.URL.Path == "/api/user/login" ||
|
2023-06-19 07:06:59 +08:00
|
|
|
|
c.Request.URL.Path == "/api/admin/login" ||
|
2023-06-15 09:41:30 +08:00
|
|
|
|
c.Request.URL.Path == "/api/user/register" ||
|
2023-08-10 17:24:23 +08:00
|
|
|
|
c.Request.URL.Path == "/api/reward/push" ||
|
2023-07-27 10:53:14 +08:00
|
|
|
|
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
|
|
|
|
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
2023-06-27 18:29:46 +08:00
|
|
|
|
strings.HasPrefix(c.Request.URL.Path, "/static/") ||
|
2023-06-21 14:22:28 +08:00
|
|
|
|
c.Request.URL.Path == "/api/admin/config/get" {
|
2023-06-15 09:41:30 +08:00
|
|
|
|
c.Next()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WebSocket 连接请求验证
|
|
|
|
|
if c.Request.URL.Path == "/api/chat" {
|
|
|
|
|
sessionId := c.Query("sessionId")
|
2023-06-16 15:32:11 +08:00
|
|
|
|
session := s.ChatSession.Get(sessionId)
|
|
|
|
|
if session.ClientIP == c.ClientIP() {
|
2023-06-15 09:41:30 +08:00
|
|
|
|
c.Next()
|
|
|
|
|
} else {
|
|
|
|
|
c.Abort()
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
session := sessions.Default(c)
|
2023-06-19 07:06:59 +08:00
|
|
|
|
var value interface{}
|
2023-07-03 15:18:15 +08:00
|
|
|
|
if strings.Contains(c.Request.URL.Path, "/api/admin/") { // 后台管理 API
|
2023-06-19 07:06:59 +08:00
|
|
|
|
value = session.Get(types.SessionAdmin)
|
|
|
|
|
} else {
|
|
|
|
|
value = session.Get(types.SessionUser)
|
|
|
|
|
}
|
2023-06-15 09:41:30 +08:00
|
|
|
|
if value != nil {
|
|
|
|
|
c.Next()
|
|
|
|
|
} else {
|
|
|
|
|
resp.NotAuth(c)
|
|
|
|
|
c.Abort()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|