You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

365 lines
10 KiB

// 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)
// 初始化存储
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"},
}
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: "角色管理", Path: "/roles", Icon: "Shield", Type: "config", SortOrder: 5, Visible: true, Status: 1},
{Name: "菜单管理", Path: "/menus", Icon: "Menu", Type: "config", SortOrder: 6, Visible: true, Status: 1},
{Name: "机构管理", Path: "/organizations", Icon: "Building2", Type: "config", SortOrder: 7, Visible: true, Status: 1},
{Name: "设置", Path: "/settings", Icon: "Settings", Type: "default", SortOrder: 8, 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 count int64
db.Model(&model.RoleMenu{}).Where("role_id = ?", r.Id).Count(&count)
if count > 0 {
continue
}
var menuIds []int64
switch r.Code {
case model.RoleSuperAdmin, model.RoleAdmin:
menuIds = allMenuIds
case model.RoleUser, model.RoleGuest:
menuIds = defaultMenuIds
}
if len(menuIds) > 0 {
records := make([]model.RoleMenu, 0, len(menuIds))
for _, menuId := range menuIds {
records = append(records, model.RoleMenu{RoleId: r.Id, MenuId: menuId})
}
db.Create(&records)
}
}
log.Println("[Seed] RoleMenus seeded successfully")
}