🎨 优化项目结构 && 完善ai配置

This commit is contained in:
2026-03-03 15:39:23 +08:00
parent 557c865948
commit 2714e63d2a
585 changed files with 62223 additions and 100018 deletions

View File

@@ -0,0 +1,112 @@
package initialize
import (
"context"
"git.echol.cn/loser/ai_proxy/server/model/example"
sysModel "git.echol.cn/loser/ai_proxy/server/model/system"
"git.echol.cn/loser/ai_proxy/server/service/system"
adapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/gorm"
)
const initOrderEnsureTables = system.InitOrderExternal - 1
type ensureTables struct{}
// auto run
func init() {
system.RegisterInit(initOrderEnsureTables, &ensureTables{})
}
func (e *ensureTables) InitializerName() string {
return "ensure_tables_created"
}
func (e *ensureTables) InitializeData(ctx context.Context) (next context.Context, err error) {
return ctx, nil
}
func (e *ensureTables) DataInserted(ctx context.Context) bool {
return true
}
func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error) {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return ctx, system.ErrMissingDBContext
}
tables := []interface{}{
sysModel.SysApi{},
sysModel.SysUser{},
sysModel.SysBaseMenu{},
sysModel.SysAuthority{},
sysModel.JwtBlacklist{},
sysModel.SysDictionary{},
sysModel.SysAutoCodeHistory{},
sysModel.SysOperationRecord{},
sysModel.SysDictionaryDetail{},
sysModel.SysBaseMenuParameter{},
sysModel.SysBaseMenuBtn{},
sysModel.SysAuthorityBtn{},
sysModel.SysAutoCodePackage{},
sysModel.SysExportTemplate{},
sysModel.Condition{},
sysModel.JoinTemplate{},
sysModel.SysParams{},
sysModel.SysVersion{},
sysModel.SysError{},
sysModel.SysLoginLog{},
sysModel.SysApiToken{},
adapter.CasbinRule{},
example.ExaFile{},
example.ExaCustomer{},
example.ExaFileChunk{},
example.ExaFileUploadAndDownload{},
example.ExaAttachmentCategory{},
}
for _, t := range tables {
_ = db.AutoMigrate(&t)
// 视图 authority_menu 会被当成表来创建引发冲突错误更新版本的gorm似乎不会
// 由于 AutoMigrate() 基本无需考虑错误,因此显式忽略
}
return ctx, nil
}
func (e *ensureTables) TableCreated(ctx context.Context) bool {
db, ok := ctx.Value("db").(*gorm.DB)
if !ok {
return false
}
tables := []interface{}{
sysModel.SysApi{},
sysModel.SysUser{},
sysModel.SysBaseMenu{},
sysModel.SysAuthority{},
sysModel.JwtBlacklist{},
sysModel.SysDictionary{},
sysModel.SysAutoCodeHistory{},
sysModel.SysOperationRecord{},
sysModel.SysDictionaryDetail{},
sysModel.SysBaseMenuParameter{},
sysModel.SysBaseMenuBtn{},
sysModel.SysAuthorityBtn{},
sysModel.SysAutoCodePackage{},
sysModel.SysExportTemplate{},
sysModel.Condition{},
sysModel.JoinTemplate{},
adapter.CasbinRule{},
example.ExaFile{},
example.ExaCustomer{},
example.ExaFileChunk{},
example.ExaFileUploadAndDownload{},
example.ExaAttachmentCategory{},
}
yes := true
for _, t := range tables {
yes = yes && db.Migrator().HasTable(t)
}
return yes
}

View File

