fix: notify receiver update precedure recode (#24099)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2026-01-20 00:16:15 +08:00
committed by GitHub
parent 81297ecfc6
commit 2b890dff68
6 changed files with 197 additions and 83 deletions
+31
View File
@@ -16,6 +16,9 @@ package notify
import (
"yunion.io/x/onecloud/cmd/climc/shell"
identity_api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/mcclient"
identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
options "yunion.io/x/onecloud/pkg/mcclient/options/notify"
)
@@ -36,4 +39,32 @@ func init() {
cmd.PerformClass("get-types", new(options.ReceiverGetTypeOptions))
cmd.Perform("get-subscription", new(options.ReceiverGetSubscriptionOptions))
cmd.GetProperty(new(options.SReceiverRoleContactType))
type SyncUserContactOptions struct {
USERID string `json:"user_id"`
Mobile string `json:"mobile"`
Email string `json:"email"`
}
R(&SyncUserContactOptions{}, "sync-user-contacts", "Sync user contact", func(s *mcclient.ClientSession, args *SyncUserContactOptions) error {
userObj, err := identity_modules.UsersV3.Get(s, args.USERID, nil)
if err != nil {
return err
}
user := identity_api.UserDetails{}
err = userObj.Unmarshal(&user)
if err != nil {
return err
}
if len(args.Mobile) == 0 {
args.Mobile = user.Mobile
}
if len(args.Email) == 0 {
args.Email = user.Email
}
err = modules.NotifyReceiver.SyncUserContact(s, user.Id, args.Mobile, args.Email)
if err != nil {
return err
}
return nil
})
}
+20 -1
View File
@@ -19,6 +19,8 @@ import (
"regexp"
"strings"
"yunion.io/x/pkg/tristate"
"yunion.io/x/onecloud/pkg/apis"
)
@@ -65,6 +67,9 @@ var (
func ParseInternationalMobile(mobile string) SInternationalMobile {
matchs := pareser.FindStringSubmatch(mobile)
if len(matchs) == 0 {
if len(mobile) == 0 {
return SInternationalMobile{}
}
return SInternationalMobile{
AreaCode: defaultAreaCode,
Mobile: mobile,
@@ -85,7 +90,7 @@ func (im *SInternationalMobile) AcceptExtMobile() {
// 对传入的手机号去除地区编号
func moveAreaCode(mobile string) string {
// 所有地区编号
allArea := `283|282|281|280|269|268|267|266|265|264|263|262|261|260|259|258|257|256|255|254|253|252|251|250|249|248|247|246|245|244|243|242|241|240|239|238|237|236|235|234|233|232|231|230|229|228|227|226|225|224|223|222|221|220|219|218|217|216|215|214|213|212|211|210|98|95|94|93|92|91|90|86|84|82|81|66|65|64|63|62|61|60|58|57|56|55|54|53|52|51|49|48|47|46|45|44|43|41|40|39|36|34|33|32|31|30|27|20|7|1`
allArea := `852|283|282|281|280|269|268|267|266|265|264|263|262|261|260|259|258|257|256|255|254|253|252|251|250|249|248|247|246|245|244|243|242|241|240|239|238|237|236|235|234|233|232|231|230|229|228|227|226|225|224|223|222|221|220|219|218|217|216|215|214|213|212|211|210|98|95|94|93|92|91|90|86|84|82|81|66|65|64|63|62|61|60|58|57|56|55|54|53|52|51|49|48|47|46|45|44|43|41|40|39|36|34|33|32|31|30|27|20|7|1`
temp := strings.Split(allArea, "|")
for _, area := range temp {
if strings.HasPrefix(mobile, "+"+area) {
@@ -111,6 +116,9 @@ func moveExtStr(mobile string) string {
}
func (im SInternationalMobile) String() string {
if len(im.Mobile) == 0 {
return ""
}
if im.AreaCode == "" {
return im.Mobile
}
@@ -159,6 +167,17 @@ type ReceiverUpdateInput struct {
// example: example@gmail.com
Email string `json:"email"`
// swagger:ignore
EnabledEmail tristate.TriState `json:"enabled_email"`
// swagger:ignore
VerifiedEmail tristate.TriState `json:"verified_email"`
// swagger:ignore
EnabledMobile tristate.TriState `json:"enabled_mobile"`
// swagger:ignore
VerifiedMobile tristate.TriState `json:"verified_mobile"`
// swagger:ignore
Mobile string `json:"mobile"`
InternationalMobile SInternationalMobile `json:"international_mobile"`
// description: enabled contacts for user
+18 -4
View File
@@ -23,15 +23,15 @@ func TestMobileExt(t *testing.T) {
}{
{
SInternationalMobile{
"+8612345678901",
"+86",
Mobile: "+8612345678901",
AreaCode: "+86",
},
"12345678901",
},
{
SInternationalMobile{
"+8612345678901;ext=2",
"+86",
Mobile: "+8612345678901;ext=2",
AreaCode: "+86",
},
"12345678901",
},
@@ -56,6 +56,20 @@ func TestMobileExt(t *testing.T) {
},
"12345678901",
},
{
SInternationalMobile{
"13811111111",
"",
},
"13811111111",
},
{
SInternationalMobile{
"+85213811111111",
"",
},
"13811111111",
},
}
for _, c := range cases {
+78 -7
View File
@@ -15,16 +15,28 @@
package notify
import (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
notify_apis "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
type ReceiverManager struct {
modulebase.ResourceManager
}
type ConfigsManager struct {
modulebase.ResourceManager
}
var (
NotifyReceiver modulebase.ResourceManager
NotifyReceiver ReceiverManager
NotifyConfig modulebase.ResourceManager
NotifyRobot modulebase.ResourceManager
Notification modulebase.ResourceManager
@@ -34,13 +46,72 @@ var (
Configs ConfigsManager
)
func (rm *ReceiverManager) SyncUserContact(s *mcclient.ClientSession, userId string, mobile string, email string) error {
recv, err := rm.GetById(s, userId, nil)
if err != nil {
if httputils.ErrorCode(err) == 404 {
// not created yet, do create
newRecv := notify_apis.ReceiverCreateInput{
UID: userId,
}
if len(mobile) > 0 {
newRecv.InternationalMobile.Mobile = mobile
newRecv.EnabledContactTypes = append(newRecv.EnabledContactTypes, "mobile")
}
if len(email) > 0 {
newRecv.Email = email
newRecv.EnabledContactTypes = append(newRecv.EnabledContactTypes, "email")
}
_, err := rm.Create(s, jsonutils.Marshal(newRecv))
if err != nil {
// create failed
return errors.Wrap(err, "create receiver")
}
// success
} else {
return errors.Wrap(err, "GetById")
}
} else {
// receiver exists
fmt.Println("receiver exists", recv.String())
recvObj := notify_apis.ReceiverDetails{}
err := recv.Unmarshal(&recvObj)
if err != nil {
return errors.Wrap(err, "Unmarshal ReceiverDetails")
}
updateRecv := notify_apis.ReceiverUpdateInput{}
changeMobile := false
changeEmail := false
if mobile != "" && recvObj.InternationalMobile.Mobile != mobile {
updateRecv.InternationalMobile.Mobile = mobile
updateRecv.EnabledContactTypes = append(updateRecv.EnabledContactTypes, "mobile")
changeMobile = true
}
if email != "" && recvObj.Email != email {
updateRecv.Email = email
updateRecv.EnabledContactTypes = append(updateRecv.EnabledContactTypes, "email")
changeEmail = true
}
if changeMobile || changeEmail {
_, err = rm.Update(s, userId, jsonutils.Marshal(updateRecv))
if err != nil {
return errors.Wrap(err, "update receiver")
}
}
}
return nil
}
func init() {
NotifyReceiver = modules.NewNotifyv2Manager(
"receiver",
"receivers",
[]string{"ID", "Name", "Domain_Id", "Project_Domain", "Email", "International_Mobile", "Enabled_Contact_Types", "Verified_Infos"},
[]string{},
)
NotifyReceiver = ReceiverManager{
ResourceManager: modules.NewNotifyv2Manager(
"receiver",
"receivers",
[]string{"ID", "Name", "Domain_Id", "Project_Domain", "Email", "International_Mobile", "Enabled_Contact_Types", "Verified_Infos"},
[]string{},
),
}
modules.Register(&NotifyReceiver)
NotifyConfig = modules.NewNotifyv2Manager(
+44 -69
View File
@@ -94,7 +94,7 @@ type SReceiver struct {
Email string `width:"128" nullable:"false" create:"optional" update:"user" get:"user" list:"user"`
// swagger:ignore
Mobile string `width:"32" nullable:"false" create:"optional"`
Mobile string `width:"32" nullable:"false" create:"optional" update:"user"`
Lang string `width:"8" charset:"ascii" nullable:"false" list:"user" update:"user"`
// swagger:ignore
@@ -618,9 +618,34 @@ func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.To
return input, httperrors.NewInputParameterError("invalid email")
}
// validate mobile
input.InternationalMobile.AcceptExtMobile()
if ok := len(input.InternationalMobile.Mobile) == 0 || LaxMobileRegexp.MatchString(input.InternationalMobile.Mobile); !ok {
return input, httperrors.NewInputParameterError("invalid mobile")
if len(input.InternationalMobile.Mobile) > 0 {
input.InternationalMobile.AcceptExtMobile()
if ok := len(input.InternationalMobile.Mobile) == 0 || LaxMobileRegexp.MatchString(input.InternationalMobile.Mobile); !ok {
return input, httperrors.NewInputParameterError("invalid mobile")
}
input.Mobile = input.InternationalMobile.String()
} else {
input.InternationalMobile.AreaCode = ""
}
if input.ForceVerified {
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
if allowScope != rbacscope.ScopeSystem {
return input, httperrors.ErrNotSufficientPrivilege
}
}
if len(input.Email) > 0 && input.Email != r.Email {
if input.ForceVerified {
r.VerifiedEmail = tristate.True
} else {
r.VerifiedEmail = tristate.False
}
}
if len(input.Mobile) > 0 && input.Mobile != r.Mobile {
if input.ForceVerified {
r.VerifiedMobile = tristate.True
} else {
r.VerifiedMobile = tristate.False
}
}
for _, cType := range input.EnabledContactTypes {
@@ -633,78 +658,29 @@ func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.To
return input, nil
}
func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
r.SVirtualResourceBase.PreUpdate(ctx, userCred, query, data)
originEmailEnable, originMobileEnable := r.EnabledEmail, r.EnabledMobile
func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
r.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
var input api.ReceiverUpdateInput
data.Unmarshal(&input)
if len(input.Email) != 0 && input.Email != r.Email {
db.Update(r, func() error {
r.VerifiedEmail = tristate.False
return nil
})
r.VerifiedEmail = tristate.False
subs, _ := r.GetSubContacts()
for i := range subs {
if subs[i].ParentContactType == api.EMAIL {
db.Update(&subs[i], func() error {
subs[i].Verified = tristate.False
subs[i].VerifiedNote = "email changed, re-verify"
return nil
})
}
}
}
mobile := input.InternationalMobile.String()
if len(mobile) != 0 && mobile != r.Mobile {
db.Update(r, func() error {
r.VerifiedMobile = tristate.False
return nil
})
subs, _ := r.GetSubContacts()
for i := range subs {
if subs[i].ParentContactType == api.MOBILE {
db.Update(&subs[i], func() error {
subs[i].Verified = tristate.False
subs[i].VerifiedNote = "mobile changed, re-verify"
return nil
})
}
}
}
// 管理后台修改联系人,如果修改或者启用手机号和邮箱,无需进行校验
if input.ForceVerified {
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
if allowScope == rbacscope.ScopeSystem {
db.Update(r, func() error {
// 修改并启用
if len(input.Email) != 0 && input.Email != r.Email && r.EnabledEmail.Bool() {
r.VerifiedEmail = tristate.True
}
if len(mobile) != 0 && mobile != r.Mobile && r.EnabledMobile.Bool() {
r.VerifiedMobile = tristate.True
}
// 从禁用变启用
if !originEmailEnable.Bool() && r.EnabledEmail.Bool() {
r.VerifiedEmail = tristate.True
}
if !originMobileEnable.Bool() && r.EnabledMobile.Bool() {
r.VerifiedMobile = tristate.True
}
subs, _ := r.GetSubContacts()
for i := range subs {
if subs[i].ParentContactType == api.EMAIL && subs[i].Verified.IsTrue() && r.VerifiedEmail.IsFalse() {
db.Update(&subs[i], func() error {
subs[i].Verified = tristate.False
subs[i].VerifiedNote = "email changed, re-verify"
return nil
})
} else if subs[i].ParentContactType == api.MOBILE && subs[i].Verified.IsTrue() && r.VerifiedMobile.IsFalse() {
db.Update(&subs[i], func() error {
subs[i].Verified = tristate.False
subs[i].VerifiedNote = "mobile changed, re-verify"
return nil
})
}
}
r.Mobile = mobile
err := ReceiverManager.TableSpec().InsertOrUpdate(ctx, r)
if err != nil {
log.Errorf("InsertOrUpdate: %v", err)
}
}
func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
r.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
cTypes := jsonutils.GetQueryStringArray(data, "enabled_contact_types")
err := r.StartSubcontactPullTask(ctx, userCred, cTypes, "")
if err != nil {
@@ -1080,7 +1056,6 @@ func (r *SReceiver) SetContact(cType string, contact string) error {
r.Mobile = contact
return nil
})
r.Mobile = contact
default:
subs, _ := r.GetSubContacts()
for i := range subs {
+6 -2
View File
@@ -80,8 +80,12 @@ func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneMo
// sync email and mobile to keystone
s := auth.GetSession(ctx, self.UserCred, "")
mobile := receiver.Mobile
if strings.HasPrefix(mobile, "+86 ") {
mobile = strings.TrimSpace(mobile[4:])
if strings.HasPrefix(mobile, "+") {
spaceIdx := strings.Index(mobile, " ")
if spaceIdx > 0 {
mobile = mobile[spaceIdx+1:]
}
mobile = strings.TrimSpace(mobile)
}
params := map[string]string{
"email": receiver.Email,