diff --git a/server/internal/dao/engine.go b/server/internal/dao/engine.go
index 99d5459..800f4b5 100644
--- a/server/internal/dao/engine.go
+++ b/server/internal/dao/engine.go
@@ -22,6 +22,7 @@ func Init() error {
}
Engine.SetMaxOpenConns(20)
Engine.SetMaxIdleConns(5)
+ Engine.ShowSQL(true)
// sync table structures
if err := Engine.Sync2(
@@ -38,10 +39,20 @@ func Init() error {
return err
}
+ // 迁移旧数据 & 清理格式
+ migrateOldData()
+
// insert initial data if tables are empty
return initSeedData()
}
+func migrateOldData() {
+ // 清理 owner_ids 中可能存在的空格
+ Engine.Exec("UPDATE application SET owner_ids = REPLACE(owner_ids, ' ', '') WHERE owner_ids LIKE '% %'")
+ // 清理 master_uids 中可能存在的空格
+ Engine.Exec("UPDATE business_group SET master_uids = REPLACE(master_uids, ' ', '') WHERE master_uids LIKE '% %'")
+}
+
func initSeedData() error {
// check if profiles table is empty
count, err := Engine.Count(new(model.Profile))
diff --git a/server/internal/handler/app.go b/server/internal/handler/app.go
index d4280e4..76488c6 100644
--- a/server/internal/handler/app.go
+++ b/server/internal/handler/app.go
@@ -8,6 +8,7 @@ import (
"kenaito-conf/server/internal/model"
"net/http"
"strconv"
+ "strings"
"time"
"github.com/gin-gonic/gin"
@@ -36,22 +37,30 @@ func (h *AppHandler) List(c *gin.Context) {
query := dao.Engine.Where("enabled = 1")
if !userInfo.IsSuperAdmin() {
+ userId, _ := strconv.Atoi(userInfo.Id)
+ userIdStr := strconv.Itoa(userId)
+
if userInfo.IsGroupAdmin() && userInfo.GroupId != nil {
- query = query.And("group_id = ?", *userInfo.GroupId)
+ // 组管理员:本组应用 + 自己是负责人的应用
+ query = query.And("(group_id = ? OR FIND_IN_SET('" + userIdStr + "', owner_ids) > 0 OR id IN (SELECT app_id FROM user_permission WHERE user_id = ?))", *userInfo.GroupId, userId)
} else {
- userId, _ := strconv.Atoi(userInfo.Id)
- query = query.And("(owner_id = ? OR id IN (SELECT app_id FROM user_permissions WHERE user_id = ?))", userId, userId)
+ // 普通用户:自己是负责人的应用 + 被授权的应用
+ query = query.And("(FIND_IN_SET('" + userIdStr + "', owner_ids) > 0 OR id IN (SELECT app_id FROM user_permission WHERE user_id = ?))", userId)
}
}
var apps []model.Application
whereClause := "enabled = 1"
if !userInfo.IsSuperAdmin() {
+ userId, _ := strconv.Atoi(userInfo.Id)
+ userIdStr := strconv.Itoa(userId)
+
if userInfo.IsGroupAdmin() && userInfo.GroupId != nil {
- whereClause += " AND group_id = " + strconv.Itoa(*userInfo.GroupId)
+ // 组管理员:本组应用 + 自己是负责人的应用
+ whereClause += " AND (group_id = " + strconv.Itoa(*userInfo.GroupId) + " OR FIND_IN_SET('" + userIdStr + "', owner_ids) > 0 OR id IN (SELECT app_id FROM user_permission WHERE user_id = " + strconv.Itoa(userId) + "))"
} else {
- userId, _ := strconv.Atoi(userInfo.Id)
- whereClause += " AND (owner_id = " + strconv.Itoa(userId) + " OR id IN (SELECT app_id FROM user_permissions WHERE user_id = " + strconv.Itoa(userId) + "))"
+ // 普通用户:自己是负责人的应用 + 被授权的应用
+ whereClause += " AND (FIND_IN_SET('" + userIdStr + "', owner_ids) > 0 OR id IN (SELECT app_id FROM user_permission WHERE user_id = " + strconv.Itoa(userId) + "))"
}
}
if req.AppType != "" {
@@ -101,13 +110,13 @@ func (h *AppHandler) GetById(c *gin.Context) {
}
type AddAppRequest struct {
- Id int `json:"id"`
- GroupId *int `json:"groupId"`
- Name string `json:"name" binding:"required"`
- Code string `json:"code" binding:"required"`
- ServiceId string `json:"serviceId"`
- OwnerId *int `json:"ownerId"`
- Remarks string `json:"remarks"`
+ Id int `json:"id"`
+ GroupId *int `json:"groupId"`
+ Name string `json:"name" binding:"required"`
+ Code string `json:"code" binding:"required"`
+ ServiceId string `json:"serviceId"`
+ OwnerIds []int `json:"ownerIds"`
+ Remarks string `json:"remarks"`
}
func (h *AppHandler) Add(c *gin.Context) {
@@ -126,29 +135,39 @@ func (h *AppHandler) Add(c *gin.Context) {
return
}
- ownerId := req.OwnerId
- if ownerId == nil {
+ // 处理负责人:如果未指定,默认为当前用户
+ ownerIds := req.OwnerIds
+ if len(ownerIds) == 0 {
uid, _ := strconv.Atoi(userInfo.Id)
- ownerId = &uid
+ ownerIds = []int{uid}
}
- var owner model.User
- if has, _ := dao.Engine.ID(*ownerId).Get(&owner); !has {
- c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "负责人不存在"})
+ // 验证所有用户ID都存在
+ var users []model.User
+ if err := dao.Engine.In("id", ownerIds).Find(&users); err != nil || len(users) != len(ownerIds) {
+ c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "部分负责人不存在"})
return
}
+ // 构建逗号分隔的用户名和ID字符串
+ ownerNames := make([]string, 0, len(users))
+ ownerIdStrs := make([]string, 0, len(users))
+ for _, u := range users {
+ ownerNames = append(ownerNames, u.Name)
+ ownerIdStrs = append(ownerIdStrs, strconv.Itoa(u.Id))
+ }
+
app := model.Application{
- GroupId: req.GroupId,
- Name: req.Name,
- Code: req.Code,
- ServiceId: req.ServiceId,
- OwnerId: ownerId,
- OwnerName: owner.Name,
- Remarks: req.Remarks,
- Enabled: true,
- CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
- CreatedBy: userInfo.Name,
+ GroupId: req.GroupId,
+ Name: req.Name,
+ Code: req.Code,
+ ServiceId: req.ServiceId,
+ OwnerIds: strings.Join(ownerIdStrs, ","),
+ OwnerNames: strings.Join(ownerNames, ","),
+ Remarks: req.Remarks,
+ Enabled: true,
+ CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
+ CreatedBy: userInfo.Name,
}
dao.Engine.Insert(&app)
@@ -180,20 +199,32 @@ func (h *AppHandler) Update(c *gin.Context) {
app.Name = req.Name
app.ServiceId = req.ServiceId
app.Remarks = req.Remarks
- if req.OwnerId != nil {
- if app.OwnerId == nil || *req.OwnerId != *app.OwnerId {
- var owner model.User
- if has, _ := dao.Engine.ID(*req.OwnerId).Get(&owner); has {
- app.OwnerName = owner.Name
- }
+
+ // 更新负责人
+ if len(req.OwnerIds) > 0 {
+ // 验证所有用户ID都存在
+ var users []model.User
+ if err := dao.Engine.In("id", req.OwnerIds).Find(&users); err != nil || len(users) != len(req.OwnerIds) {
+ c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "部分负责人不存在"})
+ return
}
- app.OwnerId = req.OwnerId
+
+ // 构建逗号分隔的用户名和ID字符串
+ ownerNames := make([]string, 0, len(users))
+ ownerIdStrs := make([]string, 0, len(users))
+ for _, u := range users {
+ ownerNames = append(ownerNames, u.Name)
+ ownerIdStrs = append(ownerIdStrs, strconv.Itoa(u.Id))
+ }
+ app.OwnerIds = strings.Join(ownerIdStrs, ",")
+ app.OwnerNames = strings.Join(ownerNames, ",")
}
+
app.UpdatedBy = userInfo.Name
now := time.Now()
app.UpdatedAt = &now
- dao.Engine.ID(app.Id).Cols("name", "service_id", "remarks", "owner_id", "owner_name", "updated_by", "updated_at").Update(&app)
+ dao.Engine.ID(app.Id).Cols("name", "service_id", "remarks", "owner_ids", "owner_names", "updated_by", "updated_at").Update(&app)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
}
@@ -255,7 +286,8 @@ func (h *AppHandler) Options(c *gin.Context) {
query = query.And("group_id = ?", *userInfo.GroupId)
} else {
userId, _ := strconv.Atoi(userInfo.Id)
- query = query.And("owner_id = ?", userId)
+ userIdStr := strconv.Itoa(userId)
+ query = query.And("FIND_IN_SET(?, owner_ids) > 0", userIdStr)
}
}
diff --git a/server/internal/handler/group.go b/server/internal/handler/group.go
index 1e7df96..4198028 100644
--- a/server/internal/handler/group.go
+++ b/server/internal/handler/group.go
@@ -6,6 +6,7 @@ import (
"kenaito-conf/server/internal/model"
"net/http"
"strconv"
+ "strings"
"time"
"github.com/gin-gonic/gin"
@@ -35,10 +36,10 @@ func (h *GroupHandler) GetById(c *gin.Context) {
}
type AddGroupRequest struct {
- Id int `json:"id"`
- Name string `json:"name" binding:"required"`
- MasterUid *int `json:"masterUid" binding:"required"`
- Remarks string `json:"remarks"`
+ Id int `json:"id"`
+ Name string `json:"name" binding:"required"`
+ MasterUids []int `json:"masterUids" binding:"required"`
+ Remarks string `json:"remarks"`
}
func (h *GroupHandler) Add(c *gin.Context) {
@@ -60,20 +61,29 @@ func (h *GroupHandler) Add(c *gin.Context) {
return
}
- var master model.User
- if has, _ := dao.Engine.ID(*req.MasterUid).Get(&master); !has {
- c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "管理员不存在"})
+ // 验证所有用户ID都存在
+ var users []model.User
+ if err := dao.Engine.In("id", req.MasterUids).Find(&users); err != nil || len(users) != len(req.MasterUids) {
+ c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "部分管理员不存在"})
return
}
+ // 构建逗号分隔的用户名和ID字符串
+ masterNames := make([]string, 0, len(users))
+ masterIds := make([]string, 0, len(users))
+ for _, u := range users {
+ masterNames = append(masterNames, u.Name)
+ masterIds = append(masterIds, strconv.Itoa(u.Id))
+ }
+
group := model.BusinessGroup{
- Name: req.Name,
- Master: master.Name,
- MasterUid: req.MasterUid,
- Remarks: req.Remarks,
- Enabled: true,
- CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
- CreatedBy: userInfo.Name,
+ Name: req.Name,
+ Master: strings.Join(masterNames, ","),
+ MasterUids: strings.Join(masterIds, ","),
+ Remarks: req.Remarks,
+ Enabled: true,
+ CreatedAt: func() *time.Time { t := time.Now(); return &t }(),
+ CreatedBy: userInfo.Name,
}
dao.Engine.Insert(&group)
@@ -106,20 +116,30 @@ func (h *GroupHandler) Update(c *gin.Context) {
return
}
- group.Name = req.Name
- group.Remarks = req.Remarks
- if req.MasterUid != nil && (group.MasterUid == nil || *req.MasterUid != *group.MasterUid) {
- var master model.User
- if has, _ := dao.Engine.ID(*req.MasterUid).Get(&master); has {
- group.Master = master.Name
- group.MasterUid = req.MasterUid
- }
+ // 验证所有用户ID都存在
+ var users []model.User
+ if err := dao.Engine.In("id", req.MasterUids).Find(&users); err != nil || len(users) != len(req.MasterUids) {
+ c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "部分管理员不存在"})
+ return
}
+
+ // 构建逗号分隔的用户名和ID字符串
+ masterNames := make([]string, 0, len(users))
+ masterIds := make([]string, 0, len(users))
+ for _, u := range users {
+ masterNames = append(masterNames, u.Name)
+ masterIds = append(masterIds, strconv.Itoa(u.Id))
+ }
+
+ group.Name = req.Name
+ group.Master = strings.Join(masterNames, ",")
+ group.MasterUids = strings.Join(masterIds, ",")
+ group.Remarks = req.Remarks
group.UpdatedBy = userInfo.Name
now := time.Now()
group.UpdatedAt = &now
- dao.Engine.ID(group.Id).Cols("name", "remarks", "master", "master_uid", "updated_by", "updated_at").Update(&group)
+ dao.Engine.ID(group.Id).Cols("name", "remarks", "master", "master_uids", "updated_by", "updated_at").Update(&group)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}
diff --git a/server/internal/model/app_config.go b/server/internal/model/app_config.go
index 0b20327..87861f6 100644
--- a/server/internal/model/app_config.go
+++ b/server/internal/model/app_config.go
@@ -22,6 +22,10 @@ type AppConfig struct {
ClientCount int `xorm:"-" json:"clientCount,omitempty"`
}
+func (AppConfig) TableName() string {
+ return "app_config"
+}
+
type AppConfigHistory struct {
Id int `xorm:"int autoincr pk" json:"id"`
OriginId int `xorm:"int" json:"originId"`
@@ -37,3 +41,7 @@ type AppConfigHistory struct {
CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`
CreatedBy string `xorm:"varchar(32)" json:"createdBy"`
}
+
+func (AppConfigHistory) TableName() string {
+ return "app_config_history"
+}
diff --git a/server/internal/model/application.go b/server/internal/model/application.go
index bb768d1..3aa1608 100644
--- a/server/internal/model/application.go
+++ b/server/internal/model/application.go
@@ -9,8 +9,8 @@ type Application struct {
Code string `xorm:"varchar(32)" json:"code"`
ServiceId string `xorm:"varchar(64)" json:"serviceId"`
Token string `xorm:"varchar(64)" json:"token"`
- OwnerId *int `xorm:"int" json:"ownerId"`
- OwnerName string `xorm:"varchar(16)" json:"ownerName"`
+ OwnerIds string `xorm:"varchar(255) 'owner_ids'" json:"ownerIds"`
+ OwnerNames string `xorm:"varchar(255) 'owner_names'" json:"ownerNames"`
Remarks string `xorm:"text" json:"remarks"`
Enabled bool `xorm:"tinyint(1) default 1" json:"enabled"`
CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`
@@ -19,3 +19,7 @@ type Application struct {
UpdatedBy string `xorm:"varchar(32)" json:"updatedBy"`
GroupName string `xorm:"-" json:"groupName,omitempty"`
}
+
+func (Application) TableName() string {
+ return "application"
+}
diff --git a/server/internal/model/business_group.go b/server/internal/model/business_group.go
index bf64195..395bbc9 100644
--- a/server/internal/model/business_group.go
+++ b/server/internal/model/business_group.go
@@ -3,14 +3,18 @@ package model
import "time"
type BusinessGroup struct {
- Id int `xorm:"int autoincr pk" json:"id"`
- Name string `xorm:"varchar(32) notnull" json:"name"`
- Master string `xorm:"varchar(16)" json:"master"`
- MasterUid *int `xorm:"int" json:"masterUid"`
- Remarks string `xorm:"text" json:"remarks"`
- Enabled bool `xorm:"tinyint(1) default 1" json:"enabled"`
- CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`
- CreatedBy string `xorm:"varchar(32)" json:"createdBy"`
- UpdatedAt *time.Time `xorm:"datetime" json:"updatedAt"`
- UpdatedBy string `xorm:"varchar(32)" json:"updatedBy"`
+ Id int `xorm:"int autoincr pk" json:"id"`
+ Name string `xorm:"varchar(32) notnull" json:"name"`
+ Master string `xorm:"varchar(255)" json:"master"`
+ MasterUids string `xorm:"varchar(255) 'master_uids'" json:"masterUids"`
+ Remarks string `xorm:"text" json:"remarks"`
+ Enabled bool `xorm:"tinyint(1) default 1" json:"enabled"`
+ CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`
+ CreatedBy string `xorm:"varchar(32)" json:"createdBy"`
+ UpdatedAt *time.Time `xorm:"datetime" json:"updatedAt"`
+ UpdatedBy string `xorm:"varchar(32)" json:"updatedBy"`
+}
+
+func (BusinessGroup) TableName() string {
+ return "business_group"
}
diff --git a/server/internal/model/client_instance.go b/server/internal/model/client_instance.go
index f8f2304..72552f5 100644
--- a/server/internal/model/client_instance.go
+++ b/server/internal/model/client_instance.go
@@ -14,3 +14,7 @@ type ClientInstance struct {
CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`
UpdatedAt *time.Time `xorm:"datetime" json:"updatedAt"`
}
+
+func (ClientInstance) TableName() string {
+ return "client_instance"
+}
diff --git a/server/internal/model/profile.go b/server/internal/model/profile.go
index 7788e8a..b41a3c0 100644
--- a/server/internal/model/profile.go
+++ b/server/internal/model/profile.go
@@ -12,9 +12,17 @@ type Profile struct {
UpdatedAt *time.Time `xorm:"datetime" json:"updatedAt"`
}
+func (Profile) TableName() string {
+ return "profile"
+}
+
type AppExtrAttr struct {
AppId int `xorm:"int pk" json:"appId"`
Env string `xorm:"varchar(16) pk" json:"env"`
AttrName string `xorm:"varchar(32) pk" json:"attrName"`
AttrValue string `xorm:"varchar(100)" json:"attrValue"`
}
+
+func (AppExtrAttr) TableName() string {
+ return "app_extr_attr"
+}
diff --git a/server/internal/model/user.go b/server/internal/model/user.go
index a406f70..8386b2e 100644
--- a/server/internal/model/user.go
+++ b/server/internal/model/user.go
@@ -18,6 +18,10 @@ type User struct {
GroupMaster bool `xorm:"-" json:"groupMaster,omitempty"`
}
+func (User) TableName() string {
+ return "user"
+}
+
type UserPermission struct {
Id int `xorm:"int autoincr pk" json:"id"`
UserId int `xorm:"int" json:"userId"`
@@ -26,6 +30,10 @@ type UserPermission struct {
Operate string `xorm:"varchar(8) default 'RW'" json:"operate"` // RO, RW
}
+func (UserPermission) TableName() string {
+ return "user_permission"
+}
+
type LoginUserInfo struct {
Id string `json:"id"`
Name string `json:"name"`
diff --git a/server/internal/service/auth.go b/server/internal/service/auth.go
index 4f7fea9..009b8c5 100644
--- a/server/internal/service/auth.go
+++ b/server/internal/service/auth.go
@@ -141,7 +141,8 @@ func (s *AuthService) loadPermissions(userInfo *model.LoginUserInfo, userId int)
if userInfo.IsGroupAdmin() {
dao.Engine.Where("group_id = ?", userInfo.GroupId).Find(&apps)
} else {
- dao.Engine.Where("owner_id = ?", userId).Find(&apps)
+ userIdStr := strconv.Itoa(userId)
+ dao.Engine.Where("FIND_IN_SET(?, owner_ids) > 0", userIdStr).Find(&apps)
}
for _, app := range apps {
userInfo.GrantAppIds = append(userInfo.GrantAppIds, app.Id)
diff --git a/web/src/views/app/AppList.vue b/web/src/views/app/AppList.vue
index fc5f866..6428240 100644
--- a/web/src/views/app/AppList.vue
+++ b/web/src/views/app/AppList.vue
@@ -12,7 +12,7 @@
-
+
@@ -61,7 +61,7 @@
-
+
@@ -126,12 +126,18 @@ export default {
},
showAdd() {
this.dialogTitle = '新建应用'
- this.form = { name: '', code: '', groupId: null, ownerId: null, remarks: '' }
+ this.form = { name: '', code: '', groupId: null, ownerIds: [], remarks: '' }
this.dialogVisible = true
},
showEdit(row) {
this.dialogTitle = '编辑应用'
this.form = { ...row }
+ // 将逗号分隔的ID字符串转为数组
+ if (typeof this.form.ownerIds === 'string' && this.form.ownerIds) {
+ this.form.ownerIds = this.form.ownerIds.split(',').map(Number)
+ } else if (!Array.isArray(this.form.ownerIds)) {
+ this.form.ownerIds = []
+ }
this.dialogVisible = true
},
handleSubmit() {
diff --git a/web/src/views/group/GroupList.vue b/web/src/views/group/GroupList.vue
index 23447ba..41daf18 100644
--- a/web/src/views/group/GroupList.vue
+++ b/web/src/views/group/GroupList.vue
@@ -31,8 +31,8 @@
-
-
+
+
@@ -62,7 +62,7 @@ export default {
form: {},
rules: {
name: [{ required: true, message: '请输入组名称', trigger: 'blur' }],
- masterUid: [{ required: true, message: '请选择负责人', trigger: 'change' }]
+ masterUids: [{ required: true, message: '请选择负责人', trigger: 'change', type: 'array', min: 1 }]
},
users: []
}
@@ -105,12 +105,18 @@ export default {
},
showAdd() {
this.dialogTitle = '新建业务组'
- this.form = { name: '', masterUid: null, remarks: '' }
+ this.form = { name: '', masterUids: [], remarks: '' }
this.dialogVisible = true
},
showEdit(row) {
this.dialogTitle = '编辑业务组'
this.form = { ...row }
+ // 将逗号分隔的ID字符串转为数组
+ if (typeof this.form.masterUids === 'string' && this.form.masterUids) {
+ this.form.masterUids = this.form.masterUids.split(',').map(Number)
+ } else {
+ this.form.masterUids = []
+ }
this.dialogVisible = true
},
handleSubmit() {