// Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 package svc import ( "context" "crypto/md5" "fmt" "log" "github.com/casbin/casbin/v2" casbinmodel "github.com/casbin/casbin/v2/model" gormadapter "github.com/casbin/gorm-adapter/v3" "github.com/youruser/base/internal/config" "github.com/youruser/base/internal/middleware" "github.com/youruser/base/internal/storage" "github.com/youruser/base/model" "gorm.io/driver/mysql" "gorm.io/gorm" "github.com/zeromicro/go-zero/rest" ) const casbinModelText = ` [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act ` type ServiceContext struct { Config config.Config Cors rest.Middleware Log rest.Middleware Auth rest.Middleware Authz rest.Middleware // 数据库连接 DB *gorm.DB // Casbin enforcer Enforcer *casbin.Enforcer // 文件存储 Storage storage.Storage } func NewServiceContext(c config.Config) *ServiceContext { // 创建数据库连接 dsn := c.MySQL.DSN db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("Failed to connect database: " + err.Error()) } // 自动迁移表 err = db.AutoMigrate( &model.User{}, &model.Profile{}, &model.File{}, &model.Menu{}, &model.Role{}, &model.RoleMenu{}, &model.Organization{}, &model.UserOrganization{}, // AI models &model.AIProvider{}, &model.AIModel{}, &model.AIApiKey{}, &model.AIConversation{}, &model.AIChatMessage{}, &model.AIUsageRecord{}, &model.AIUserQuota{}, ) if err != nil { panic("Failed to migrate database: " + err.Error()) } // 初始化 Casbin enforcer := initCasbin(db) // 种子超级管理员 seedSuperAdmin(db) // 种子 Casbin 策略 seedCasbinPolicies(enforcer) // 种子角色、菜单、角色-菜单关联 seedRoles(db) seedMenus(db) seedRoleMenus(db) // 种子 AI 供应商和模型 seedAIProviders(db) seedAIModels(db) // 初始化存储 store, err := storage.NewStorage(c.Storage) if err != nil { panic("Failed to initialize storage: " + err.Error()) } log.Printf("[Storage] Initialized with type: %s", c.Storage.Type) return &ServiceContext{ Config: c, Cors: middleware.NewCorsMiddleware().Handle, Log: middleware.NewLogMiddleware().Handle, Auth: middleware.NewAuthMiddleware().Handle, Authz: middleware.NewAuthzMiddleware(enforcer).Handle, DB: db, Enforcer: enforcer, Storage: store, } } // Close 关闭资源 func (s *ServiceContext) Close() error { if s.DB != nil { sqlDB, err := s.DB.DB() if err != nil { return err } return sqlDB.Close() } return nil } // initCasbin 初始化 Casbin enforcer func initCasbin(db *gorm.DB) *casbin.Enforcer { // 使用 GORM adapter(自动创建 casbin_rule 表) adapter, err := gormadapter.NewAdapterByDB(db) if err != nil { panic("Failed to create Casbin adapter: " + err.Error()) } // 从字符串加载 model m, err := casbinmodel.NewModelFromString(casbinModelText) if err != nil { panic("Failed to create Casbin model: " + err.Error()) } enforcer, err := casbin.NewEnforcer(m, adapter) if err != nil { panic("Failed to create Casbin enforcer: " + err.Error()) } // 加载策略 if err := enforcer.LoadPolicy(); err != nil { panic("Failed to load Casbin policy: " + err.Error()) } log.Println("[Casbin] Enforcer initialized successfully") return enforcer } // seedSuperAdmin 首次启动创建超级管理员 func seedSuperAdmin(db *gorm.DB) { ctx := context.Background() existing, err := model.FindOneByUsername(ctx, db, "admin") if err == nil { if existing.Role != model.RoleSuperAdmin { existing.Role = model.RoleSuperAdmin existing.Source = model.SourceSystem model.Update(ctx, db, existing) log.Println("[Seed] Updated admin to super_admin role") } return } password := fmt.Sprintf("%x", md5.Sum([]byte("admin123"))) admin := &model.User{ Username: "admin", Phone: "13800000000", Email: "", Password: password, Role: model.RoleSuperAdmin, Source: model.SourceSystem, Remark: "系统自动创建的超级管理员", Status: 1, } _, err = model.Insert(ctx, db, admin) if err != nil { log.Printf("[Seed] Failed to create super admin: %v", err) return } log.Println("[Seed] Super admin created: admin / admin123") } // seedCasbinPolicies 种子 Casbin 策略(幂等) func seedCasbinPolicies(enforcer *casbin.Enforcer) { // 角色层级: super_admin > admin > user > guest roleHierarchy := [][]string{ {"super_admin", "admin"}, {"admin", "user"}, {"user", "guest"}, } for _, g := range roleHierarchy { if has, _ := enforcer.HasGroupingPolicy(g[0], g[1]); !has { enforcer.AddGroupingPolicy(g[0], g[1]) } } // 默认策略 policies := [][]string{ // guest: 仪表盘只读 {"guest", "/api/v1/dashboard/*", "GET"}, // user: 个人中心 {"user", "/api/v1/profile/*", "GET"}, {"user", "/api/v1/profile/*", "PUT"}, {"user", "/api/v1/profile/*", "POST"}, // admin: 用户管理(增查改) {"admin", "/api/v1/users", "GET"}, {"admin", "/api/v1/user", "POST"}, {"admin", "/api/v1/user/:id", "GET"}, {"admin", "/api/v1/user/:id", "PUT"}, // super_admin: 用户删除 {"super_admin", "/api/v1/user/:id", "DELETE"}, // user: 文件管理 {"user", "/api/v1/file/upload", "POST"}, {"user", "/api/v1/files", "GET"}, {"user", "/api/v1/file/:id", "GET"}, {"user", "/api/v1/file/:id/url", "GET"}, {"user", "/api/v1/file/:id", "PUT"}, // super_admin: 文件删除 {"super_admin", "/api/v1/file/:id", "DELETE"}, // user: 个人机构相关 {"user", "/api/v1/profile/orgs", "GET"}, {"user", "/api/v1/profile/current-org", "PUT"}, // admin: 菜单管理(读取) {"admin", "/api/v1/menus", "GET"}, // super_admin: 菜单管理(增删改) {"super_admin", "/api/v1/menu", "POST"}, {"super_admin", "/api/v1/menu/:id", "PUT"}, {"super_admin", "/api/v1/menu/:id", "DELETE"}, // admin: 角色管理(读取) {"admin", "/api/v1/roles", "GET"}, {"admin", "/api/v1/role/:id/menus", "GET"}, // super_admin: 角色管理(增删改) {"super_admin", "/api/v1/role", "POST"}, {"super_admin", "/api/v1/role/:id", "PUT"}, {"super_admin", "/api/v1/role/:id", "DELETE"}, {"super_admin", "/api/v1/role/:id/menus", "PUT"}, // admin: 机构管理 {"admin", "/api/v1/organizations", "GET"}, {"admin", "/api/v1/organization", "POST"}, {"admin", "/api/v1/organization/:id", "PUT"}, {"admin", "/api/v1/organization/:id/members", "GET"}, {"admin", "/api/v1/organization/:id/member", "POST"}, {"admin", "/api/v1/organization/:id/member/:userId", "PUT"}, {"admin", "/api/v1/organization/:id/member/:userId", "DELETE"}, // super_admin: 机构删除 {"super_admin", "/api/v1/organization/:id", "DELETE"}, // AI: all authenticated users {"user", "/api/v1/ai/chat/completions", "POST"}, {"user", "/api/v1/ai/conversations", "GET"}, {"user", "/api/v1/ai/conversation", "POST"}, {"user", "/api/v1/ai/conversation/:id", "GET"}, {"user", "/api/v1/ai/conversation/:id", "PUT"}, {"user", "/api/v1/ai/conversation/:id", "DELETE"}, {"user", "/api/v1/ai/models", "GET"}, {"user", "/api/v1/ai/quota/me", "GET"}, // AI: user API key management {"user", "/api/v1/ai/keys", "GET"}, {"user", "/api/v1/ai/key", "POST"}, {"user", "/api/v1/ai/key/:id", "PUT"}, {"user", "/api/v1/ai/key/:id", "DELETE"}, // AI: user usage records {"user", "/api/v1/ai/quota/records", "GET"}, // AI: admin provider/model management {"admin", "/api/v1/ai/providers", "GET"}, {"admin", "/api/v1/ai/provider", "POST"}, {"admin", "/api/v1/ai/provider/:id", "PUT"}, {"admin", "/api/v1/ai/provider/:id", "DELETE"}, {"admin", "/api/v1/ai/model", "POST"}, {"admin", "/api/v1/ai/model/:id", "PUT"}, {"admin", "/api/v1/ai/model/:id", "DELETE"}, // AI: admin quota/stats management {"admin", "/api/v1/ai/quotas", "GET"}, {"admin", "/api/v1/ai/quota/recharge", "POST"}, {"admin", "/api/v1/ai/stats", "GET"}, } for _, p := range policies { if has, _ := enforcer.HasPolicy(p[0], p[1], p[2]); !has { enforcer.AddPolicy(p[0], p[1], p[2]) } } enforcer.SavePolicy() log.Println("[Casbin] Policies seeded successfully") } // seedRoles 种子系统角色(幂等) func seedRoles(db *gorm.DB) { roles := []model.Role{ {Name: "超级管理员", Code: model.RoleSuperAdmin, Description: "系统超级管理员", IsSystem: true, SortOrder: 1, Status: 1}, {Name: "管理员", Code: model.RoleAdmin, Description: "系统管理员", IsSystem: true, SortOrder: 2, Status: 1}, {Name: "普通用户", Code: model.RoleUser, Description: "普通用户", IsSystem: true, SortOrder: 3, Status: 1}, {Name: "访客", Code: model.RoleGuest, Description: "访客", IsSystem: true, SortOrder: 4, Status: 1}, } for _, r := range roles { var existing model.Role if err := db.Where("code = ?", r.Code).First(&existing).Error; err != nil { db.Create(&r) } } log.Println("[Seed] Roles seeded successfully") } // seedMenus 种子默认菜单(幂等) func seedMenus(db *gorm.DB) { menus := []model.Menu{ {Name: "我的", Path: "/my", Icon: "User", Type: "default", SortOrder: 1, Visible: true, Status: 1}, {Name: "仪表盘", Path: "/dashboard", Icon: "LayoutDashboard", Type: "config", SortOrder: 2, Visible: true, Status: 1}, {Name: "用户管理", Path: "/users", Icon: "Users", Type: "config", SortOrder: 3, Visible: true, Status: 1}, {Name: "文件管理", Path: "/files", Icon: "FolderOpen", Type: "config", SortOrder: 4, Visible: true, Status: 1}, {Name: "AI 对话", Path: "/ai/chat", Icon: "MessageSquare", Type: "config", SortOrder: 5, Visible: true, Status: 1}, {Name: "AI 模型", Path: "/ai/models", Icon: "Cpu", Type: "config", SortOrder: 6, Visible: true, Status: 1}, {Name: "API 密钥", Path: "/ai/keys", Icon: "Key", Type: "config", SortOrder: 7, Visible: true, Status: 1}, {Name: "用量统计", Path: "/ai/usage", Icon: "BarChart3", Type: "config", SortOrder: 8, Visible: true, Status: 1}, {Name: "额度管理", Path: "/ai/quota", Icon: "Wallet", Type: "config", SortOrder: 9, Visible: true, Status: 1}, {Name: "角色管理", Path: "/roles", Icon: "Shield", Type: "config", SortOrder: 10, Visible: true, Status: 1}, {Name: "菜单管理", Path: "/menus", Icon: "Menu", Type: "config", SortOrder: 11, Visible: true, Status: 1}, {Name: "机构管理", Path: "/organizations", Icon: "Building2", Type: "config", SortOrder: 12, Visible: true, Status: 1}, {Name: "设置", Path: "/settings", Icon: "Settings", Type: "default", SortOrder: 13, Visible: true, Status: 1}, } for _, m := range menus { var existing model.Menu if err := db.Where("path = ?", m.Path).First(&existing).Error; err != nil { db.Create(&m) } } log.Println("[Seed] Menus seeded successfully") } // seedRoleMenus 种子角色-菜单关联(幂等) func seedRoleMenus(db *gorm.DB) { // 获取所有角色 var roles []model.Role db.Find(&roles) // 获取所有菜单 var menus []model.Menu db.Find(&menus) if len(roles) == 0 || len(menus) == 0 { return } // 构建菜单分类 var allMenuIds []int64 var defaultMenuIds []int64 for _, m := range menus { allMenuIds = append(allMenuIds, m.Id) if m.Type == "default" { defaultMenuIds = append(defaultMenuIds, m.Id) } } for _, r := range roles { var menuIds []int64 switch r.Code { case model.RoleSuperAdmin, model.RoleAdmin: menuIds = allMenuIds case model.RoleUser, model.RoleGuest: menuIds = defaultMenuIds } // 获取已有的菜单关联 var existingMenuIds []int64 db.Model(&model.RoleMenu{}).Where("role_id = ?", r.Id).Pluck("menu_id", &existingMenuIds) existingSet := make(map[int64]bool) for _, id := range existingMenuIds { existingSet[id] = true } // 添加缺失的菜单关联 var newRecords []model.RoleMenu for _, menuId := range menuIds { if !existingSet[menuId] { newRecords = append(newRecords, model.RoleMenu{RoleId: r.Id, MenuId: menuId}) } } if len(newRecords) > 0 { db.Create(&newRecords) } } log.Println("[Seed] RoleMenus seeded successfully") } // seedAIProviders 种子 AI 供应商(幂等) func seedAIProviders(db *gorm.DB) { providers := []model.AIProvider{ {Name: "openai", DisplayName: "OpenAI", BaseUrl: "https://api.openai.com/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 1}, {Name: "claude", DisplayName: "Anthropic Claude", BaseUrl: "https://api.anthropic.com", SdkType: "anthropic", Protocol: "anthropic", IsActive: true, SortOrder: 2}, {Name: "qwen", DisplayName: "阿里千问", BaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 3}, {Name: "zhipu", DisplayName: "智谱 GLM", BaseUrl: "https://open.bigmodel.cn/api/paas/v4", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 4}, {Name: "deepseek", DisplayName: "DeepSeek", BaseUrl: "https://api.deepseek.com/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 5}, } for _, p := range providers { var existing model.AIProvider if err := db.Where("name = ?", p.Name).First(&existing).Error; err != nil { db.Create(&p) } } log.Println("[Seed] AI providers seeded successfully") } // seedAIModels 种子 AI 模型(幂等) func seedAIModels(db *gorm.DB) { // First, fetch providers by name to get their IDs providerIds := map[string]int64{} var providers []model.AIProvider db.Find(&providers) for _, p := range providers { providerIds[p.Name] = p.Id } models := []model.AIModel{ {ProviderId: providerIds["openai"], ModelId: "gpt-4o", DisplayName: "GPT-4o", InputPrice: 0.0025, OutputPrice: 0.01, MaxTokens: 16384, ContextWindow: 128000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 1}, {ProviderId: providerIds["openai"], ModelId: "gpt-4o-mini", DisplayName: "GPT-4o Mini", InputPrice: 0.00015, OutputPrice: 0.0006, MaxTokens: 16384, ContextWindow: 128000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 2}, {ProviderId: providerIds["claude"], ModelId: "claude-sonnet-4-5-20250514", DisplayName: "Claude Sonnet 4.5", InputPrice: 0.003, OutputPrice: 0.015, MaxTokens: 8192, ContextWindow: 200000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 3}, {ProviderId: providerIds["claude"], ModelId: "claude-haiku-3-5-20241022", DisplayName: "Claude Haiku 3.5", InputPrice: 0.0008, OutputPrice: 0.004, MaxTokens: 8192, ContextWindow: 200000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 4}, {ProviderId: providerIds["qwen"], ModelId: "qwen-plus", DisplayName: "通义千问 Plus", InputPrice: 0.0008, OutputPrice: 0.002, MaxTokens: 8192, ContextWindow: 131072, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 5}, {ProviderId: providerIds["qwen"], ModelId: "qwen-turbo", DisplayName: "通义千问 Turbo", InputPrice: 0.0003, OutputPrice: 0.0006, MaxTokens: 8192, ContextWindow: 131072, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 6}, {ProviderId: providerIds["zhipu"], ModelId: "glm-4-flash", DisplayName: "GLM-4 Flash", InputPrice: 0.0001, OutputPrice: 0.0001, MaxTokens: 4096, ContextWindow: 128000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 7}, {ProviderId: providerIds["deepseek"], ModelId: "deepseek-chat", DisplayName: "DeepSeek Chat", InputPrice: 0.00014, OutputPrice: 0.00028, MaxTokens: 8192, ContextWindow: 64000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 8}, {ProviderId: providerIds["deepseek"], ModelId: "deepseek-reasoner", DisplayName: "DeepSeek Reasoner", InputPrice: 0.00055, OutputPrice: 0.00219, MaxTokens: 8192, ContextWindow: 64000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 9}, } for _, m := range models { var existing model.AIModel if err := db.Where("model_id = ?", m.ModelId).First(&existing).Error; err != nil { db.Create(&m) } } log.Println("[Seed] AI models seeded successfully") }