diff --git a/docs/admin-adfahi-research/sql/IM-Server改造.sql b/docs/admin-adfahi-research/sql/IM-Server改造.sql index 3c75193..bb383eb 100644 --- a/docs/admin-adfahi-research/sql/IM-Server改造.sql +++ b/docs/admin-adfahi-research/sql/IM-Server改造.sql @@ -8,6 +8,53 @@ SET NAMES utf8mb4; USE `tinode`; +-- ============================================================ +-- 0. User account number. +-- account_no is the short user number shown in Admin. The real primary key +-- im_users.id remains the internal Tinode/Snowflake user ID. +-- Existing users are backfilled from 1000 by created time; new users are +-- assigned by IM Server in the user creation transaction. +-- ============================================================ + +SET @sql := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND COLUMN_NAME = 'account_no') = 0, + 'ALTER TABLE `im_users` ADD COLUMN `account_no` int unsigned NOT NULL DEFAULT 0 COMMENT ''用户编号'' AFTER `id`', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @im_user_account_no := ( + SELECT GREATEST(COALESCE(MAX(`account_no`), 999), 999) + FROM `im_users` + WHERE `account_no` > 0 +); + +UPDATE `im_users` AS u +JOIN ( + SELECT ordered.`id`, (@im_user_account_no := @im_user_account_no + 1) AS next_no + FROM ( + SELECT `id` + FROM `im_users` + WHERE `account_no` = 0 + ORDER BY `createdat`, `id` + ) AS ordered +) AS seq ON seq.`id` = u.`id` +SET u.`account_no` = seq.next_no +WHERE u.`account_no` = 0; + +SET @sql := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND INDEX_NAME = 'uk_im_users_account_no') = 0, + 'CREATE UNIQUE INDEX `uk_im_users_account_no` ON `im_users` (`account_no`)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + -- ============================================================ -- 1. Applied runtime policy snapshots. -- policy_type: security, user_policy_groups, sensitive_words. diff --git a/server/internal/logic/sys/im_user.go b/server/internal/logic/sys/im_user.go index eea4466..08f4aec 100644 --- a/server/internal/logic/sys/im_user.go +++ b/server/internal/logic/sys/im_user.go @@ -69,16 +69,17 @@ func (s *sSysImUser) Create(ctx context.Context, in *sysin.ImUserCreateInp) (res "password": in.Password, } var wire struct { - UserId int64 `json:"user_id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - Phone string `json:"phone"` + DisplayNo int `json:"display_no"` + UserId int64 `json:"user_id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Phone string `json:"phone"` } path := "/internal/admin-query/tenants/" + url.PathEscape(tenant.Code) + "/users" if err = imadmin.DefaultClient(ctx).InternalPost(ctx, path, payload, &wire); err != nil { return nil, err } - res = &sysin.ImUserCreateModel{UserId: wire.UserId, Username: wire.Username, Nickname: wire.Nickname, Phone: wire.Phone} + res = &sysin.ImUserCreateModel{DisplayNo: wire.DisplayNo, UserId: wire.UserId, Username: wire.Username, Nickname: wire.Nickname, Phone: wire.Phone} if in.GroupId > 0 { if err = service.SysImUserPolicyGroup().MemberAdd(ctx, &sysin.ImUserPolicyGroupMemberAddInp{GroupId: in.GroupId, UserId: res.UserId}); err != nil { return nil, gerror.Wrap(err, "用户已创建,但加入用户分组失败") @@ -123,6 +124,7 @@ func (s *sSysImUser) Import(ctx context.Context, in *sysin.ImUserImportInp) (res res.FailedCount++ } else { item.Success = true + item.DisplayNo = created.DisplayNo item.UserId = created.UserId item.Username = created.Username res.SuccessCount++ @@ -205,7 +207,7 @@ func (s *sSysImUser) List(ctx context.Context, in *sysin.ImUserListInp) (list [] values.Set("user_ids", strings.Join(userIds, ",")) } if in.UserId != "" { - values.Set("user_id", in.UserId) + values.Set("account_no", in.UserId) } if in.Nickname != "" { values.Set("nickname", in.Nickname) @@ -239,17 +241,6 @@ func (s *sSysImUser) List(ctx context.Context, in *sysin.ImUserListInp) (list [] if err = fillImUserRegisterTypes(ctx, tenant.Id, list); err != nil { return nil, 0, err } - page := in.Page - if page < 1 { - page = 1 - } - perPage := in.PerPage - if perPage < 1 { - perPage = 10 - } - for index, item := range list { - item.DisplayNo = 1000 + (page-1)*perPage + index - } return list, totalCount, nil } diff --git a/server/internal/model/input/sysin/im_user.go b/server/internal/model/input/sysin/im_user.go index 941368f..00319f8 100644 --- a/server/internal/model/input/sysin/im_user.go +++ b/server/internal/model/input/sysin/im_user.go @@ -18,9 +18,9 @@ import ( type ImUserListInp struct { form.PageReq TenantId int64 `json:"tenantId" dc:"租户ID"` - UserId string `json:"userId" dc:"用户ID"` + UserId string `json:"userId" dc:"后台展示编号"` GroupId int64 `json:"groupId" dc:"用户策略分组ID"` - Keyword string `json:"keyword" dc:"用户名、昵称或手机号"` + Keyword string `json:"keyword" dc:"用户名、昵称、手机号或编号"` Nickname string `json:"nickname" dc:"昵称"` AuthName string `json:"authName" dc:"登录标识"` State *int `json:"state" dc:"账号状态"` @@ -88,10 +88,11 @@ func (in *ImUserCreateInp) Filter(ctx context.Context) error { } type ImUserCreateModel struct { - UserId int64 `json:"userId" dc:"用户ID"` - Username string `json:"username" dc:"用户名"` - Nickname string `json:"nickname" dc:"昵称"` - Phone string `json:"phone" dc:"手机号"` + DisplayNo int `json:"displayNo" dc:"后台展示编号"` + UserId int64 `json:"userId" dc:"用户ID"` + Username string `json:"username" dc:"用户名"` + Nickname string `json:"nickname" dc:"昵称"` + Phone string `json:"phone" dc:"手机号"` } type ImUserBatchCreateInp struct { @@ -114,11 +115,12 @@ func (in *ImUserBatchCreateInp) Filter(ctx context.Context) error { } type ImUserBatchItemModel struct { - Row int `json:"row" dc:"行号"` - Username string `json:"username" dc:"用户名"` - UserId int64 `json:"userId" dc:"用户ID"` - Success bool `json:"success" dc:"是否成功"` - Reason string `json:"reason" dc:"失败原因"` + Row int `json:"row" dc:"行号"` + DisplayNo int `json:"displayNo" dc:"后台展示编号"` + Username string `json:"username" dc:"用户名"` + UserId int64 `json:"userId" dc:"用户ID"` + Success bool `json:"success" dc:"是否成功"` + Reason string `json:"reason" dc:"失败原因"` } type ImUserBatchCreateModel struct { diff --git a/web/src/views/imUser/DevicesModal.vue b/web/src/views/imUser/DevicesModal.vue index a308eda..3fd8445 100644 --- a/web/src/views/imUser/DevicesModal.vue +++ b/web/src/views/imUser/DevicesModal.vue @@ -3,7 +3,7 @@ v-model:show="showModal" preset="dialog" :show-icon="false" - :title="'登录设备 ' + userId" + :title="'登录设备 #' + (displayNo || userId)" :mask-closable="false" :style="{ width: dialogWidth }" > @@ -33,6 +33,7 @@ const showModal = ref(false); const actionRef = ref(); const tenantId = ref(0); + const displayNo = ref(0); const userId = ref(''); const dialogWidth = computed(() => adaModalWidth(1080)); const scrollX = computed(() => adaTableScrollX(deviceColumns, 0)); @@ -46,6 +47,7 @@ function openModal(record: State) { tenantId.value = record.tenantId; + displayNo.value = record.displayNo; userId.value = record.userId; showModal.value = true; setTimeout(() => { diff --git a/web/src/views/imUser/ResetPasswordModal.vue b/web/src/views/imUser/ResetPasswordModal.vue index 60f194c..9affe1b 100644 --- a/web/src/views/imUser/ResetPasswordModal.vue +++ b/web/src/views/imUser/ResetPasswordModal.vue @@ -1,8 +1,14 @@