feat: add invite user binding

This commit is contained in:
richjohnrambo
2026-09-11 10:46:20 +08:00
parent 68d9bafbdd
commit 2fb5102add
18 changed files with 452 additions and 73 deletions
+7
View File
@@ -51,6 +51,13 @@ type RevokeReq struct {
type RevokeRes struct{}
type BindUserReq struct {
g.Meta `path:"/imInvite/bindUser" method:"post" tags:"IM邀请码" summary:"绑定邀请码用户"`
sysin.ImInviteBindUserInp
}
type BindUserRes struct{}
type BatchRevokeReq struct {
g.Meta `path:"/imInvite/batchRevoke" method:"post" tags:"IM邀请码" summary:"批量撤销IM注册邀请码"`
sysin.ImInviteBatchRevokeInp
@@ -60,6 +60,11 @@ func (c *cImInvite) Revoke(ctx context.Context, req *iminvite.RevokeReq) (res *i
return
}
func (c *cImInvite) BindUser(ctx context.Context, req *iminvite.BindUserReq) (res *iminvite.BindUserRes, err error) {
err = service.SysImInvite().BindUser(ctx, &req.ImInviteBindUserInp)
return
}
func (c *cImInvite) BatchRevoke(ctx context.Context, req *iminvite.BatchRevokeReq) (res *iminvite.BatchRevokeRes, err error) {
data, err := service.SysImInvite().BatchRevoke(ctx, &req.ImInviteBatchRevokeInp)
if err != nil {
+113 -21
View File
@@ -7,8 +7,10 @@ import (
"errors"
"fmt"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"net/url"
"strconv"
"strings"
"time"
@@ -44,18 +46,21 @@ type imInviteListRequest struct {
}
type imInviteCodeResponse struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
TenantName string `json:"tenant_name"`
BatchID *int64 `json:"batch_id"`
Code string `json:"code"`
Status string `json:"status"`
ExpiresAt time.Time `json:"expires_at"`
UsageCount int `json:"usage_count"`
LastUsedAt *time.Time `json:"last_used_at"`
CreatedBy int64 `json:"created_by"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"created_at"`
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
TenantName string `json:"tenant_name"`
BatchID *int64 `json:"batch_id"`
Code string `json:"code"`
Status string `json:"status"`
ExpiresAt time.Time `json:"expires_at"`
MaxUses int `json:"max_uses"`
UsageCount int `json:"usage_count"`
BoundUserID *int64 `json:"bound_user_id"`
BoundUserLabel string `json:"bound_user_label"`
LastUsedAt *time.Time `json:"last_used_at"`
CreatedBy int64 `json:"created_by"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"created_at"`
}
type imInviteBatchResponse struct {
@@ -202,6 +207,47 @@ func (s *sSysImInvite) Revoke(ctx context.Context, in *sysin.ImInviteRevokeInp)
return err
}
func (s *sSysImInvite) BindUser(ctx context.Context, in *sysin.ImInviteBindUserInp) (err error) {
if err = in.Filter(ctx); err != nil {
return err
}
scope, db, err := inviteDB(ctx, in.TenantId)
if err != nil {
return err
}
id, err := sysin.ParseOptionalInt64(in.Id)
if err != nil || id <= 0 {
return gerror.New("邀请码ID不能为空")
}
tenant, err := resolveImAdminTenant(ctx, scope.TenantId)
if err != nil {
return err
}
label, err := inviteBoundUserLabel(ctx, tenant, in.UserId)
if err != nil {
return err
}
now := time.Now().UTC().Round(time.Millisecond)
result, err := db.Exec(ctx, `UPDATE im_registration_invite_codes
SET bound_user_id=?,bound_user_label=?,updated_at=?
WHERE tenant_id=? AND id=? AND status='active' AND usage_count=0 AND (bound_user_id IS NULL OR bound_user_id=0)`,
in.UserId, label, now, scope.TenantId, id)
if err == nil {
if affected, _ := result.RowsAffected(); affected == 0 {
err = gerror.New("邀请码不存在、已使用、已绑定或不可绑定")
}
}
auditResult := "success"
if err != nil {
auditResult = "failed"
}
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_bind_user", "invite_code", id, auditResult, g.Map{"user_id": in.UserId})
if err != nil {
return err
}
return nil
}
func (s *sSysImInvite) BatchRevoke(ctx context.Context, in *sysin.ImInviteBatchRevokeInp) (res *sysin.ImInviteBatchModel, err error) {
scope, db, err := inviteDB(ctx, in.TenantId)
if err != nil {
@@ -251,14 +297,14 @@ func (s *sSysImInvite) Export(ctx context.Context, in *sysin.ImInviteListInp) (e
request.Response.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=registration-invite-codes-%s.csv", time.Now().Format("20060102-150405")))
_, _ = request.Response.Writer.Write([]byte{0xef, 0xbb, 0xbf})
writer := csv.NewWriter(request.Response.Writer)
_ = writer.Write([]string{"tenant_id", "tenant_name", "code", "status", "batch_id", "expires_at", "usage_count", "last_used_at", "created_by", "created_at", "remark"})
_ = writer.Write([]string{"tenant_id", "tenant_name", "code", "status", "expires_at", "usage_count", "available_uses", "last_used_at", "created_by", "created_at", "remark"})
for offset := 0; ; offset += 100 {
items, total, queryErr := queryInviteCodes(ctx, db, req, 100, offset)
if queryErr != nil {
return gerror.Wrap(queryErr, "导出邀请码失败")
}
for _, item := range items {
_ = writer.Write([]string{strconv.FormatInt(item.TenantID, 10), item.TenantName, item.Code, item.Status, formatOptionalInt64(item.BatchID), item.ExpiresAt.Format(time.RFC3339), strconv.Itoa(item.UsageCount), formatTime(item.LastUsedAt), strconv.FormatInt(item.CreatedBy, 10), item.CreatedAt.Format(time.RFC3339), item.Remark})
_ = writer.Write([]string{strconv.FormatInt(item.TenantID, 10), item.TenantName, item.Code, item.Status, item.ExpiresAt.Format(time.RFC3339), strconv.Itoa(item.UsageCount), formatInviteAvailableUses(item.MaxUses, item.UsageCount), formatTime(item.LastUsedAt), strconv.FormatInt(item.CreatedBy, 10), item.CreatedAt.Format(time.RFC3339), item.Remark})
}
writer.Flush()
if writer.Error() != nil {
@@ -300,10 +346,10 @@ func createSingleInvite(ctx context.Context, db gdb.DB, tenantID, operatorID int
}
now := time.Now().UTC().Round(time.Millisecond)
_, err = db.Exec(ctx, `INSERT INTO im_registration_invite_codes
(id,tenant_id,batch_id,code,status,expires_at,usage_count,last_used_at,created_by,remark,created_at,updated_at)
VALUES(?,?,NULL,?,'active',?,0,NULL,?,?,?,?)`, id, tenantID, code, expiresAt, operatorID, remark, now, now)
(id,tenant_id,batch_id,code,status,expires_at,max_uses,usage_count,last_used_at,created_by,remark,created_at,updated_at)
VALUES(?,?,NULL,?,'active',?,1,0,NULL,?,?,?,?)`, id, tenantID, code, expiresAt, operatorID, remark, now, now)
if err == nil {
return imInviteCodeResponse{ID: id, TenantID: tenantID, Code: code, Status: "active", ExpiresAt: expiresAt, CreatedBy: operatorID, Remark: remark, CreatedAt: now}, nil
return imInviteCodeResponse{ID: id, TenantID: tenantID, Code: code, Status: "active", ExpiresAt: expiresAt, MaxUses: 1, CreatedBy: operatorID, Remark: remark, CreatedAt: now}, nil
}
if !isDuplicateError(err) {
return imInviteCodeResponse{}, err
@@ -356,8 +402,8 @@ func createInviteBatch(ctx context.Context, db gdb.DB, tenantID, operatorID int6
}
for i := range codes {
if _, txErr := tx.Exec(`INSERT INTO im_registration_invite_codes
(id,tenant_id,batch_id,code,status,expires_at,usage_count,last_used_at,created_by,remark,created_at,updated_at)
VALUES(?,?,?,?,'active',?,0,NULL,?,?,?,?)`, codes[i].ID, tenantID, batchID, codes[i].Code, expiresAt, operatorID, remark, now, now); txErr != nil {
(id,tenant_id,batch_id,code,status,expires_at,max_uses,usage_count,last_used_at,created_by,remark,created_at,updated_at)
VALUES(?,?,?,?,'active',?,0,0,NULL,?,?,?,?)`, codes[i].ID, tenantID, batchID, codes[i].Code, expiresAt, operatorID, remark, now, now); txErr != nil {
return txErr
}
codes[i].TenantID, codes[i].Status, codes[i].ExpiresAt, codes[i].CreatedBy, codes[i].Remark, codes[i].CreatedAt = tenantID, "active", expiresAt, operatorID, remark, now
@@ -400,7 +446,7 @@ func queryInviteCodes(ctx context.Context, db gdb.DB, req imInviteListRequest, l
}
queryArgs := append(append([]any{}, args...), limit, offset)
items := make([]imInviteCodeResponse, 0)
err = db.Raw(`SELECT c.id,c.tenant_id,t.name tenant_name,c.batch_id,c.code,c.status,c.expires_at,c.created_by,c.remark,c.created_at,
err = db.Raw(`SELECT c.id,c.tenant_id,t.name tenant_name,c.batch_id,c.code,c.status,c.expires_at,c.max_uses,c.bound_user_id,c.bound_user_label,c.created_by,c.remark,c.created_at,
c.usage_count,c.last_used_at
FROM im_registration_invite_codes c
LEFT JOIN im_tenant t ON t.id=c.tenant_id
@@ -521,10 +567,45 @@ func convertInviteCodes(items []imInviteCodeResponse) []*sysin.ImInviteCodeModel
func convertInviteCode(item imInviteCodeResponse) *sysin.ImInviteCodeModel {
return &sysin.ImInviteCodeModel{Id: strconv.FormatInt(item.ID, 10), BatchId: formatOptionalInt64(item.BatchID), Code: item.Code,
TenantId: strconv.FormatInt(item.TenantID, 10), TenantName: item.TenantName,
Status: item.Status, ExpiresAt: gtime.New(item.ExpiresAt), UsageCount: item.UsageCount, LastUsedAt: convertTime(item.LastUsedAt),
Status: item.Status, ExpiresAt: gtime.New(item.ExpiresAt), MaxUses: item.MaxUses, UsageCount: item.UsageCount,
BoundUserId: formatOptionalInt64(item.BoundUserID), BoundUserLabel: item.BoundUserLabel, LastUsedAt: convertTime(item.LastUsedAt),
CreatedBy: strconv.FormatInt(item.CreatedBy, 10), Remark: item.Remark, CreatedAt: gtime.New(item.CreatedAt)}
}
func inviteBoundUserLabel(ctx context.Context, tenant *entity.ImTenant, userID int64) (string, error) {
if tenant == nil || tenant.Code == "" || userID <= 0 {
return "", gerror.New("绑定用户不合法")
}
values := addPageQuery(url.Values{}, 1, 1)
values.Set("user_ids", strconv.FormatInt(userID, 10))
users, _, err := imAdminQueryList[sysin.ImUserListModel](ctx, tenant,
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/users",
values,
map[string]string{
"public": "profileJson",
"trusted": "trustedJson",
"access": "accessJson",
},
)
if err != nil {
return "", err
}
if len(users) == 0 {
return "", gerror.New("绑定用户不存在")
}
user := users[0]
if user.Nickname != "" {
return fmt.Sprintf("%s(%s)", user.Nickname, user.UserId), nil
}
if user.Username != "" {
return fmt.Sprintf("%s(%s)", user.Username, user.UserId), nil
}
if user.Phone != "" {
return fmt.Sprintf("%s(%s)", user.Phone, user.UserId), nil
}
return user.UserId, nil
}
func convertInviteBatch(item *imInviteBatchResponse) *sysin.ImInviteBatchModel {
if item == nil {
return nil
@@ -564,6 +645,17 @@ func formatTime(value *time.Time) string {
return value.Format(time.RFC3339)
}
func formatInviteAvailableUses(maxUses, usageCount int) string {
if maxUses <= 0 {
return "不限制"
}
available := maxUses - usageCount
if available < 0 {
available = 0
}
return strconv.Itoa(available)
}
func batchID(batch *imInviteBatchResponse) int64 {
if batch == nil {
return 0
+31 -12
View File
@@ -105,24 +105,43 @@ type ImInviteRevokeInp struct {
Id string `json:"id" v:"required#邀请码ID不能为空" dc:"邀请码ID"`
}
type ImInviteBindUserInp struct {
TenantId int64 `json:"tenantId" dc:"IM租户ID"`
Id string `json:"id" v:"required#邀请码ID不能为空" dc:"邀请码ID"`
UserId int64 `json:"userId" v:"required|min:1#绑定用户不能为空|绑定用户不合法" dc:"绑定C端用户ID"`
}
func (in *ImInviteBindUserInp) Filter(ctx context.Context) (err error) {
if _, err = ParseOptionalInt64(in.Id); err != nil {
return gerror.New("邀请码ID不能为空")
}
if in.UserId <= 0 {
return gerror.New("绑定用户不能为空")
}
return nil
}
type ImInviteBatchRevokeInp struct {
TenantId int64 `json:"tenantId" dc:"IM租户ID"`
BatchId string `json:"batchId" v:"required#批次ID不能为空" dc:"批次ID"`
}
type ImInviteCodeModel struct {
Id string `json:"id" dc:"ID"`
TenantId string `json:"tenantId" dc:"IM租户ID"`
TenantName string `json:"tenantName" dc:"IM租户名称"`
BatchId string `json:"batchId" dc:"批次ID"`
Code string `json:"code" dc:"邀请码"`
Status string `json:"status" dc:"状态"`
ExpiresAt *gtime.Time `json:"expiresAt" dc:"到期时间"`
UsageCount int `json:"usageCount" dc:"累计使用次数"`
LastUsedAt *gtime.Time `json:"lastUsedAt" dc:"最后使用时间"`
CreatedBy string `json:"createdBy" dc:"创建人"`
Remark string `json:"remark" dc:"备注"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
Id string `json:"id" dc:"ID"`
TenantId string `json:"tenantId" dc:"IM租户ID"`
TenantName string `json:"tenantName" dc:"IM租户名称"`
BatchId string `json:"batchId" dc:"批次ID"`
Code string `json:"code" dc:"邀请码"`
Status string `json:"status" dc:"状态"`
ExpiresAt *gtime.Time `json:"expiresAt" dc:"到期时间"`
MaxUses int `json:"maxUses" dc:"最大可用次数,0表示不限制"`
UsageCount int `json:"usageCount" dc:"累计使用次数"`
BoundUserId string `json:"boundUserId" dc:"绑定用户ID"`
BoundUserLabel string `json:"boundUserLabel" dc:"绑定用户展示名"`
LastUsedAt *gtime.Time `json:"lastUsedAt" dc:"最后使用时间"`
CreatedBy string `json:"createdBy" dc:"创建人"`
Remark string `json:"remark" dc:"备注"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
}
type ImInviteBatchModel struct {
+2
View File
@@ -209,6 +209,8 @@ type (
BatchView(ctx context.Context, in *sysin.ImInviteBatchViewInp) (res *sysin.ImInviteBatchDetailModel, err error)
// Revoke 撤销邀请码
Revoke(ctx context.Context, in *sysin.ImInviteRevokeInp) (err error)
// BindUser 绑定邀请码注册后自动添加的C端用户
BindUser(ctx context.Context, in *sysin.ImInviteBindUserInp) (err error)
// BatchRevoke 批量撤销邀请码
BatchRevoke(ctx context.Context, in *sysin.ImInviteBatchRevokeInp) (res *sysin.ImInviteBatchModel, err error)
// Export 导出邀请码
@@ -8,14 +8,14 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager
SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1);
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @workbenchRootId, '意见反馈配置', 'imFeedbackConfigIndex', 'feedback-config/index', '', 2, '', '/imFeedbackConfig/get', '', '/imFeedbackConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 30, '', 1, @now, @now
SELECT @workbenchRootId, '意见反馈配置', 'imFeedbackConfigIndex', 'feedback-config/index', '', 2, '', '/imFeedbackConfig/get', '', '/imFeedbackConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 40, '', 1, @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex');
SET @imFeedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex' LIMIT 1);
UPDATE `hg_admin_menu`
SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager',
`level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30,
`level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40,
`status`=1, `hidden`=0, `updated_at`=@now
WHERE `id`=@imFeedbackConfigId;
@@ -8,14 +8,14 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager
SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1);
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @workbenchRootId, '总经理信箱配置', 'imMailboxConfigIndex', 'mailbox-config/index', '', 2, '', '/imMailboxConfig/get', '', '/imMailboxConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 20, '', 1, @now, @now
SELECT @workbenchRootId, '总经理信箱配置', 'imMailboxConfigIndex', 'mailbox-config/index', '', 2, '', '/imMailboxConfig/get', '', '/imMailboxConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 30, '', 1, @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex');
SET @imMailboxConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex' LIMIT 1);
UPDATE `hg_admin_menu`
SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager',
`level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20,
`level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30,
`status`=1, `hidden`=0, `updated_at`=@now
WHERE `id`=@imMailboxConfigId;
@@ -37,7 +37,6 @@ UPDATE `hg_admin_menu` SET `redirect`='/im/dashboard/index', `updated_at`=@now W
UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='用户管理', `path`='user/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=10, `updated_at`=@now WHERE `name`='imUserIndex';
UPDATE `hg_admin_menu` SET `pid`=@peopleId, `path`='user-policy-group/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=20, `updated_at`=@now WHERE `name`='imUserPolicyGroupIndex';
UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='群组管理', `path`='group/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=30, `updated_at`=@now WHERE `name`='imGroupIndex';
UPDATE `hg_admin_menu` SET `pid`=@peopleId, `path`='org/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=40, `updated_at`=@now WHERE `name`='imOrgIndex';
UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='邀请码管理', `path`='registration-invite/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=50, `updated_at`=@now WHERE `name`='imRegistrationInviteIndex';
-- Content review.
@@ -97,15 +96,17 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager
SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1);
SET @workbenchId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex' LIMIT 1);
SET @orgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1);
SET @mailboxConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex' LIMIT 1);
SET @feedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex' LIMIT 1);
UPDATE `hg_admin_menu` SET `pid`=0, `title`='工作台管理', `path`='/workbench', `icon`='AppstoreOutlined', `type`=1, `redirect`='/workbench/config/index', `component`='LAYOUT', `always_show`=1, `active_menu`='', `is_root`=1, `hidden`=0, `level`=1, `tree`=CONCAT('tr_', `id`, ' '), `sort`=126, `status`=1, `updated_at`=@now WHERE `id`=@workbenchRootId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='工作台配置', `path`='config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=10, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@workbenchId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@mailboxConfigId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@feedbackConfigId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='组织架构', `path`='org/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@orgId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@mailboxConfigId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@feedbackConfigId;
UPDATE `hg_admin_menu` SET `level`=3, `tree`=CONCAT('tr_', @workbenchRootId, ' tr_', @workbenchId, ' '), `updated_at`=@now WHERE `pid`=@workbenchId AND `type`=3;
UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@mailboxConfigId, @feedbackConfigId) AND child.`type`=3;
UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@orgId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3;
INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId);
SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @orgId, @mailboxConfigId, @feedbackConfigId);
@@ -48,6 +48,7 @@ UPDATE `hg_admin_menu` SET `title` = '创建邀请码' WHERE `name` = 'imInviteC
UPDATE `hg_admin_menu` SET `title` = '批量创建邀请码' WHERE `name` = 'imInviteBatchCreate';
UPDATE `hg_admin_menu` SET `title` = '批次详情' WHERE `name` = 'imInviteBatchView';
UPDATE `hg_admin_menu` SET `title` = '撤销邀请码' WHERE `name` = 'imInviteRevoke';
UPDATE `hg_admin_menu` SET `title` = '绑定用户' WHERE `name` = 'imInviteBindUser';
UPDATE `hg_admin_menu` SET `title` = '撤销批次邀请码' WHERE `name` = 'imInviteBatchRevoke';
UPDATE `hg_admin_menu` SET `title` = '导出邀请码' WHERE `name` = 'imInviteExport';
@@ -87,9 +87,10 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='workbenchManager')
SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name`='workbenchManager' LIMIT 1);
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=10, `updated_at`=@now WHERE `id`=@workbenchId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `updated_at`=@now WHERE `id`=@mailboxConfigId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `updated_at`=@now WHERE `id`=@feedbackConfigId;
UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='组织架构', `path`='org/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `updated_at`=@now WHERE `id`=@imOrgId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `updated_at`=@now WHERE `id`=@mailboxConfigId;
UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40, `updated_at`=@now WHERE `id`=@feedbackConfigId;
UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@workbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3;
INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId);
SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId);
+29 -9
View File
@@ -1,40 +1,60 @@
SET @now := NOW();
SET @imRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imManager' LIMIT 1);
SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1);
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imRootId, '组织架构', 'imOrgIndex', 'org/index', '', '2', '', '/imOrg/departmentList', '', '/imOrg/index', '1', 'imManager', '0', '0', '', '0', '0', '0', '2', CONCAT('tr_', @imRootId, ' '), '65', '', '1', @now, @now
SELECT NULL, COALESCE(@workbenchRootId, @imRootId), '组织架构', 'imOrgIndex', 'org/index', '', '2', '', '/imOrg/departmentList', '', '/imOrg/index', '1', IF(@workbenchRootId IS NULL, 'imManager', 'workbenchManager'), '0', '0', '', '0', '0', '0', IF(@workbenchRootId IS NULL, '2', '2'), CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' '), '20', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex');
SET @imOrgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1);
UPDATE `hg_admin_menu`
SET `pid` = COALESCE(@workbenchRootId, @imRootId),
`title` = '组织架构',
`path` = 'org/index',
`active_menu` = IF(@workbenchRootId IS NULL, 'imManager', 'workbenchManager'),
`level` = 2,
`tree` = CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' '),
`sort` = 20,
`status` = 1,
`hidden` = 0,
`updated_at` = @now
WHERE `id` = @imOrgId;
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '部门编辑', 'imOrgDepartmentEdit', '', '', '3', '', '/imOrg/departmentEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '10', '', '1', @now, @now
SELECT NULL, @imOrgId, '部门编辑', 'imOrgDepartmentEdit', '', '', '3', '', '/imOrg/departmentEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '10', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentEdit');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '部门导出', 'imOrgDepartmentExport', '', '', '3', '', '/imOrg/departmentExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '15', '', '1', @now, @now
SELECT NULL, @imOrgId, '部门导出', 'imOrgDepartmentExport', '', '', '3', '', '/imOrg/departmentExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '15', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentExport');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '部门删除', 'imOrgDepartmentDelete', '', '', '3', '', '/imOrg/departmentDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '20', '', '1', @now, @now
SELECT NULL, @imOrgId, '部门删除', 'imOrgDepartmentDelete', '', '', '3', '', '/imOrg/departmentDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '20', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentDelete');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '成员列表', 'imOrgMemberList', '', '', '3', '', '/imOrg/memberList', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '30', '', '1', @now, @now
SELECT NULL, @imOrgId, '成员列表', 'imOrgMemberList', '', '', '3', '', '/imOrg/memberList', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '30', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberList');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '成员编辑', 'imOrgMemberEdit', '', '', '3', '', '/imOrg/memberEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '40', '', '1', @now, @now
SELECT NULL, @imOrgId, '成员编辑', 'imOrgMemberEdit', '', '', '3', '', '/imOrg/memberEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '40', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberEdit');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '成员导出', 'imOrgMemberExport', '', '', '3', '', '/imOrg/memberExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '45', '', '1', @now, @now
SELECT NULL, @imOrgId, '成员导出', 'imOrgMemberExport', '', '', '3', '', '/imOrg/memberExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '45', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberExport');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '成员移除', 'imOrgMemberDelete', '', '', '3', '', '/imOrg/memberDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '50', '', '1', @now, @now
SELECT NULL, @imOrgId, '成员移除', 'imOrgMemberDelete', '', '', '3', '', '/imOrg/memberDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '50', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberDelete');
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @imOrgId, '组织搜索', 'imOrgSearch', '', '', '3', '', '/imOrg/search', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '60', '', '1', @now, @now
SELECT NULL, @imOrgId, '组织搜索', 'imOrgSearch', '', '', '3', '', '/imOrg/search', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '60', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgSearch');
UPDATE `hg_admin_menu`
SET `level` = 3,
`tree` = CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '),
`updated_at` = @now
WHERE `pid` = @imOrgId AND `type` = 3;
@@ -22,6 +22,7 @@ WHERE @workbenchRootId IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex');
SET @imWorkbenchId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex' LIMIT 1);
SET @imOrgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1);
UPDATE `hg_admin_menu`
SET `pid` = @workbenchRootId, `title` = '工作台配置', `path` = 'config/index',
@@ -31,6 +32,14 @@ SET `pid` = @workbenchRootId, `title` = '工作台配置', `path` = 'config/inde
`updated_at` = @now
WHERE `id` = @imWorkbenchId;
UPDATE `hg_admin_menu`
SET `pid` = @workbenchRootId, `title` = '组织架构', `path` = 'org/index',
`type` = 2, `permissions` = '/imOrg/departmentList', `component` = '/imOrg/index',
`always_show` = 1, `active_menu` = 'workbenchManager', `is_root` = 0, `hidden` = 0,
`level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 20, `status` = 1,
`updated_at` = @now
WHERE `id` = @imOrgId;
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @imWorkbenchId, item.title, item.name, '', '', 3, '', item.permissions, '', '', 1, '', 0, 0, '', 0, 1, 0, 3, CONCAT('tr_', @workbenchRootId, ' tr_', @imWorkbenchId, ' '), item.sort, '', 1, @now, @now
FROM (
@@ -51,13 +60,13 @@ SET @feedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFee
UPDATE `hg_admin_menu`
SET `pid` = @workbenchRootId, `path` = 'mailbox-config/index', `active_menu` = 'workbenchManager',
`level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 20,
`level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 30,
`status` = 1, `hidden` = 0, `updated_at` = @now
WHERE `id` = @mailboxConfigId;
UPDATE `hg_admin_menu`
SET `pid` = @workbenchRootId, `path` = 'feedback-config/index', `active_menu` = 'workbenchManager',
`level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 30,
`level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 40,
`status` = 1, `hidden` = 0, `updated_at` = @now
WHERE `id` = @feedbackConfigId;
@@ -66,11 +75,11 @@ JOIN `hg_admin_menu` page ON page.`id` = child.`pid`
SET child.`level` = 3,
child.`tree` = CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '),
child.`updated_at` = @now
WHERE page.`id` IN (@mailboxConfigId, @feedbackConfigId)
WHERE page.`id` IN (@imOrgId, @mailboxConfigId, @feedbackConfigId)
AND child.`type` = 3;
-- Preserve access for every role which could already open a workbench feature.
INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
SELECT `role_id`, @workbenchRootId
FROM `hg_admin_role_menu`
WHERE `menu_id` IN (@imWorkbenchId, @mailboxConfigId, @feedbackConfigId);
WHERE `menu_id` IN (@imWorkbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId);
@@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_batches (
id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
quantity INT UNSIGNED NOT NULL,
max_uses_per_code INT UNSIGNED NOT NULL DEFAULT 0,
idempotency_key VARCHAR(128) NOT NULL,
expires_at DATETIME(3) NOT NULL,
created_by BIGINT NOT NULL,
@@ -99,6 +100,9 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_codes (
code CHAR(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
status VARCHAR(16) NOT NULL,
expires_at DATETIME(3) NOT NULL,
max_uses INT UNSIGNED NOT NULL DEFAULT 0,
bound_user_id BIGINT NULL,
bound_user_label VARCHAR(128) NOT NULL DEFAULT '',
usage_count INT UNSIGNED NOT NULL DEFAULT 0,
last_used_at DATETIME(3) NULL,
created_by BIGINT NOT NULL,
@@ -107,6 +111,7 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_codes (
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_im_registration_invite_code_tenant (tenant_id, code),
KEY idx_im_registration_invite_tenant_bound_user (tenant_id, bound_user_id),
KEY idx_im_registration_invite_tenant_status_expires (tenant_id, status, expires_at),
KEY idx_im_registration_invite_tenant_batch (tenant_id, batch_id),
KEY idx_im_registration_invite_tenant_created (tenant_id, created_at)
@@ -166,8 +171,9 @@ FROM (
SELECT '批量生成邀请码', 'imInviteBatchCreate', '/imInvite/batchCreate', 20 UNION ALL
SELECT '批次详情', 'imInviteBatchView', '/imInvite/batchView', 30 UNION ALL
SELECT '撤销邀请码', 'imInviteRevoke', '/imInvite/revoke', 40 UNION ALL
SELECT '批量撤销邀请码', 'imInviteBatchRevoke', '/imInvite/batchRevoke', 50 UNION ALL
SELECT '导出邀请码', 'imInviteExport', '/imInvite/export', 60
SELECT '绑定用户', 'imInviteBindUser', '/imInvite/bindUser', 50 UNION ALL
SELECT '批量撤销邀请码', 'imInviteBatchRevoke', '/imInvite/batchRevoke', 60 UNION ALL
SELECT '导出邀请码', 'imInviteExport', '/imInvite/export', 70
) p
WHERE NOT EXISTS (SELECT 1 FROM hg_admin_menu m WHERE m.name = p.name);