@@ -4,11 +4,10 @@ import (
"os"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/model/app"
"git.echol.cn/loser/ai_proxy/server/model/example"
"git.echol.cn/loser/ai_proxy/server/model/system"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
@@ -44,15 +43,34 @@ func RegisterTables() {
db := global.GVA_DB
err := db.AutoMigrate(
// System tables (管理后台表)
system.SysApi{},
system.SysIgnoreApi{},
system.SysUser{},
system.SysBaseMenu{},
system.JwtBlacklist{},
system.SysAuthority{},
system.SysDictionary{},
system.SysOperationRecord{},
system.SysAutoCodeHistory{},
system.SysDictionaryDetail{},
system.SysBaseMenuParameter{},
system.SysBaseMenuBtn{},
system.SysAuthorityBtn{},
system.SysAutoCodePackage{},
system.SysExportTemplate{},
system.Condition{},
system.JoinTemplate{},
system.SysParams{},
system.SysVersion{},
system.SysError{},
system.SysApiToken{},
system.SysLoginLog{},
// App tables (前台应用表)
app.AiPreset{},
app.AiProvider{},
app.AiPresetBinding{},
app.AiRequestLog{},
example.ExaFile{},
example.ExaCustomer{},
example.ExaFileChunk{},
example.ExaFileUploadAndDownload{},
example.ExaAttachmentCategory{},
)
if err != nil {
global.GVA_LOG.Error("register table failed", zap.Error(err))
@@ -66,47 +84,4 @@ func RegisterTables() {
os.Exit(0)
}
global.GVA_LOG.Info("register table success")
// 初始化默认管理员账号
initDefaultAdmin()
}
// initDefaultAdmin 初始化默认管理员账号
func initDefaultAdmin() {
var count int64
err := global.GVA_DB.Model(&system.SysUser{}).Where("username = ?", "root").Count(&count).Error
if err != nil {
global.GVA_LOG.Error("check admin user failed", zap.Error(err))
return
}
// 如果已存在 root 用户,则跳过
if count > 0 {
global.GVA_LOG.Info("default admin user already exists, skip initialization")
return
}
// 加密密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("root123"), bcrypt.DefaultCost)
if err != nil {
global.GVA_LOG.Error("hash password failed", zap.Error(err))
return
}
// 创建默认管理员账号
adminUser := system.SysUser{
Username: "root",
Password: string(hashedPassword),
Nickname: "超级管理员",
Email: "admin@example.com",
Role: "admin",
Status: "active",
}
if err := global.GVA_DB.Create(&adminUser).Error; err != nil {
global.GVA_LOG.Error("create default admin user failed", zap.Error(err))
return
}
global.GVA_LOG.Info("default admin user created successfully (username: root, password: root123)")
}

View File

@@ -2,11 +2,12 @@ package initialize
import (
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/model/app"
)
func bizModel() error {
db := global.GVA_DB
err := db.AutoMigrate()
err := db.AutoMigrate(app.AutoMigrateTables...)
if err != nil {
return err
}

View File

@@ -2,7 +2,6 @@ package internal
import (
"fmt"
"git.echol.cn/loser/ai_proxy/server/config"
"git.echol.cn/loser/ai_proxy/server/global"
"gorm.io/gorm/logger"

View File

@@ -3,7 +3,6 @@ package internal
import (
"context"
"fmt"
"github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/event"
opt "go.mongodb.org/mongo-driver/mongo/options"

25
server/initialize/mcp.go Normal file
View File

@@ -0,0 +1,25 @@
package initialize
import (
"git.echol.cn/loser/ai_proxy/server/global"
mcpTool "git.echol.cn/loser/ai_proxy/server/mcp"
"github.com/mark3labs/mcp-go/server"
)
func McpRun() *server.SSEServer {
config := global.GVA_CONFIG.MCP
s := server.NewMCPServer(
config.Name,
config.Version,
)
global.GVA_MCP_SERVER = s
mcpTool.RegisterAllTools(s)
return server.NewSSEServer(s,
server.WithSSEEndpoint(config.SSEPath),
server.WithMessageEndpoint(config.MessagePath),
server.WithBaseURL(config.UrlPrefix))
}

View File

@@ -3,9 +3,6 @@ package initialize
import (
"context"
"fmt"
"sort"
"strings"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/initialize/internal"
"git.echol.cn/loser/ai_proxy/server/utils"
@@ -14,6 +11,8 @@ import (
"github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/bson"
option "go.mongodb.org/mongo-driver/mongo/options"
"sort"
"strings"
)
var Mongo = new(mongo)

View File

@@ -2,11 +2,10 @@ package initialize
import (
"bufio"
"github.com/songzhibin97/gkit/cache/local_cache"
"os"
"strings"
"github.com/songzhibin97/gkit/cache/local_cache"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/utils"
)

View File

@@ -0,0 +1,15 @@
package initialize
import (
"git.echol.cn/loser/ai_proxy/server/global"
"github.com/gin-gonic/gin"
)
func InstallPlugin(PrivateGroup *gin.RouterGroup, PublicRouter *gin.RouterGroup, engine *gin.Engine) {
if global.GVA_DB == nil {
global.GVA_LOG.Info("项目暂未初始化,无法安装插件,初始化后重启项目即可完成插件安装")
return
}
bizPluginV1(PrivateGroup, PublicRouter)
bizPluginV2(engine)
}

View File

@@ -0,0 +1,36 @@
package initialize
import (
"fmt"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/plugin/email"
"git.echol.cn/loser/ai_proxy/server/utils/plugin"
"github.com/gin-gonic/gin"
)
func PluginInit(group *gin.RouterGroup, Plugin ...plugin.Plugin) {
for i := range Plugin {
fmt.Println(Plugin[i].RouterPath(), "注册开始!")
PluginGroup := group.Group(Plugin[i].RouterPath())
Plugin[i].Register(PluginGroup)
fmt.Println(Plugin[i].RouterPath(), "注册成功!")
}
}
func bizPluginV1(group ...*gin.RouterGroup) {
private := group[0]
public := group[1]
// 添加跟角色挂钩权限的插件 示例 本地示例模式于在线仓库模式注意上方的import 可以自行切换 效果相同
PluginInit(private, email.CreateEmailPlug(
global.GVA_CONFIG.Email.To,
global.GVA_CONFIG.Email.From,
global.GVA_CONFIG.Email.Host,
global.GVA_CONFIG.Email.Secret,
global.GVA_CONFIG.Email.Nickname,
global.GVA_CONFIG.Email.Port,
global.GVA_CONFIG.Email.IsSSL,
global.GVA_CONFIG.Email.IsLoginAuth,
))
holder(public, private)
}

View File

@@ -0,0 +1,16 @@
package initialize
import (
_ "git.echol.cn/loser/ai_proxy/server/plugin"
"git.echol.cn/loser/ai_proxy/server/utils/plugin/v2"
"github.com/gin-gonic/gin"
)
func PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) {
for i := 0; i < len(plugins); i++ {
plugins[i].Register(group)
}
}
func bizPluginV2(engine *gin.Engine) {
PluginInitV2(engine, plugin.Registered()...)
}

View File

@@ -1,5 +1,10 @@
package initialize
import (
_ "git.echol.cn/loser/ai_proxy/server/source/example"
_ "git.echol.cn/loser/ai_proxy/server/source/system"
)
func init() {
// do nothing, only import source package so that inits can be registered
// do nothing,only import source package so that inits can be registered
}

View File

@@ -6,6 +6,7 @@ import (
"git.echol.cn/loser/ai_proxy/server/docs"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/middleware"
"git.echol.cn/loser/ai_proxy/server/router"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@@ -30,77 +31,101 @@ func (fs justFilesFilesystem) Open(name string) (http.File, error) {
return f, nil
}
// Routers 初始化总路由
// 初始化总路由
func Routers() *gin.Engine {
Router := gin.New()
// 设置文件上传大小限制10MB
Router.MaxMultipartMemory = 10 << 20
// 使用 Gin 默认的 Recovery 中间件
Router.Use(gin.Recovery())
// 使用自定义的 Recovery 中间件,记录 panic 并入库
Router.Use(middleware.GinRecovery(true))
if gin.Mode() == gin.DebugMode {
Router.Use(gin.Logger())
}
// 简单的 CORS 中间件
Router.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, x-token")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if !global.GVA_CONFIG.MCP.Separate {
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
sseServer := McpRun()
c.Next()
})
// 注册mcp服务
Router.GET(global.GVA_CONFIG.MCP.SSEPath, func(c *gin.Context) {
sseServer.SSEHandler().ServeHTTP(c.Writer, c.Request)
})
Router.POST(global.GVA_CONFIG.MCP.MessagePath, func(c *gin.Context) {
sseServer.MessageHandler().ServeHTTP(c.Writer, c.Request)
})
}
systemRouter := router.RouterGroupApp.System
appRouter := router.RouterGroupApp.App
exampleRouter := router.RouterGroupApp.Example
// 如果想要不使用nginx代理前端网页可以修改 web/.env.production 下的
// VUE_APP_BASE_API = /
// VUE_APP_BASE_PATH = http://localhost
// 然后执行打包命令 npm run build。在打开下面3行注释
// Router.StaticFile("/favicon.ico", "./dist/favicon.ico")
// Router.Static("/assets", "./dist/assets") // dist里面的静态资源
// Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面
// 静态文件服务
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)})
// Swagger 文档
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件")
// 跨域,如需跨域可以打开下面的注释
// Router.Use(middleware.Cors()) // 直接放行全部跨域请求
// Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求
// global.GVA_LOG.Info("use middleware cors")
docs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix
Router.GET(global.GVA_CONFIG.System.RouterPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
global.GVA_LOG.Info("register swagger handler")
// 方便统一添加路由组前缀 多服务器上线使用
// 路由组
PublicGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
PrivateGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
{
// 健康检查
// 健康监测
PublicGroup.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, "ok")
})
}
// 系统管理路由
{
systemGroup := PublicGroup.Group("v1/system")
systemRouter.UserRouter.InitUserRouter(systemGroup) // 用户管理路由:/v1/system/user/*
systemRouter.ApiRouter.InitApiRouter(systemGroup) // API管理路由/v1/system/api/*
systemRouter.InitBaseRouter(PublicGroup) // 注册基础功能路由 不做鉴权
systemRouter.InitInitRouter(PublicGroup) // 自动初始化相关
}
// 前台应用路由
{
appGroup := PublicGroup.Group("app")
appRouter.AiPresetRouter.InitAiPresetRouter(appGroup) // AI预设路由/app/preset/*
appRouter.AiProviderRouter.InitAiProviderRouter(appGroup) // AI提供商路由/app/provider/*
appRouter.PresetBindingRouter.InitPresetBindingRouter(appGroup) // 预设绑定路由:/app/binding/*
systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由
systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由
systemRouter.InitSystemRouter(PrivateGroup) // system相关路由
systemRouter.InitSysVersionRouter(PrivateGroup) // 发版相关路由
systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由
systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码
systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由
systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理
systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史
systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录
systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理
systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理
systemRouter.InitSysExportTemplateRouter(PrivateGroup, PublicGroup) // 导出模板
systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理
systemRouter.InitSysErrorRouter(PrivateGroup, PublicGroup) // 错误日志
systemRouter.InitLoginLogRouter(PrivateGroup) // 登录日志
systemRouter.InitApiTokenRouter(PrivateGroup) // apiToken签发
systemRouter.InitSkillsRouter(PrivateGroup) // Skills 定义器
exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由
exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类
}
// OpenAI 兼容的代理接口
{
v1Group := PublicGroup.Group("v1")
appRouter.AiProxyRouter.InitAiProxyRouter(v1Group) // /v1/chat/completions
}
//插件路由安装
InstallPlugin(PrivateGroup, PublicGroup, Router)
// 注册业务路由
initBizRouter(PrivateGroup, PublicGroup)
global.GVA_ROUTERS = Router.Routes()
global.GVA_LOG.Info("router register success")
return Router
}

View File

@@ -17,10 +17,10 @@ func initBizRouter(routers ...*gin.RouterGroup) {
holder(publicGroup, privateGroup)
// 注册 app 模块路由
// 注册 AI 代理路由
appRouter := router.RouterGroupApp.App
appRouter.AiPresetRouter.InitAiPresetRouter(privateGroup)
appRouter.AiProviderRouter.InitAiProviderRouter(privateGroup)
appRouter.AiProxyRouter.InitAiProxyRouter(privateGroup)
appRouter.PresetBindingRouter.InitPresetBindingRouter(privateGroup)
appRouter.InitAiProxyRouter(publicGroup) // AI 代理接口(公开)
appRouter.InitAiPresetRouter(privateGroup) // 预设管理(需要登录)
appRouter.InitAiProviderRouter(privateGroup) // 提供商管理(需要登录)
appRouter.InitAiPresetBindingRouter(privateGroup) // 绑定管理(需要登录)
}

View File

@@ -2,7 +2,6 @@ package initialize
import (
"fmt"
"git.echol.cn/loser/ai_proxy/server/task"
"github.com/robfig/cron/v3"