@@ -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_a t"`
CreatedBy int64 ` json:"created_by " `
Remark string ` json:"remark " `
CreatedAt time . Time ` json:"creat ed_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_coun t"`
BoundUserID * int64 ` json:"bound_user_id " `
BoundUserLabel string ` json:"bound_user_label " `
LastUsedAt * time . Time ` json:"last_us ed_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