feat: add invite user binding
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user