feat: add SMS verification registration with UniSMS provider

- Add phone field to user model with index and helper methods
- Implement SMS provider interface with UniSMS (合一短信) implementation
- Add SMS verification code sending endpoint with rate limiting (1/60s)
- Support SMS registration in Register() (mutually exclusive with email)
- Add SMS configuration to admin settings (provider, keys, signature, template)
- Display phone number in admin user list contact column
- Add i18n translations for all SMS-related messages (zh-CN, en, zh-TW)
- Add Claude Code skills: sync-upstream, migrate-server
- Update CLAUDE.md with git conventions and deployment guide

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nosqli
2026-03-06 23:57:15 +08:00
parent 4a4cf0a0df
commit 835cd8e74b
26 changed files with 815 additions and 17 deletions

View File

@@ -157,7 +157,21 @@ func Register(c *gin.Context) {
return
}
}
exist, err := model.CheckUserExistOrDeleted(user.Username, user.Email)
if common.SMSVerificationEnabled {
if user.Phone == "" || user.SmsVerificationCode == "" {
common.ApiErrorI18n(c, i18n.MsgUserSmsVerificationRequired)
return
}
if !common.IsValidPhone(user.Phone) {
common.ApiErrorI18n(c, i18n.MsgUserPhoneFormatInvalid)
return
}
if !common.VerifyCodeWithKey(user.Phone, user.SmsVerificationCode, common.SmsVerificationPurpose) {
common.ApiErrorI18n(c, i18n.MsgUserVerificationCodeError)
return
}
}
exist, err := model.CheckUserExistOrDeleted(user.Username, user.Email, user.Phone)
if err != nil {
common.ApiErrorI18n(c, i18n.MsgDatabaseError)
common.SysLog(fmt.Sprintf("CheckUserExistOrDeleted error: %v", err))
@@ -179,6 +193,9 @@ func Register(c *gin.Context) {
if common.EmailVerificationEnabled {
cleanUser.Email = user.Email
}
if common.SMSVerificationEnabled {
cleanUser.Phone = user.Phone
}
if err := cleanUser.Insert(inviterId); err != nil {
common.ApiError(c, err)
return
@@ -390,6 +407,7 @@ func GetSelf(c *gin.Context) {
"role": user.Role,
"status": user.Status,
"email": user.Email,
"phone": user.Phone,
"github_id": user.GitHubId,
"discord_id": user.DiscordId,
"oidc_id": user.OidcId,