diff --git a/backend/deploy/envs/user.env b/backend/deploy/envs/user.env
index 5416ff1..844a8be 100644
--- a/backend/deploy/envs/user.env
+++ b/backend/deploy/envs/user.env
@@ -3,3 +3,23 @@
# 服务端口
PORT=20000
+
+# ==================== 数据库配置 ====================
+DB_HOST=localhost
+DB_PORT=15432
+DB_USER=postgres
+DB_PASSWORD=123456
+DB_NAME=top-fans
+
+# ==================== Redis 配置 ====================
+REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_DB=0
+
+# ==================== 短信配置 ====================
+# 与 asset.env 中的阿里云 AccessKey 相同(同一账号)
+SMS_ACCESS_KEY_ID=LTAI5t6QcdJHpYbCPxM8SXYE
+SMS_ACCESS_KEY_SECRET=ybvjSEb7wilMt3qT5nOppYPoNVayCD
+SMS_SIGN_NAME=TopFans
+SMS_TEMPLATE_CODE=SMS_314621237
+SMS_REGION=cn-hangzhou
diff --git a/backend/dev.sh b/backend/dev.sh
index 3a2ac4c..1d04dd4 100755
--- a/backend/dev.sh
+++ b/backend/dev.sh
@@ -86,23 +86,32 @@ DB_PORT="${DB_PORT:-5432}"
DB_USER="${DB_USER:-haihuizhu}"
DB_PASSWORD="${DB_PASSWORD:-admin}"
DB_NAME="${DB_NAME:-top-fans}"
+REDIS_HOST="${REDIS_HOST:-localhost}"
+REDIS_PORT="${REDIS_PORT:-6379}"
+REDIS_DB="${REDIS_DB:-0}"
DB_ARGS=(-db-host="$DB_HOST" -db-port="$DB_PORT" -db-user="$DB_USER" -db-password="$DB_PASSWORD" -db-name="$DB_NAME")
+REDIS_ARGS=(-redis-host="$REDIS_HOST" -redis-port="$REDIS_PORT" -redis-db="$REDIS_DB")
# 启动一个服务
-# 用法: start_service name binary port use_db
+# 用法: start_service name binary port use_db use_redis
start_service() {
local name=$1
local binary=$2
local port=$3
local use_db=$4
+ local use_redis=$5
echo -e "${GREEN}🚀 启动 $name...${NC}"
+ local args=("-port=$port")
if [ "$use_db" = "1" ]; then
- "$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
- else
- "$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
+ args+=("${DB_ARGS[@]}")
fi
+ if [ "$use_redis" = "1" ]; then
+ args+=("${REDIS_ARGS[@]}")
+ fi
+
+ "$SCRIPT_DIR/$binary" "${args[@]}" > "/tmp/${name}.log" 2>&1 &
local pid=$!
# 保存 PID 到文件
echo $pid > "/tmp/dev_sh_${name}.pid"
@@ -140,13 +149,14 @@ build_service() {
}
# 重建并重启单个服务(构建成功后才杀旧进程)
-# 用法: restart_service name dir binary port use_db
+# 用法: restart_service name dir binary port use_db use_redis
restart_service() {
local name=$1
local dir=$2
local binary=$3
local port=$4
local use_db=$5
+ local use_redis=$6
local pid_file="/tmp/dev_sh_${name}.pid"
local lock_file="/tmp/dev_sh_${name}.lock"
@@ -199,11 +209,14 @@ restart_service() {
# Step 3: 启动新进程
sleep 1
cd "$SCRIPT_DIR"
+ local args=("-port=$port")
if [ "$use_db" = "1" ]; then
- "$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
- else
- "$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
+ args+=("${DB_ARGS[@]}")
fi
+ if [ "$use_redis" = "1" ]; then
+ args+=("${REDIS_ARGS[@]}")
+ fi
+ "$SCRIPT_DIR/$binary" "${args[@]}" > "/tmp/${name}.log" 2>&1 &
local new_pid=$!
echo $new_pid > "$pid_file"
sleep 2
@@ -219,7 +232,7 @@ restart_service() {
}
# 启动文件监听器
-# 用法: start_watcher name dir1:dir2:dir3 binary port use_db
+# 用法: start_watcher name dir1:dir2:dir3 binary port use_db use_redis
# 注意: 多个目录用冒号分隔
start_watcher() {
local name=$1
@@ -227,6 +240,7 @@ start_watcher() {
local binary=$3
local port=$4
local use_db=$5
+ local use_redis=$6
local watch_paths=()
local watch_path=""
local restart_marker="/tmp/dev_sh_${name}_restart"
@@ -322,10 +336,10 @@ start_watcher() {
build_proto
# proto 变化时重启 gateway(gateway 是 proto 客户端的调用方)
echo -e "${YELLOW}🔄 [Proto 变化] 重启 gateway...${NC}"
- restart_service "gateway" "gateway" "gateway/gateway" "8080" "0"
+ restart_service "gateway" "gateway" "gateway/gateway" "8080" "0" "0"
fi
- restart_service "$name" "${watch_paths[0]}" "$binary" "$port" "$use_db"
+ restart_service "$name" "${watch_paths[0]}" "$binary" "$port" "$use_db" "$use_redis"
fi
done
) &
@@ -387,31 +401,31 @@ cd "$SCRIPT_DIR"
# 启动所有服务
echo ""
echo -e "${YELLOW}🚀 启动所有服务...${NC}"
-start_service "userService" "services/userService/userService" 20000 1
-start_service "assetService" "services/assetService/assetService" 20003 1
-start_service "socialService" "services/socialService/socialService" 20002 1
+start_service "userService" "services/userService/userService" 20000 1 1
+start_service "assetService" "services/assetService/assetService" 20003 1 0
+start_service "socialService" "services/socialService/socialService" 20002 1 0
# galleryService 需要连接 taskService (20006),单独处理
echo -e "${GREEN}🚀 启动 galleryService...${NC}"
"$SCRIPT_DIR/services/galleryService/galleryService" -port=20004 -task-service-url="tri://localhost:20006" "${DB_ARGS[@]}" > "/tmp/galleryService.log" 2>&1 &
echo $! > "/tmp/dev_sh_galleryService.pid"
sleep 2
echo -e "${GREEN}✅ galleryService 已启动 (PID: $(cat /tmp/dev_sh_galleryService.pid), 端口: 20004)${NC}"
-start_service "activityService" "services/activityService/activityService" 20005 1
-start_service "taskService" "services/taskService/taskService" 20006 1
-start_service "starbookService" "services/starbookService/starbookService" 20007 1
-start_service "gateway" "gateway/gateway" 8080 0
+start_service "activityService" "services/activityService/activityService" 20005 1 0
+start_service "taskService" "services/taskService/taskService" 20006 1 0
+start_service "starbookService" "services/starbookService/starbookService" 20007 1 0
+start_service "gateway" "gateway/gateway" 8080 0 0
# 启动所有文件监听器
echo ""
echo -e "${YELLOW}👁️ 启动所有文件监听器...${NC}"
-start_watcher "gateway" "gateway:pkg/proto" "gateway/gateway" 8080 0
-start_watcher "userService" "services/userService" "services/userService/userService" 20000 1
-start_watcher "assetService" "services/assetService:pkg/proto/asset" "services/assetService/assetService" 20003 1
-start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1
-start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1
-start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1
-start_watcher "taskService" "services/taskService" "services/taskService/taskService" 20006 1
-start_watcher "starbookService" "services/starbookService" "services/starbookService/starbookService" 20007 1
+start_watcher "gateway" "gateway:pkg/proto" "gateway/gateway" 8080 0 0
+start_watcher "userService" "services/userService" "services/userService/userService" 20000 1 1
+start_watcher "assetService" "services/assetService:pkg/proto/asset" "services/assetService/assetService" 20003 1 0
+start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1 0
+start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1 0
+start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1 0
+start_watcher "taskService" "services/taskService" "services/taskService/taskService" 20006 1 0
+start_watcher "starbookService" "services/starbookService" "services/starbookService/starbookService" 20007 1 0
echo ""
echo -e "${GREEN}========================================${NC}"
diff --git a/backend/gateway/controller/auth_controller.go b/backend/gateway/controller/auth_controller.go
index 67bdae2..2a5c839 100644
--- a/backend/gateway/controller/auth_controller.go
+++ b/backend/gateway/controller/auth_controller.go
@@ -363,6 +363,107 @@ func (ctrl *AuthController) CheckNickname(c *gin.Context) {
})
}
+// SendCode 发送验证码
+// @Summary 发送验证码
+// @Description 发送手机验证码,用于注册或重置密码
+// @Tags auth
+// @Accept json
+// @Produce json
+// @Param request body dto.SendCodeRequest true "发送验证码请求"
+// @Success 200 {object} response.Response{data=dto.SendCodeResponse}
+// @Router /api/v1/auth/send-code [post]
+func (ctrl *AuthController) SendCode(c *gin.Context) {
+ var req dto.SendCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ logger.Logger.Warn("Invalid send code request", zap.Error(err))
+ response.BadRequest(c, "参数错误")
+ return
+ }
+
+ logger.Logger.Info("SendCode request received",
+ zap.String("mobile", req.Mobile),
+ zap.String("scene", req.Scene),
+ )
+
+ // 调用 Dubbo 服务
+ ctx := context.Background()
+ resp, err := ctrl.userServiceClient.SendCode(ctx, &pb.SendCodeRequest{
+ Mobile: req.Mobile,
+ Scene: req.Scene,
+ })
+ if err != nil {
+ logger.Logger.Error("SendCode failed", zap.Error(err))
+ response.HandleError(c, err)
+ return
+ }
+
+ // 检查业务错误
+ if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
+ response.HandleError(c, &pbError{message: resp.Base.Message})
+ return
+ }
+
+ logger.Logger.Info("SendCode successful",
+ zap.String("mobile", req.Mobile),
+ )
+
+ response.Success(c, gin.H{
+ "expires_in": resp.ExpiresIn,
+ })
+}
+
+// VerifyCode 验证验证码
+// @Summary 验证验证码
+// @Description 验证手机验证码,验证成功后返回 verify_token
+// @Tags auth
+// @Accept json
+// @Produce json
+// @Param request body dto.VerifyCodeRequest true "验证验证码请求"
+// @Success 200 {object} response.Response{data=dto.VerifyCodeResponse}
+// @Router /api/v1/auth/verify-code [post]
+func (ctrl *AuthController) VerifyCode(c *gin.Context) {
+ var req dto.VerifyCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ logger.Logger.Warn("Invalid verify code request", zap.Error(err))
+ response.BadRequest(c, "参数错误")
+ return
+ }
+
+ logger.Logger.Info("VerifyCode request received",
+ zap.String("mobile", req.Mobile),
+ zap.String("scene", req.Scene),
+ )
+
+ // 调用 Dubbo 服务
+ ctx := context.Background()
+ resp, err := ctrl.userServiceClient.VerifyCode(ctx, &pb.VerifyCodeRequest{
+ Mobile: req.Mobile,
+ Code: req.Code,
+ Scene: req.Scene,
+ })
+ if err != nil {
+ logger.Logger.Error("VerifyCode failed", zap.Error(err))
+ response.HandleError(c, err)
+ return
+ }
+
+ // 检查业务错误
+ if resp.Base != nil && resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
+ response.HandleError(c, &pbError{message: resp.Base.Message})
+ return
+ }
+
+ logger.Logger.Info("VerifyCode successful",
+ zap.String("mobile", req.Mobile),
+ )
+
+ response.Success(c, gin.H{
+ "verified": resp.Verified,
+ "verify_token": resp.VerifyToken,
+ "expires_in": resp.ExpiresIn,
+ })
+}
+
// CheckMobile 检查手机号是否已被注册
// @Summary 检查手机号是否被注册
// @Description 检查指定手机号是否已被他人使用
diff --git a/backend/gateway/dto/auth_sms_dto.go b/backend/gateway/dto/auth_sms_dto.go
new file mode 100644
index 0000000..cfc722a
--- /dev/null
+++ b/backend/gateway/dto/auth_sms_dto.go
@@ -0,0 +1,30 @@
+package dto
+
+// SendCodeRequest 发送验证码请求
+type SendCodeRequest struct {
+ Mobile string `json:"mobile" binding:"required"`
+ Scene string `json:"scene" binding:"required"` // register, password
+}
+
+// SendCodeResponse 发送验证码响应
+type SendCodeResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ ExpiresIn int `json:"expires_in"` // 多少秒后可以重发
+}
+
+// VerifyCodeRequest 验证验证码请求
+type VerifyCodeRequest struct {
+ Mobile string `json:"mobile" binding:"required"`
+ Code string `json:"code" binding:"required"`
+ Scene string `json:"scene" binding:"required"`
+}
+
+// VerifyCodeResponse 验证验证码响应
+type VerifyCodeResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Verified bool `json:"verified"`
+ VerifyToken string `json:"verify_token"`
+ ExpiresIn int `json:"expires_in"` // token有效期(秒)
+}
\ No newline at end of file
diff --git a/backend/gateway/router/router.go b/backend/gateway/router/router.go
index 48a4bf1..584ef4a 100644
--- a/backend/gateway/router/router.go
+++ b/backend/gateway/router/router.go
@@ -87,6 +87,8 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl
auth.POST("/validate", authCtrl.ValidateToken) // 验证 Token
auth.POST("/check-nickname", authCtrl.CheckNickname) // 检查昵称是否被注册
auth.POST("/check-mobile", authCtrl.CheckMobile) // 检查手机号是否被注册
+ auth.POST("/send-code", authCtrl.SendCode) // 发送验证码
+ auth.POST("/verify-code", authCtrl.VerifyCode) // 验证验证码
}
// 认证相关路由(需要认证)
diff --git a/backend/go.work.sum b/backend/go.work.sum
index f897d0d..b11b254 100644
--- a/backend/go.work.sum
+++ b/backend/go.work.sum
@@ -26,6 +26,39 @@ github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HR
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
+github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
+github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
+github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
+github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
+github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
+github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8 h1:aDPyz6C+nenypx24N5qEt09NjpS6mu7Cu1A+wf9UTaY=
+github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8/go.mod h1:e/vWJ5gLVnraPROSh+3oMSodf5ukaUlqNgH0IIcnz98=
+github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
+github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
+github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94=
+github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
+github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.8/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
+github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
+github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
+github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
+github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
+github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
@@ -60,6 +93,9 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM=
github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
+github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI=
@@ -138,11 +174,13 @@ github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0s
github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 h1:7qnwS9+oeSiOIsiUMajT+0R7HR6hw5NegnKPmn/94oI=
github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFRbV5juy/C3MGdj4ePi+g6ePIp4=
github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
@@ -257,6 +295,7 @@ github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
@@ -269,6 +308,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rhnvrm/simples3 v0.6.1 h1:H0DJwybR6ryQE+Odi9eqkHuzjYAeJgtGcGtuBwOhsH8=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -276,13 +316,18 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCL
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
+github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
@@ -290,21 +335,77 @@ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
+github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
+golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
+golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k=
@@ -316,6 +417,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
+gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
diff --git a/backend/pkg/proto/user/user.pb.go b/backend/pkg/proto/user/user.pb.go
index ec0c859..6711966 100644
--- a/backend/pkg/proto/user/user.pb.go
+++ b/backend/pkg/proto/user/user.pb.go
@@ -385,10 +385,11 @@ func (x *Star) GetTag() string {
// 注册请求
type RegisterRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` // 手机号
- Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` // 密码
- StarId int64 `protobuf:"varint,3,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 选择第一个粉丝身份的明星ID
- Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` // 第一个粉丝身份的昵称
+ Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` // 手机号
+ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` // 密码
+ StarId int64 `protobuf:"varint,3,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 选择第一个粉丝身份的明星ID
+ Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` // 第一个粉丝身份的昵称
+ VerifyToken string `protobuf:"bytes,5,opt,name=verify_token,json=verifyToken,proto3" json:"verify_token,omitempty"` // 短信验证token(注册前必须先通过短信验证)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -451,6 +452,13 @@ func (x *RegisterRequest) GetNickname() string {
return ""
}
+func (x *RegisterRequest) GetVerifyToken() string {
+ if x != nil {
+ return x.VerifyToken
+ }
+ return ""
+}
+
// 注册响应
type RegisterResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1172,6 +1180,242 @@ func (x *CheckMobileResponse) GetExists() bool {
return false
}
+// 发送验证码请求
+type SendCodeRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` // 手机号
+ Scene string `protobuf:"bytes,2,opt,name=scene,proto3" json:"scene,omitempty"` // 场景:register, password
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SendCodeRequest) Reset() {
+ *x = SendCodeRequest{}
+ mi := &file_user_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SendCodeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SendCodeRequest) ProtoMessage() {}
+
+func (x *SendCodeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_user_proto_msgTypes[17]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SendCodeRequest.ProtoReflect.Descriptor instead.
+func (*SendCodeRequest) Descriptor() ([]byte, []int) {
+ return file_user_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *SendCodeRequest) GetMobile() string {
+ if x != nil {
+ return x.Mobile
+ }
+ return ""
+}
+
+func (x *SendCodeRequest) GetScene() string {
+ if x != nil {
+ return x.Scene
+ }
+ return ""
+}
+
+// 发送验证码响应
+type SendCodeResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
+ ExpiresIn int32 `protobuf:"varint,2,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` // 多少秒后可以重发
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SendCodeResponse) Reset() {
+ *x = SendCodeResponse{}
+ mi := &file_user_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SendCodeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SendCodeResponse) ProtoMessage() {}
+
+func (x *SendCodeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_user_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SendCodeResponse.ProtoReflect.Descriptor instead.
+func (*SendCodeResponse) Descriptor() ([]byte, []int) {
+ return file_user_proto_rawDescGZIP(), []int{18}
+}
+
+func (x *SendCodeResponse) GetBase() *common.BaseResponse {
+ if x != nil {
+ return x.Base
+ }
+ return nil
+}
+
+func (x *SendCodeResponse) GetExpiresIn() int32 {
+ if x != nil {
+ return x.ExpiresIn
+ }
+ return 0
+}
+
+// 验证验证码请求
+type VerifyCodeRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` // 手机号
+ Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` // 验证码
+ Scene string `protobuf:"bytes,3,opt,name=scene,proto3" json:"scene,omitempty"` // 场景:register, password
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *VerifyCodeRequest) Reset() {
+ *x = VerifyCodeRequest{}
+ mi := &file_user_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *VerifyCodeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*VerifyCodeRequest) ProtoMessage() {}
+
+func (x *VerifyCodeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_user_proto_msgTypes[19]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use VerifyCodeRequest.ProtoReflect.Descriptor instead.
+func (*VerifyCodeRequest) Descriptor() ([]byte, []int) {
+ return file_user_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *VerifyCodeRequest) GetMobile() string {
+ if x != nil {
+ return x.Mobile
+ }
+ return ""
+}
+
+func (x *VerifyCodeRequest) GetCode() string {
+ if x != nil {
+ return x.Code
+ }
+ return ""
+}
+
+func (x *VerifyCodeRequest) GetScene() string {
+ if x != nil {
+ return x.Scene
+ }
+ return ""
+}
+
+// 验证验证码响应
+type VerifyCodeResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
+ Verified bool `protobuf:"varint,2,opt,name=verified,proto3" json:"verified,omitempty"` // 是否验证成功
+ VerifyToken string `protobuf:"bytes,3,opt,name=verify_token,json=verifyToken,proto3" json:"verify_token,omitempty"` // 验证成功后返回的token
+ ExpiresIn int32 `protobuf:"varint,4,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` // token有效期(秒)
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *VerifyCodeResponse) Reset() {
+ *x = VerifyCodeResponse{}
+ mi := &file_user_proto_msgTypes[20]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *VerifyCodeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*VerifyCodeResponse) ProtoMessage() {}
+
+func (x *VerifyCodeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_user_proto_msgTypes[20]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use VerifyCodeResponse.ProtoReflect.Descriptor instead.
+func (*VerifyCodeResponse) Descriptor() ([]byte, []int) {
+ return file_user_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *VerifyCodeResponse) GetBase() *common.BaseResponse {
+ if x != nil {
+ return x.Base
+ }
+ return nil
+}
+
+func (x *VerifyCodeResponse) GetVerified() bool {
+ if x != nil {
+ return x.Verified
+ }
+ return false
+}
+
+func (x *VerifyCodeResponse) GetVerifyToken() string {
+ if x != nil {
+ return x.VerifyToken
+ }
+ return ""
+}
+
+func (x *VerifyCodeResponse) GetExpiresIn() int32 {
+ if x != nil {
+ return x.ExpiresIn
+ }
+ return 0
+}
+
// 获取用户信息请求
type GetUserRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1182,7 +1426,7 @@ type GetUserRequest struct {
func (x *GetUserRequest) Reset() {
*x = GetUserRequest{}
- mi := &file_user_proto_msgTypes[17]
+ mi := &file_user_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1194,7 +1438,7 @@ func (x *GetUserRequest) String() string {
func (*GetUserRequest) ProtoMessage() {}
func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[17]
+ mi := &file_user_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1207,7 +1451,7 @@ func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead.
func (*GetUserRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{17}
+ return file_user_proto_rawDescGZIP(), []int{21}
}
func (x *GetUserRequest) GetUserId() int64 {
@@ -1228,7 +1472,7 @@ type GetUserResponse struct {
func (x *GetUserResponse) Reset() {
*x = GetUserResponse{}
- mi := &file_user_proto_msgTypes[18]
+ mi := &file_user_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1240,7 +1484,7 @@ func (x *GetUserResponse) String() string {
func (*GetUserResponse) ProtoMessage() {}
func (x *GetUserResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[18]
+ mi := &file_user_proto_msgTypes[22]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1253,7 +1497,7 @@ func (x *GetUserResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserResponse.ProtoReflect.Descriptor instead.
func (*GetUserResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{18}
+ return file_user_proto_rawDescGZIP(), []int{22}
}
func (x *GetUserResponse) GetBase() *common.BaseResponse {
@@ -1281,7 +1525,7 @@ type GetFanProfileRequest struct {
func (x *GetFanProfileRequest) Reset() {
*x = GetFanProfileRequest{}
- mi := &file_user_proto_msgTypes[19]
+ mi := &file_user_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1293,7 +1537,7 @@ func (x *GetFanProfileRequest) String() string {
func (*GetFanProfileRequest) ProtoMessage() {}
func (x *GetFanProfileRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[19]
+ mi := &file_user_proto_msgTypes[23]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1306,7 +1550,7 @@ func (x *GetFanProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFanProfileRequest.ProtoReflect.Descriptor instead.
func (*GetFanProfileRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{19}
+ return file_user_proto_rawDescGZIP(), []int{23}
}
func (x *GetFanProfileRequest) GetUserId() int64 {
@@ -1334,7 +1578,7 @@ type GetFanProfileResponse struct {
func (x *GetFanProfileResponse) Reset() {
*x = GetFanProfileResponse{}
- mi := &file_user_proto_msgTypes[20]
+ mi := &file_user_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1346,7 +1590,7 @@ func (x *GetFanProfileResponse) String() string {
func (*GetFanProfileResponse) ProtoMessage() {}
func (x *GetFanProfileResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[20]
+ mi := &file_user_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1359,7 +1603,7 @@ func (x *GetFanProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFanProfileResponse.ProtoReflect.Descriptor instead.
func (*GetFanProfileResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{20}
+ return file_user_proto_rawDescGZIP(), []int{24}
}
func (x *GetFanProfileResponse) GetBase() *common.BaseResponse {
@@ -1388,7 +1632,7 @@ type UpdateFanProfileSocialRequest struct {
func (x *UpdateFanProfileSocialRequest) Reset() {
*x = UpdateFanProfileSocialRequest{}
- mi := &file_user_proto_msgTypes[21]
+ mi := &file_user_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1400,7 +1644,7 @@ func (x *UpdateFanProfileSocialRequest) String() string {
func (*UpdateFanProfileSocialRequest) ProtoMessage() {}
func (x *UpdateFanProfileSocialRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[21]
+ mi := &file_user_proto_msgTypes[25]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1413,7 +1657,7 @@ func (x *UpdateFanProfileSocialRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateFanProfileSocialRequest.ProtoReflect.Descriptor instead.
func (*UpdateFanProfileSocialRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{21}
+ return file_user_proto_rawDescGZIP(), []int{25}
}
func (x *UpdateFanProfileSocialRequest) GetUserId() int64 {
@@ -1448,7 +1692,7 @@ type UpdateFanProfileSocialResponse struct {
func (x *UpdateFanProfileSocialResponse) Reset() {
*x = UpdateFanProfileSocialResponse{}
- mi := &file_user_proto_msgTypes[22]
+ mi := &file_user_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1460,7 +1704,7 @@ func (x *UpdateFanProfileSocialResponse) String() string {
func (*UpdateFanProfileSocialResponse) ProtoMessage() {}
func (x *UpdateFanProfileSocialResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[22]
+ mi := &file_user_proto_msgTypes[26]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1473,7 +1717,7 @@ func (x *UpdateFanProfileSocialResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateFanProfileSocialResponse.ProtoReflect.Descriptor instead.
func (*UpdateFanProfileSocialResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{22}
+ return file_user_proto_rawDescGZIP(), []int{26}
}
func (x *UpdateFanProfileSocialResponse) GetBase() *common.BaseResponse {
@@ -1505,7 +1749,7 @@ type UpdateCrystalBalanceRequest struct {
func (x *UpdateCrystalBalanceRequest) Reset() {
*x = UpdateCrystalBalanceRequest{}
- mi := &file_user_proto_msgTypes[23]
+ mi := &file_user_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1517,7 +1761,7 @@ func (x *UpdateCrystalBalanceRequest) String() string {
func (*UpdateCrystalBalanceRequest) ProtoMessage() {}
func (x *UpdateCrystalBalanceRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[23]
+ mi := &file_user_proto_msgTypes[27]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1530,7 +1774,7 @@ func (x *UpdateCrystalBalanceRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateCrystalBalanceRequest.ProtoReflect.Descriptor instead.
func (*UpdateCrystalBalanceRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{23}
+ return file_user_proto_rawDescGZIP(), []int{27}
}
func (x *UpdateCrystalBalanceRequest) GetUserId() int64 {
@@ -1586,7 +1830,7 @@ type UpdateCrystalBalanceResponse struct {
func (x *UpdateCrystalBalanceResponse) Reset() {
*x = UpdateCrystalBalanceResponse{}
- mi := &file_user_proto_msgTypes[24]
+ mi := &file_user_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1598,7 +1842,7 @@ func (x *UpdateCrystalBalanceResponse) String() string {
func (*UpdateCrystalBalanceResponse) ProtoMessage() {}
func (x *UpdateCrystalBalanceResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[24]
+ mi := &file_user_proto_msgTypes[28]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1611,7 +1855,7 @@ func (x *UpdateCrystalBalanceResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateCrystalBalanceResponse.ProtoReflect.Descriptor instead.
func (*UpdateCrystalBalanceResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{24}
+ return file_user_proto_rawDescGZIP(), []int{28}
}
func (x *UpdateCrystalBalanceResponse) GetBase() *common.BaseResponse {
@@ -1640,7 +1884,7 @@ type UpdateAssetsCountRequest struct {
func (x *UpdateAssetsCountRequest) Reset() {
*x = UpdateAssetsCountRequest{}
- mi := &file_user_proto_msgTypes[25]
+ mi := &file_user_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1652,7 +1896,7 @@ func (x *UpdateAssetsCountRequest) String() string {
func (*UpdateAssetsCountRequest) ProtoMessage() {}
func (x *UpdateAssetsCountRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[25]
+ mi := &file_user_proto_msgTypes[29]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1665,7 +1909,7 @@ func (x *UpdateAssetsCountRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAssetsCountRequest.ProtoReflect.Descriptor instead.
func (*UpdateAssetsCountRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{25}
+ return file_user_proto_rawDescGZIP(), []int{29}
}
func (x *UpdateAssetsCountRequest) GetUserId() int64 {
@@ -1700,7 +1944,7 @@ type UpdateAssetsCountResponse struct {
func (x *UpdateAssetsCountResponse) Reset() {
*x = UpdateAssetsCountResponse{}
- mi := &file_user_proto_msgTypes[26]
+ mi := &file_user_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1712,7 +1956,7 @@ func (x *UpdateAssetsCountResponse) String() string {
func (*UpdateAssetsCountResponse) ProtoMessage() {}
func (x *UpdateAssetsCountResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[26]
+ mi := &file_user_proto_msgTypes[30]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1725,7 +1969,7 @@ func (x *UpdateAssetsCountResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAssetsCountResponse.ProtoReflect.Descriptor instead.
func (*UpdateAssetsCountResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{26}
+ return file_user_proto_rawDescGZIP(), []int{30}
}
func (x *UpdateAssetsCountResponse) GetBase() *common.BaseResponse {
@@ -1755,7 +1999,7 @@ type AddExhibitionHoursRequest struct {
func (x *AddExhibitionHoursRequest) Reset() {
*x = AddExhibitionHoursRequest{}
- mi := &file_user_proto_msgTypes[27]
+ mi := &file_user_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1767,7 +2011,7 @@ func (x *AddExhibitionHoursRequest) String() string {
func (*AddExhibitionHoursRequest) ProtoMessage() {}
func (x *AddExhibitionHoursRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[27]
+ mi := &file_user_proto_msgTypes[31]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1780,7 +2024,7 @@ func (x *AddExhibitionHoursRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddExhibitionHoursRequest.ProtoReflect.Descriptor instead.
func (*AddExhibitionHoursRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{27}
+ return file_user_proto_rawDescGZIP(), []int{31}
}
func (x *AddExhibitionHoursRequest) GetUserId() int64 {
@@ -1824,7 +2068,7 @@ type AddExhibitionHoursResponse struct {
func (x *AddExhibitionHoursResponse) Reset() {
*x = AddExhibitionHoursResponse{}
- mi := &file_user_proto_msgTypes[28]
+ mi := &file_user_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1836,7 +2080,7 @@ func (x *AddExhibitionHoursResponse) String() string {
func (*AddExhibitionHoursResponse) ProtoMessage() {}
func (x *AddExhibitionHoursResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[28]
+ mi := &file_user_proto_msgTypes[32]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1849,7 +2093,7 @@ func (x *AddExhibitionHoursResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddExhibitionHoursResponse.ProtoReflect.Descriptor instead.
func (*AddExhibitionHoursResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{28}
+ return file_user_proto_rawDescGZIP(), []int{32}
}
func (x *AddExhibitionHoursResponse) GetBase() *common.BaseResponse {
@@ -1889,7 +2133,7 @@ type GetCurrentUserRequest struct {
func (x *GetCurrentUserRequest) Reset() {
*x = GetCurrentUserRequest{}
- mi := &file_user_proto_msgTypes[29]
+ mi := &file_user_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1901,7 +2145,7 @@ func (x *GetCurrentUserRequest) String() string {
func (*GetCurrentUserRequest) ProtoMessage() {}
func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[29]
+ mi := &file_user_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1914,7 +2158,7 @@ func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead.
func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{29}
+ return file_user_proto_rawDescGZIP(), []int{33}
}
// 获取当前登录用户信息响应
@@ -1930,7 +2174,7 @@ type GetCurrentUserResponse struct {
func (x *GetCurrentUserResponse) Reset() {
*x = GetCurrentUserResponse{}
- mi := &file_user_proto_msgTypes[30]
+ mi := &file_user_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1942,7 +2186,7 @@ func (x *GetCurrentUserResponse) String() string {
func (*GetCurrentUserResponse) ProtoMessage() {}
func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[30]
+ mi := &file_user_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1955,7 +2199,7 @@ func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead.
func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{30}
+ return file_user_proto_rawDescGZIP(), []int{34}
}
func (x *GetCurrentUserResponse) GetBase() *common.BaseResponse {
@@ -1995,7 +2239,7 @@ type GetMyProfileRequest struct {
func (x *GetMyProfileRequest) Reset() {
*x = GetMyProfileRequest{}
- mi := &file_user_proto_msgTypes[31]
+ mi := &file_user_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2007,7 +2251,7 @@ func (x *GetMyProfileRequest) String() string {
func (*GetMyProfileRequest) ProtoMessage() {}
func (x *GetMyProfileRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[31]
+ mi := &file_user_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2020,7 +2264,7 @@ func (x *GetMyProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyProfileRequest.ProtoReflect.Descriptor instead.
func (*GetMyProfileRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{31}
+ return file_user_proto_rawDescGZIP(), []int{35}
}
// 获取个人信息页响应
@@ -2036,7 +2280,7 @@ type GetMyProfileResponse struct {
func (x *GetMyProfileResponse) Reset() {
*x = GetMyProfileResponse{}
- mi := &file_user_proto_msgTypes[32]
+ mi := &file_user_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2048,7 +2292,7 @@ func (x *GetMyProfileResponse) String() string {
func (*GetMyProfileResponse) ProtoMessage() {}
func (x *GetMyProfileResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[32]
+ mi := &file_user_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2061,7 +2305,7 @@ func (x *GetMyProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyProfileResponse.ProtoReflect.Descriptor instead.
func (*GetMyProfileResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{32}
+ return file_user_proto_rawDescGZIP(), []int{36}
}
func (x *GetMyProfileResponse) GetBase() *common.BaseResponse {
@@ -2102,7 +2346,7 @@ type UpdateNicknameRequest struct {
func (x *UpdateNicknameRequest) Reset() {
*x = UpdateNicknameRequest{}
- mi := &file_user_proto_msgTypes[33]
+ mi := &file_user_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2114,7 +2358,7 @@ func (x *UpdateNicknameRequest) String() string {
func (*UpdateNicknameRequest) ProtoMessage() {}
func (x *UpdateNicknameRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[33]
+ mi := &file_user_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2127,7 +2371,7 @@ func (x *UpdateNicknameRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateNicknameRequest.ProtoReflect.Descriptor instead.
func (*UpdateNicknameRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{33}
+ return file_user_proto_rawDescGZIP(), []int{37}
}
func (x *UpdateNicknameRequest) GetNickname() string {
@@ -2148,7 +2392,7 @@ type UpdateNicknameResponse struct {
func (x *UpdateNicknameResponse) Reset() {
*x = UpdateNicknameResponse{}
- mi := &file_user_proto_msgTypes[34]
+ mi := &file_user_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2160,7 +2404,7 @@ func (x *UpdateNicknameResponse) String() string {
func (*UpdateNicknameResponse) ProtoMessage() {}
func (x *UpdateNicknameResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[34]
+ mi := &file_user_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2173,7 +2417,7 @@ func (x *UpdateNicknameResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateNicknameResponse.ProtoReflect.Descriptor instead.
func (*UpdateNicknameResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{34}
+ return file_user_proto_rawDescGZIP(), []int{38}
}
func (x *UpdateNicknameResponse) GetBase() *common.BaseResponse {
@@ -2201,7 +2445,7 @@ type UpdatePasswordRequest struct {
func (x *UpdatePasswordRequest) Reset() {
*x = UpdatePasswordRequest{}
- mi := &file_user_proto_msgTypes[35]
+ mi := &file_user_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2213,7 +2457,7 @@ func (x *UpdatePasswordRequest) String() string {
func (*UpdatePasswordRequest) ProtoMessage() {}
func (x *UpdatePasswordRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[35]
+ mi := &file_user_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2226,7 +2470,7 @@ func (x *UpdatePasswordRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdatePasswordRequest.ProtoReflect.Descriptor instead.
func (*UpdatePasswordRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{35}
+ return file_user_proto_rawDescGZIP(), []int{39}
}
func (x *UpdatePasswordRequest) GetOldPassword() string {
@@ -2253,7 +2497,7 @@ type UpdatePasswordResponse struct {
func (x *UpdatePasswordResponse) Reset() {
*x = UpdatePasswordResponse{}
- mi := &file_user_proto_msgTypes[36]
+ mi := &file_user_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2265,7 +2509,7 @@ func (x *UpdatePasswordResponse) String() string {
func (*UpdatePasswordResponse) ProtoMessage() {}
func (x *UpdatePasswordResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[36]
+ mi := &file_user_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2278,7 +2522,7 @@ func (x *UpdatePasswordResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdatePasswordResponse.ProtoReflect.Descriptor instead.
func (*UpdatePasswordResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{36}
+ return file_user_proto_rawDescGZIP(), []int{40}
}
func (x *UpdatePasswordResponse) GetBase() *common.BaseResponse {
@@ -2298,7 +2542,7 @@ type UpdateAvatarRequest struct {
func (x *UpdateAvatarRequest) Reset() {
*x = UpdateAvatarRequest{}
- mi := &file_user_proto_msgTypes[37]
+ mi := &file_user_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2310,7 +2554,7 @@ func (x *UpdateAvatarRequest) String() string {
func (*UpdateAvatarRequest) ProtoMessage() {}
func (x *UpdateAvatarRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[37]
+ mi := &file_user_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2323,7 +2567,7 @@ func (x *UpdateAvatarRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAvatarRequest.ProtoReflect.Descriptor instead.
func (*UpdateAvatarRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{37}
+ return file_user_proto_rawDescGZIP(), []int{41}
}
func (x *UpdateAvatarRequest) GetAvatarUrl() string {
@@ -2344,7 +2588,7 @@ type UpdateAvatarResponse struct {
func (x *UpdateAvatarResponse) Reset() {
*x = UpdateAvatarResponse{}
- mi := &file_user_proto_msgTypes[38]
+ mi := &file_user_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2356,7 +2600,7 @@ func (x *UpdateAvatarResponse) String() string {
func (*UpdateAvatarResponse) ProtoMessage() {}
func (x *UpdateAvatarResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[38]
+ mi := &file_user_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2369,7 +2613,7 @@ func (x *UpdateAvatarResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAvatarResponse.ProtoReflect.Descriptor instead.
func (*UpdateAvatarResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{38}
+ return file_user_proto_rawDescGZIP(), []int{42}
}
func (x *UpdateAvatarResponse) GetBase() *common.BaseResponse {
@@ -2396,7 +2640,7 @@ type GetFanIdentitiesRequest struct {
func (x *GetFanIdentitiesRequest) Reset() {
*x = GetFanIdentitiesRequest{}
- mi := &file_user_proto_msgTypes[39]
+ mi := &file_user_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2408,7 +2652,7 @@ func (x *GetFanIdentitiesRequest) String() string {
func (*GetFanIdentitiesRequest) ProtoMessage() {}
func (x *GetFanIdentitiesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[39]
+ mi := &file_user_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2421,7 +2665,7 @@ func (x *GetFanIdentitiesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFanIdentitiesRequest.ProtoReflect.Descriptor instead.
func (*GetFanIdentitiesRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{39}
+ return file_user_proto_rawDescGZIP(), []int{43}
}
func (x *GetFanIdentitiesRequest) GetKeyword() string {
@@ -2442,7 +2686,7 @@ type GetFanIdentitiesResponse struct {
func (x *GetFanIdentitiesResponse) Reset() {
*x = GetFanIdentitiesResponse{}
- mi := &file_user_proto_msgTypes[40]
+ mi := &file_user_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2454,7 +2698,7 @@ func (x *GetFanIdentitiesResponse) String() string {
func (*GetFanIdentitiesResponse) ProtoMessage() {}
func (x *GetFanIdentitiesResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[40]
+ mi := &file_user_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2467,7 +2711,7 @@ func (x *GetFanIdentitiesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFanIdentitiesResponse.ProtoReflect.Descriptor instead.
func (*GetFanIdentitiesResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{40}
+ return file_user_proto_rawDescGZIP(), []int{44}
}
func (x *GetFanIdentitiesResponse) GetBase() *common.BaseResponse {
@@ -2493,7 +2737,7 @@ type GetMyFanIdentitiesRequest struct {
func (x *GetMyFanIdentitiesRequest) Reset() {
*x = GetMyFanIdentitiesRequest{}
- mi := &file_user_proto_msgTypes[41]
+ mi := &file_user_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2505,7 +2749,7 @@ func (x *GetMyFanIdentitiesRequest) String() string {
func (*GetMyFanIdentitiesRequest) ProtoMessage() {}
func (x *GetMyFanIdentitiesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[41]
+ mi := &file_user_proto_msgTypes[45]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2518,7 +2762,7 @@ func (x *GetMyFanIdentitiesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyFanIdentitiesRequest.ProtoReflect.Descriptor instead.
func (*GetMyFanIdentitiesRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{41}
+ return file_user_proto_rawDescGZIP(), []int{45}
}
// 我的粉丝身份项(包含粉丝档案和明星信息)
@@ -2532,7 +2776,7 @@ type MyFanIdentityItem struct {
func (x *MyFanIdentityItem) Reset() {
*x = MyFanIdentityItem{}
- mi := &file_user_proto_msgTypes[42]
+ mi := &file_user_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2544,7 +2788,7 @@ func (x *MyFanIdentityItem) String() string {
func (*MyFanIdentityItem) ProtoMessage() {}
func (x *MyFanIdentityItem) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[42]
+ mi := &file_user_proto_msgTypes[46]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2557,7 +2801,7 @@ func (x *MyFanIdentityItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use MyFanIdentityItem.ProtoReflect.Descriptor instead.
func (*MyFanIdentityItem) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{42}
+ return file_user_proto_rawDescGZIP(), []int{46}
}
func (x *MyFanIdentityItem) GetFanProfile() *FanProfile {
@@ -2586,7 +2830,7 @@ type GetMyFanIdentitiesResponse struct {
func (x *GetMyFanIdentitiesResponse) Reset() {
*x = GetMyFanIdentitiesResponse{}
- mi := &file_user_proto_msgTypes[43]
+ mi := &file_user_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2598,7 +2842,7 @@ func (x *GetMyFanIdentitiesResponse) String() string {
func (*GetMyFanIdentitiesResponse) ProtoMessage() {}
func (x *GetMyFanIdentitiesResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[43]
+ mi := &file_user_proto_msgTypes[47]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2611,7 +2855,7 @@ func (x *GetMyFanIdentitiesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyFanIdentitiesResponse.ProtoReflect.Descriptor instead.
func (*GetMyFanIdentitiesResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{43}
+ return file_user_proto_rawDescGZIP(), []int{47}
}
func (x *GetMyFanIdentitiesResponse) GetBase() *common.BaseResponse {
@@ -2646,7 +2890,7 @@ type AddIdentityRequest struct {
func (x *AddIdentityRequest) Reset() {
*x = AddIdentityRequest{}
- mi := &file_user_proto_msgTypes[44]
+ mi := &file_user_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2658,7 +2902,7 @@ func (x *AddIdentityRequest) String() string {
func (*AddIdentityRequest) ProtoMessage() {}
func (x *AddIdentityRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[44]
+ mi := &file_user_proto_msgTypes[48]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2671,7 +2915,7 @@ func (x *AddIdentityRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddIdentityRequest.ProtoReflect.Descriptor instead.
func (*AddIdentityRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{44}
+ return file_user_proto_rawDescGZIP(), []int{48}
}
func (x *AddIdentityRequest) GetStarId() int64 {
@@ -2699,7 +2943,7 @@ type AddIdentityResponse struct {
func (x *AddIdentityResponse) Reset() {
*x = AddIdentityResponse{}
- mi := &file_user_proto_msgTypes[45]
+ mi := &file_user_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2711,7 +2955,7 @@ func (x *AddIdentityResponse) String() string {
func (*AddIdentityResponse) ProtoMessage() {}
func (x *AddIdentityResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[45]
+ mi := &file_user_proto_msgTypes[49]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2724,7 +2968,7 @@ func (x *AddIdentityResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddIdentityResponse.ProtoReflect.Descriptor instead.
func (*AddIdentityResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{45}
+ return file_user_proto_rawDescGZIP(), []int{49}
}
func (x *AddIdentityResponse) GetBase() *common.BaseResponse {
@@ -2751,7 +2995,7 @@ type SwitchIdentityRequest struct {
func (x *SwitchIdentityRequest) Reset() {
*x = SwitchIdentityRequest{}
- mi := &file_user_proto_msgTypes[46]
+ mi := &file_user_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2763,7 +3007,7 @@ func (x *SwitchIdentityRequest) String() string {
func (*SwitchIdentityRequest) ProtoMessage() {}
func (x *SwitchIdentityRequest) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[46]
+ mi := &file_user_proto_msgTypes[50]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2776,7 +3020,7 @@ func (x *SwitchIdentityRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SwitchIdentityRequest.ProtoReflect.Descriptor instead.
func (*SwitchIdentityRequest) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{46}
+ return file_user_proto_rawDescGZIP(), []int{50}
}
func (x *SwitchIdentityRequest) GetNewStarId() int64 {
@@ -2799,7 +3043,7 @@ type SwitchIdentityResponse struct {
func (x *SwitchIdentityResponse) Reset() {
*x = SwitchIdentityResponse{}
- mi := &file_user_proto_msgTypes[47]
+ mi := &file_user_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2811,7 +3055,7 @@ func (x *SwitchIdentityResponse) String() string {
func (*SwitchIdentityResponse) ProtoMessage() {}
func (x *SwitchIdentityResponse) ProtoReflect() protoreflect.Message {
- mi := &file_user_proto_msgTypes[47]
+ mi := &file_user_proto_msgTypes[51]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2824,7 +3068,7 @@ func (x *SwitchIdentityResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SwitchIdentityResponse.ProtoReflect.Descriptor instead.
func (*SwitchIdentityResponse) Descriptor() ([]byte, []int) {
- return file_user_proto_rawDescGZIP(), []int{47}
+ return file_user_proto_rawDescGZIP(), []int{51}
}
func (x *SwitchIdentityResponse) GetBase() *common.BaseResponse {
@@ -2903,12 +3147,13 @@ const file_user_proto_rawDesc = "" +
"\tis_active\x18\a \x01(\bR\bisActive\x12\x1d\n" +
"\n" +
"created_at\x18\b \x01(\x03R\tcreatedAt\x12\x10\n" +
- "\x03tag\x18\t \x01(\tR\x03tag\"z\n" +
+ "\x03tag\x18\t \x01(\tR\x03tag\"\x9d\x01\n" +
"\x0fRegisterRequest\x12\x16\n" +
"\x06mobile\x18\x01 \x01(\tR\x06mobile\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x17\n" +
"\astar_id\x18\x03 \x01(\x03R\x06starId\x12\x1a\n" +
- "\bnickname\x18\x04 \x01(\tR\bnickname\"\xe9\x01\n" +
+ "\bnickname\x18\x04 \x01(\tR\bnickname\x12!\n" +
+ "\fverify_token\x18\x05 \x01(\tR\vverifyToken\"\xe9\x01\n" +
"\x10RegisterResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12!\n" +
"\faccess_token\x18\x02 \x01(\tR\vaccessToken\x12\x1d\n" +
@@ -2957,7 +3202,24 @@ const file_user_proto_rawDesc = "" +
"\x06mobile\x18\x01 \x01(\tR\x06mobile\"_\n" +
"\x13CheckMobileResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x16\n" +
- "\x06exists\x18\x02 \x01(\bR\x06exists\")\n" +
+ "\x06exists\x18\x02 \x01(\bR\x06exists\"?\n" +
+ "\x0fSendCodeRequest\x12\x16\n" +
+ "\x06mobile\x18\x01 \x01(\tR\x06mobile\x12\x14\n" +
+ "\x05scene\x18\x02 \x01(\tR\x05scene\"c\n" +
+ "\x10SendCodeResponse\x120\n" +
+ "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1d\n" +
+ "\n" +
+ "expires_in\x18\x02 \x01(\x05R\texpiresIn\"U\n" +
+ "\x11VerifyCodeRequest\x12\x16\n" +
+ "\x06mobile\x18\x01 \x01(\tR\x06mobile\x12\x12\n" +
+ "\x04code\x18\x02 \x01(\tR\x04code\x12\x14\n" +
+ "\x05scene\x18\x03 \x01(\tR\x05scene\"\xa4\x01\n" +
+ "\x12VerifyCodeResponse\x120\n" +
+ "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1a\n" +
+ "\bverified\x18\x02 \x01(\bR\bverified\x12!\n" +
+ "\fverify_token\x18\x03 \x01(\tR\vverifyToken\x12\x1d\n" +
+ "\n" +
+ "expires_in\x18\x04 \x01(\x05R\texpiresIn\")\n" +
"\x0eGetUserRequest\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x03R\x06userId\"k\n" +
"\x0fGetUserResponse\x120\n" +
@@ -3068,7 +3330,7 @@ const file_user_proto_rawDesc = "" +
"\n" +
"expires_in\x18\x03 \x01(\x03R\texpiresIn\x129\n" +
"\vfan_profile\x18\x04 \x01(\v2\x18.topfans.user.FanProfileR\n" +
- "fanProfile2\xd9\x14\n" +
+ "fanProfile2\xbd\x16\n" +
"\x11UserSocialService\x12k\n" +
"\bRegister\x12\x1d.topfans.user.RegisterRequest\x1a\x1e.topfans.user.RegisterResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/auth/register\x12_\n" +
"\x05Login\x12\x1a.topfans.user.LoginRequest\x1a\x1b.topfans.user.LoginResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/api/v1/auth/login\x12v\n" +
@@ -3076,7 +3338,10 @@ const file_user_proto_rawDesc = "" +
"\rValidateToken\x12\".topfans.user.ValidateTokenRequest\x1a#.topfans.user.ValidateTokenResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/auth/validate\x12c\n" +
"\x06Logout\x12\x1b.topfans.user.LogoutRequest\x1a\x1c.topfans.user.LogoutResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/api/v1/auth/logout\x12\x80\x01\n" +
"\rCheckNickname\x12\".topfans.user.CheckNicknameRequest\x1a#.topfans.user.CheckNicknameResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/v1/auth/check-nickname\x12x\n" +
- "\vCheckMobile\x12 .topfans.user.CheckMobileRequest\x1a!.topfans.user.CheckMobileResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/api/v1/auth/check-mobile\x12g\n" +
+ "\vCheckMobile\x12 .topfans.user.CheckMobileRequest\x1a!.topfans.user.CheckMobileResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/api/v1/auth/check-mobile\x12l\n" +
+ "\bSendCode\x12\x1d.topfans.user.SendCodeRequest\x1a\x1e.topfans.user.SendCodeResponse\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/api/v1/auth/send-code\x12t\n" +
+ "\n" +
+ "VerifyCode\x12\x1f.topfans.user.VerifyCodeRequest\x1a .topfans.user.VerifyCodeResponse\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/api/v1/auth/verify-code\x12g\n" +
"\aGetUser\x12\x1c.topfans.user.GetUserRequest\x1a\x1d.topfans.user.GetUserResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\x12\x17/api/v1/users/{user_id}\x12\x90\x01\n" +
"\rGetFanProfile\x12\".topfans.user.GetFanProfileRequest\x1a#.topfans.user.GetFanProfileResponse\"6\x82\xd3\xe4\x93\x020\x12./api/v1/users/{user_id}/fan-profiles/{star_id}\x12s\n" +
"\x16UpdateFanProfileSocial\x12+.topfans.user.UpdateFanProfileSocialRequest\x1a,.topfans.user.UpdateFanProfileSocialResponse\x12m\n" +
@@ -3105,7 +3370,7 @@ func file_user_proto_rawDescGZIP() []byte {
return file_user_proto_rawDescData
}
-var file_user_proto_msgTypes = make([]protoimpl.MessageInfo, 48)
+var file_user_proto_msgTypes = make([]protoimpl.MessageInfo, 52)
var file_user_proto_goTypes = []any{
(*User)(nil), // 0: topfans.user.User
(*FanProfile)(nil), // 1: topfans.user.FanProfile
@@ -3124,131 +3389,141 @@ var file_user_proto_goTypes = []any{
(*CheckNicknameResponse)(nil), // 14: topfans.user.CheckNicknameResponse
(*CheckMobileRequest)(nil), // 15: topfans.user.CheckMobileRequest
(*CheckMobileResponse)(nil), // 16: topfans.user.CheckMobileResponse
- (*GetUserRequest)(nil), // 17: topfans.user.GetUserRequest
- (*GetUserResponse)(nil), // 18: topfans.user.GetUserResponse
- (*GetFanProfileRequest)(nil), // 19: topfans.user.GetFanProfileRequest
- (*GetFanProfileResponse)(nil), // 20: topfans.user.GetFanProfileResponse
- (*UpdateFanProfileSocialRequest)(nil), // 21: topfans.user.UpdateFanProfileSocialRequest
- (*UpdateFanProfileSocialResponse)(nil), // 22: topfans.user.UpdateFanProfileSocialResponse
- (*UpdateCrystalBalanceRequest)(nil), // 23: topfans.user.UpdateCrystalBalanceRequest
- (*UpdateCrystalBalanceResponse)(nil), // 24: topfans.user.UpdateCrystalBalanceResponse
- (*UpdateAssetsCountRequest)(nil), // 25: topfans.user.UpdateAssetsCountRequest
- (*UpdateAssetsCountResponse)(nil), // 26: topfans.user.UpdateAssetsCountResponse
- (*AddExhibitionHoursRequest)(nil), // 27: topfans.user.AddExhibitionHoursRequest
- (*AddExhibitionHoursResponse)(nil), // 28: topfans.user.AddExhibitionHoursResponse
- (*GetCurrentUserRequest)(nil), // 29: topfans.user.GetCurrentUserRequest
- (*GetCurrentUserResponse)(nil), // 30: topfans.user.GetCurrentUserResponse
- (*GetMyProfileRequest)(nil), // 31: topfans.user.GetMyProfileRequest
- (*GetMyProfileResponse)(nil), // 32: topfans.user.GetMyProfileResponse
- (*UpdateNicknameRequest)(nil), // 33: topfans.user.UpdateNicknameRequest
- (*UpdateNicknameResponse)(nil), // 34: topfans.user.UpdateNicknameResponse
- (*UpdatePasswordRequest)(nil), // 35: topfans.user.UpdatePasswordRequest
- (*UpdatePasswordResponse)(nil), // 36: topfans.user.UpdatePasswordResponse
- (*UpdateAvatarRequest)(nil), // 37: topfans.user.UpdateAvatarRequest
- (*UpdateAvatarResponse)(nil), // 38: topfans.user.UpdateAvatarResponse
- (*GetFanIdentitiesRequest)(nil), // 39: topfans.user.GetFanIdentitiesRequest
- (*GetFanIdentitiesResponse)(nil), // 40: topfans.user.GetFanIdentitiesResponse
- (*GetMyFanIdentitiesRequest)(nil), // 41: topfans.user.GetMyFanIdentitiesRequest
- (*MyFanIdentityItem)(nil), // 42: topfans.user.MyFanIdentityItem
- (*GetMyFanIdentitiesResponse)(nil), // 43: topfans.user.GetMyFanIdentitiesResponse
- (*AddIdentityRequest)(nil), // 44: topfans.user.AddIdentityRequest
- (*AddIdentityResponse)(nil), // 45: topfans.user.AddIdentityResponse
- (*SwitchIdentityRequest)(nil), // 46: topfans.user.SwitchIdentityRequest
- (*SwitchIdentityResponse)(nil), // 47: topfans.user.SwitchIdentityResponse
- (*common.BaseResponse)(nil), // 48: topfans.common.BaseResponse
+ (*SendCodeRequest)(nil), // 17: topfans.user.SendCodeRequest
+ (*SendCodeResponse)(nil), // 18: topfans.user.SendCodeResponse
+ (*VerifyCodeRequest)(nil), // 19: topfans.user.VerifyCodeRequest
+ (*VerifyCodeResponse)(nil), // 20: topfans.user.VerifyCodeResponse
+ (*GetUserRequest)(nil), // 21: topfans.user.GetUserRequest
+ (*GetUserResponse)(nil), // 22: topfans.user.GetUserResponse
+ (*GetFanProfileRequest)(nil), // 23: topfans.user.GetFanProfileRequest
+ (*GetFanProfileResponse)(nil), // 24: topfans.user.GetFanProfileResponse
+ (*UpdateFanProfileSocialRequest)(nil), // 25: topfans.user.UpdateFanProfileSocialRequest
+ (*UpdateFanProfileSocialResponse)(nil), // 26: topfans.user.UpdateFanProfileSocialResponse
+ (*UpdateCrystalBalanceRequest)(nil), // 27: topfans.user.UpdateCrystalBalanceRequest
+ (*UpdateCrystalBalanceResponse)(nil), // 28: topfans.user.UpdateCrystalBalanceResponse
+ (*UpdateAssetsCountRequest)(nil), // 29: topfans.user.UpdateAssetsCountRequest
+ (*UpdateAssetsCountResponse)(nil), // 30: topfans.user.UpdateAssetsCountResponse
+ (*AddExhibitionHoursRequest)(nil), // 31: topfans.user.AddExhibitionHoursRequest
+ (*AddExhibitionHoursResponse)(nil), // 32: topfans.user.AddExhibitionHoursResponse
+ (*GetCurrentUserRequest)(nil), // 33: topfans.user.GetCurrentUserRequest
+ (*GetCurrentUserResponse)(nil), // 34: topfans.user.GetCurrentUserResponse
+ (*GetMyProfileRequest)(nil), // 35: topfans.user.GetMyProfileRequest
+ (*GetMyProfileResponse)(nil), // 36: topfans.user.GetMyProfileResponse
+ (*UpdateNicknameRequest)(nil), // 37: topfans.user.UpdateNicknameRequest
+ (*UpdateNicknameResponse)(nil), // 38: topfans.user.UpdateNicknameResponse
+ (*UpdatePasswordRequest)(nil), // 39: topfans.user.UpdatePasswordRequest
+ (*UpdatePasswordResponse)(nil), // 40: topfans.user.UpdatePasswordResponse
+ (*UpdateAvatarRequest)(nil), // 41: topfans.user.UpdateAvatarRequest
+ (*UpdateAvatarResponse)(nil), // 42: topfans.user.UpdateAvatarResponse
+ (*GetFanIdentitiesRequest)(nil), // 43: topfans.user.GetFanIdentitiesRequest
+ (*GetFanIdentitiesResponse)(nil), // 44: topfans.user.GetFanIdentitiesResponse
+ (*GetMyFanIdentitiesRequest)(nil), // 45: topfans.user.GetMyFanIdentitiesRequest
+ (*MyFanIdentityItem)(nil), // 46: topfans.user.MyFanIdentityItem
+ (*GetMyFanIdentitiesResponse)(nil), // 47: topfans.user.GetMyFanIdentitiesResponse
+ (*AddIdentityRequest)(nil), // 48: topfans.user.AddIdentityRequest
+ (*AddIdentityResponse)(nil), // 49: topfans.user.AddIdentityResponse
+ (*SwitchIdentityRequest)(nil), // 50: topfans.user.SwitchIdentityRequest
+ (*SwitchIdentityResponse)(nil), // 51: topfans.user.SwitchIdentityResponse
+ (*common.BaseResponse)(nil), // 52: topfans.common.BaseResponse
}
var file_user_proto_depIdxs = []int32{
- 48, // 0: topfans.user.RegisterResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 0: topfans.user.RegisterResponse.base:type_name -> topfans.common.BaseResponse
0, // 1: topfans.user.RegisterResponse.user:type_name -> topfans.user.User
1, // 2: topfans.user.RegisterResponse.fan_profile:type_name -> topfans.user.FanProfile
- 48, // 3: topfans.user.LoginResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 3: topfans.user.LoginResponse.base:type_name -> topfans.common.BaseResponse
0, // 4: topfans.user.LoginResponse.user:type_name -> topfans.user.User
1, // 5: topfans.user.LoginResponse.fan_profile:type_name -> topfans.user.FanProfile
1, // 6: topfans.user.LoginResponse.fan_profiles:type_name -> topfans.user.FanProfile
- 48, // 7: topfans.user.RefreshTokenResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 8: topfans.user.ValidateTokenResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 9: topfans.user.LogoutResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 10: topfans.user.CheckNicknameResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 11: topfans.user.CheckMobileResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 12: topfans.user.GetUserResponse.base:type_name -> topfans.common.BaseResponse
- 0, // 13: topfans.user.GetUserResponse.user:type_name -> topfans.user.User
- 48, // 14: topfans.user.GetFanProfileResponse.base:type_name -> topfans.common.BaseResponse
- 1, // 15: topfans.user.GetFanProfileResponse.profile:type_name -> topfans.user.FanProfile
- 48, // 16: topfans.user.UpdateFanProfileSocialResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 17: topfans.user.UpdateCrystalBalanceResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 18: topfans.user.UpdateAssetsCountResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 19: topfans.user.AddExhibitionHoursResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 20: topfans.user.GetCurrentUserResponse.base:type_name -> topfans.common.BaseResponse
- 0, // 21: topfans.user.GetCurrentUserResponse.user:type_name -> topfans.user.User
- 1, // 22: topfans.user.GetCurrentUserResponse.fan_profile:type_name -> topfans.user.FanProfile
- 1, // 23: topfans.user.GetCurrentUserResponse.fan_profiles:type_name -> topfans.user.FanProfile
- 48, // 24: topfans.user.GetMyProfileResponse.base:type_name -> topfans.common.BaseResponse
- 0, // 25: topfans.user.GetMyProfileResponse.user:type_name -> topfans.user.User
- 1, // 26: topfans.user.GetMyProfileResponse.fan_profile:type_name -> topfans.user.FanProfile
- 1, // 27: topfans.user.GetMyProfileResponse.fan_profiles:type_name -> topfans.user.FanProfile
- 48, // 28: topfans.user.UpdateNicknameResponse.base:type_name -> topfans.common.BaseResponse
- 1, // 29: topfans.user.UpdateNicknameResponse.fan_profile:type_name -> topfans.user.FanProfile
- 48, // 30: topfans.user.UpdatePasswordResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 31: topfans.user.UpdateAvatarResponse.base:type_name -> topfans.common.BaseResponse
- 48, // 32: topfans.user.GetFanIdentitiesResponse.base:type_name -> topfans.common.BaseResponse
- 2, // 33: topfans.user.GetFanIdentitiesResponse.stars:type_name -> topfans.user.Star
- 1, // 34: topfans.user.MyFanIdentityItem.fan_profile:type_name -> topfans.user.FanProfile
- 2, // 35: topfans.user.MyFanIdentityItem.star:type_name -> topfans.user.Star
- 48, // 36: topfans.user.GetMyFanIdentitiesResponse.base:type_name -> topfans.common.BaseResponse
- 42, // 37: topfans.user.GetMyFanIdentitiesResponse.items:type_name -> topfans.user.MyFanIdentityItem
- 48, // 38: topfans.user.AddIdentityResponse.base:type_name -> topfans.common.BaseResponse
- 1, // 39: topfans.user.AddIdentityResponse.fan_profile:type_name -> topfans.user.FanProfile
- 48, // 40: topfans.user.SwitchIdentityResponse.base:type_name -> topfans.common.BaseResponse
- 1, // 41: topfans.user.SwitchIdentityResponse.fan_profile:type_name -> topfans.user.FanProfile
- 3, // 42: topfans.user.UserSocialService.Register:input_type -> topfans.user.RegisterRequest
- 5, // 43: topfans.user.UserSocialService.Login:input_type -> topfans.user.LoginRequest
- 7, // 44: topfans.user.UserSocialService.RefreshToken:input_type -> topfans.user.RefreshTokenRequest
- 9, // 45: topfans.user.UserSocialService.ValidateToken:input_type -> topfans.user.ValidateTokenRequest
- 11, // 46: topfans.user.UserSocialService.Logout:input_type -> topfans.user.LogoutRequest
- 13, // 47: topfans.user.UserSocialService.CheckNickname:input_type -> topfans.user.CheckNicknameRequest
- 15, // 48: topfans.user.UserSocialService.CheckMobile:input_type -> topfans.user.CheckMobileRequest
- 17, // 49: topfans.user.UserSocialService.GetUser:input_type -> topfans.user.GetUserRequest
- 19, // 50: topfans.user.UserSocialService.GetFanProfile:input_type -> topfans.user.GetFanProfileRequest
- 21, // 51: topfans.user.UserSocialService.UpdateFanProfileSocial:input_type -> topfans.user.UpdateFanProfileSocialRequest
- 23, // 52: topfans.user.UserSocialService.UpdateCrystalBalance:input_type -> topfans.user.UpdateCrystalBalanceRequest
- 25, // 53: topfans.user.UserSocialService.UpdateAssetsCount:input_type -> topfans.user.UpdateAssetsCountRequest
- 27, // 54: topfans.user.UserSocialService.AddExhibitionHours:input_type -> topfans.user.AddExhibitionHoursRequest
- 29, // 55: topfans.user.UserSocialService.GetCurrentUser:input_type -> topfans.user.GetCurrentUserRequest
- 31, // 56: topfans.user.UserSocialService.GetMyProfile:input_type -> topfans.user.GetMyProfileRequest
- 33, // 57: topfans.user.UserSocialService.UpdateNickname:input_type -> topfans.user.UpdateNicknameRequest
- 35, // 58: topfans.user.UserSocialService.UpdatePassword:input_type -> topfans.user.UpdatePasswordRequest
- 37, // 59: topfans.user.UserSocialService.UpdateAvatar:input_type -> topfans.user.UpdateAvatarRequest
- 39, // 60: topfans.user.UserSocialService.GetFanIdentities:input_type -> topfans.user.GetFanIdentitiesRequest
- 41, // 61: topfans.user.UserSocialService.GetMyFanIdentities:input_type -> topfans.user.GetMyFanIdentitiesRequest
- 44, // 62: topfans.user.UserSocialService.AddIdentity:input_type -> topfans.user.AddIdentityRequest
- 46, // 63: topfans.user.UserSocialService.SwitchIdentity:input_type -> topfans.user.SwitchIdentityRequest
- 4, // 64: topfans.user.UserSocialService.Register:output_type -> topfans.user.RegisterResponse
- 6, // 65: topfans.user.UserSocialService.Login:output_type -> topfans.user.LoginResponse
- 8, // 66: topfans.user.UserSocialService.RefreshToken:output_type -> topfans.user.RefreshTokenResponse
- 10, // 67: topfans.user.UserSocialService.ValidateToken:output_type -> topfans.user.ValidateTokenResponse
- 12, // 68: topfans.user.UserSocialService.Logout:output_type -> topfans.user.LogoutResponse
- 14, // 69: topfans.user.UserSocialService.CheckNickname:output_type -> topfans.user.CheckNicknameResponse
- 16, // 70: topfans.user.UserSocialService.CheckMobile:output_type -> topfans.user.CheckMobileResponse
- 18, // 71: topfans.user.UserSocialService.GetUser:output_type -> topfans.user.GetUserResponse
- 20, // 72: topfans.user.UserSocialService.GetFanProfile:output_type -> topfans.user.GetFanProfileResponse
- 22, // 73: topfans.user.UserSocialService.UpdateFanProfileSocial:output_type -> topfans.user.UpdateFanProfileSocialResponse
- 24, // 74: topfans.user.UserSocialService.UpdateCrystalBalance:output_type -> topfans.user.UpdateCrystalBalanceResponse
- 26, // 75: topfans.user.UserSocialService.UpdateAssetsCount:output_type -> topfans.user.UpdateAssetsCountResponse
- 28, // 76: topfans.user.UserSocialService.AddExhibitionHours:output_type -> topfans.user.AddExhibitionHoursResponse
- 30, // 77: topfans.user.UserSocialService.GetCurrentUser:output_type -> topfans.user.GetCurrentUserResponse
- 32, // 78: topfans.user.UserSocialService.GetMyProfile:output_type -> topfans.user.GetMyProfileResponse
- 34, // 79: topfans.user.UserSocialService.UpdateNickname:output_type -> topfans.user.UpdateNicknameResponse
- 36, // 80: topfans.user.UserSocialService.UpdatePassword:output_type -> topfans.user.UpdatePasswordResponse
- 38, // 81: topfans.user.UserSocialService.UpdateAvatar:output_type -> topfans.user.UpdateAvatarResponse
- 40, // 82: topfans.user.UserSocialService.GetFanIdentities:output_type -> topfans.user.GetFanIdentitiesResponse
- 43, // 83: topfans.user.UserSocialService.GetMyFanIdentities:output_type -> topfans.user.GetMyFanIdentitiesResponse
- 45, // 84: topfans.user.UserSocialService.AddIdentity:output_type -> topfans.user.AddIdentityResponse
- 47, // 85: topfans.user.UserSocialService.SwitchIdentity:output_type -> topfans.user.SwitchIdentityResponse
- 64, // [64:86] is the sub-list for method output_type
- 42, // [42:64] is the sub-list for method input_type
- 42, // [42:42] is the sub-list for extension type_name
- 42, // [42:42] is the sub-list for extension extendee
- 0, // [0:42] is the sub-list for field type_name
+ 52, // 7: topfans.user.RefreshTokenResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 8: topfans.user.ValidateTokenResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 9: topfans.user.LogoutResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 10: topfans.user.CheckNicknameResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 11: topfans.user.CheckMobileResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 12: topfans.user.SendCodeResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 13: topfans.user.VerifyCodeResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 14: topfans.user.GetUserResponse.base:type_name -> topfans.common.BaseResponse
+ 0, // 15: topfans.user.GetUserResponse.user:type_name -> topfans.user.User
+ 52, // 16: topfans.user.GetFanProfileResponse.base:type_name -> topfans.common.BaseResponse
+ 1, // 17: topfans.user.GetFanProfileResponse.profile:type_name -> topfans.user.FanProfile
+ 52, // 18: topfans.user.UpdateFanProfileSocialResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 19: topfans.user.UpdateCrystalBalanceResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 20: topfans.user.UpdateAssetsCountResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 21: topfans.user.AddExhibitionHoursResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 22: topfans.user.GetCurrentUserResponse.base:type_name -> topfans.common.BaseResponse
+ 0, // 23: topfans.user.GetCurrentUserResponse.user:type_name -> topfans.user.User
+ 1, // 24: topfans.user.GetCurrentUserResponse.fan_profile:type_name -> topfans.user.FanProfile
+ 1, // 25: topfans.user.GetCurrentUserResponse.fan_profiles:type_name -> topfans.user.FanProfile
+ 52, // 26: topfans.user.GetMyProfileResponse.base:type_name -> topfans.common.BaseResponse
+ 0, // 27: topfans.user.GetMyProfileResponse.user:type_name -> topfans.user.User
+ 1, // 28: topfans.user.GetMyProfileResponse.fan_profile:type_name -> topfans.user.FanProfile
+ 1, // 29: topfans.user.GetMyProfileResponse.fan_profiles:type_name -> topfans.user.FanProfile
+ 52, // 30: topfans.user.UpdateNicknameResponse.base:type_name -> topfans.common.BaseResponse
+ 1, // 31: topfans.user.UpdateNicknameResponse.fan_profile:type_name -> topfans.user.FanProfile
+ 52, // 32: topfans.user.UpdatePasswordResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 33: topfans.user.UpdateAvatarResponse.base:type_name -> topfans.common.BaseResponse
+ 52, // 34: topfans.user.GetFanIdentitiesResponse.base:type_name -> topfans.common.BaseResponse
+ 2, // 35: topfans.user.GetFanIdentitiesResponse.stars:type_name -> topfans.user.Star
+ 1, // 36: topfans.user.MyFanIdentityItem.fan_profile:type_name -> topfans.user.FanProfile
+ 2, // 37: topfans.user.MyFanIdentityItem.star:type_name -> topfans.user.Star
+ 52, // 38: topfans.user.GetMyFanIdentitiesResponse.base:type_name -> topfans.common.BaseResponse
+ 46, // 39: topfans.user.GetMyFanIdentitiesResponse.items:type_name -> topfans.user.MyFanIdentityItem
+ 52, // 40: topfans.user.AddIdentityResponse.base:type_name -> topfans.common.BaseResponse
+ 1, // 41: topfans.user.AddIdentityResponse.fan_profile:type_name -> topfans.user.FanProfile
+ 52, // 42: topfans.user.SwitchIdentityResponse.base:type_name -> topfans.common.BaseResponse
+ 1, // 43: topfans.user.SwitchIdentityResponse.fan_profile:type_name -> topfans.user.FanProfile
+ 3, // 44: topfans.user.UserSocialService.Register:input_type -> topfans.user.RegisterRequest
+ 5, // 45: topfans.user.UserSocialService.Login:input_type -> topfans.user.LoginRequest
+ 7, // 46: topfans.user.UserSocialService.RefreshToken:input_type -> topfans.user.RefreshTokenRequest
+ 9, // 47: topfans.user.UserSocialService.ValidateToken:input_type -> topfans.user.ValidateTokenRequest
+ 11, // 48: topfans.user.UserSocialService.Logout:input_type -> topfans.user.LogoutRequest
+ 13, // 49: topfans.user.UserSocialService.CheckNickname:input_type -> topfans.user.CheckNicknameRequest
+ 15, // 50: topfans.user.UserSocialService.CheckMobile:input_type -> topfans.user.CheckMobileRequest
+ 17, // 51: topfans.user.UserSocialService.SendCode:input_type -> topfans.user.SendCodeRequest
+ 19, // 52: topfans.user.UserSocialService.VerifyCode:input_type -> topfans.user.VerifyCodeRequest
+ 21, // 53: topfans.user.UserSocialService.GetUser:input_type -> topfans.user.GetUserRequest
+ 23, // 54: topfans.user.UserSocialService.GetFanProfile:input_type -> topfans.user.GetFanProfileRequest
+ 25, // 55: topfans.user.UserSocialService.UpdateFanProfileSocial:input_type -> topfans.user.UpdateFanProfileSocialRequest
+ 27, // 56: topfans.user.UserSocialService.UpdateCrystalBalance:input_type -> topfans.user.UpdateCrystalBalanceRequest
+ 29, // 57: topfans.user.UserSocialService.UpdateAssetsCount:input_type -> topfans.user.UpdateAssetsCountRequest
+ 31, // 58: topfans.user.UserSocialService.AddExhibitionHours:input_type -> topfans.user.AddExhibitionHoursRequest
+ 33, // 59: topfans.user.UserSocialService.GetCurrentUser:input_type -> topfans.user.GetCurrentUserRequest
+ 35, // 60: topfans.user.UserSocialService.GetMyProfile:input_type -> topfans.user.GetMyProfileRequest
+ 37, // 61: topfans.user.UserSocialService.UpdateNickname:input_type -> topfans.user.UpdateNicknameRequest
+ 39, // 62: topfans.user.UserSocialService.UpdatePassword:input_type -> topfans.user.UpdatePasswordRequest
+ 41, // 63: topfans.user.UserSocialService.UpdateAvatar:input_type -> topfans.user.UpdateAvatarRequest
+ 43, // 64: topfans.user.UserSocialService.GetFanIdentities:input_type -> topfans.user.GetFanIdentitiesRequest
+ 45, // 65: topfans.user.UserSocialService.GetMyFanIdentities:input_type -> topfans.user.GetMyFanIdentitiesRequest
+ 48, // 66: topfans.user.UserSocialService.AddIdentity:input_type -> topfans.user.AddIdentityRequest
+ 50, // 67: topfans.user.UserSocialService.SwitchIdentity:input_type -> topfans.user.SwitchIdentityRequest
+ 4, // 68: topfans.user.UserSocialService.Register:output_type -> topfans.user.RegisterResponse
+ 6, // 69: topfans.user.UserSocialService.Login:output_type -> topfans.user.LoginResponse
+ 8, // 70: topfans.user.UserSocialService.RefreshToken:output_type -> topfans.user.RefreshTokenResponse
+ 10, // 71: topfans.user.UserSocialService.ValidateToken:output_type -> topfans.user.ValidateTokenResponse
+ 12, // 72: topfans.user.UserSocialService.Logout:output_type -> topfans.user.LogoutResponse
+ 14, // 73: topfans.user.UserSocialService.CheckNickname:output_type -> topfans.user.CheckNicknameResponse
+ 16, // 74: topfans.user.UserSocialService.CheckMobile:output_type -> topfans.user.CheckMobileResponse
+ 18, // 75: topfans.user.UserSocialService.SendCode:output_type -> topfans.user.SendCodeResponse
+ 20, // 76: topfans.user.UserSocialService.VerifyCode:output_type -> topfans.user.VerifyCodeResponse
+ 22, // 77: topfans.user.UserSocialService.GetUser:output_type -> topfans.user.GetUserResponse
+ 24, // 78: topfans.user.UserSocialService.GetFanProfile:output_type -> topfans.user.GetFanProfileResponse
+ 26, // 79: topfans.user.UserSocialService.UpdateFanProfileSocial:output_type -> topfans.user.UpdateFanProfileSocialResponse
+ 28, // 80: topfans.user.UserSocialService.UpdateCrystalBalance:output_type -> topfans.user.UpdateCrystalBalanceResponse
+ 30, // 81: topfans.user.UserSocialService.UpdateAssetsCount:output_type -> topfans.user.UpdateAssetsCountResponse
+ 32, // 82: topfans.user.UserSocialService.AddExhibitionHours:output_type -> topfans.user.AddExhibitionHoursResponse
+ 34, // 83: topfans.user.UserSocialService.GetCurrentUser:output_type -> topfans.user.GetCurrentUserResponse
+ 36, // 84: topfans.user.UserSocialService.GetMyProfile:output_type -> topfans.user.GetMyProfileResponse
+ 38, // 85: topfans.user.UserSocialService.UpdateNickname:output_type -> topfans.user.UpdateNicknameResponse
+ 40, // 86: topfans.user.UserSocialService.UpdatePassword:output_type -> topfans.user.UpdatePasswordResponse
+ 42, // 87: topfans.user.UserSocialService.UpdateAvatar:output_type -> topfans.user.UpdateAvatarResponse
+ 44, // 88: topfans.user.UserSocialService.GetFanIdentities:output_type -> topfans.user.GetFanIdentitiesResponse
+ 47, // 89: topfans.user.UserSocialService.GetMyFanIdentities:output_type -> topfans.user.GetMyFanIdentitiesResponse
+ 49, // 90: topfans.user.UserSocialService.AddIdentity:output_type -> topfans.user.AddIdentityResponse
+ 51, // 91: topfans.user.UserSocialService.SwitchIdentity:output_type -> topfans.user.SwitchIdentityResponse
+ 68, // [68:92] is the sub-list for method output_type
+ 44, // [44:68] is the sub-list for method input_type
+ 44, // [44:44] is the sub-list for extension type_name
+ 44, // [44:44] is the sub-list for extension extendee
+ 0, // [0:44] is the sub-list for field type_name
}
func init() { file_user_proto_init() }
@@ -3262,7 +3537,7 @@ func file_user_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_proto_rawDesc), len(file_user_proto_rawDesc)),
NumEnums: 0,
- NumMessages: 48,
+ NumMessages: 52,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/backend/pkg/proto/user/user.triple.go b/backend/pkg/proto/user/user.triple.go
index d6b78a8..1333bd4 100644
--- a/backend/pkg/proto/user/user.triple.go
+++ b/backend/pkg/proto/user/user.triple.go
@@ -50,6 +50,10 @@ const (
UserSocialServiceCheckNicknameProcedure = "/topfans.user.UserSocialService/CheckNickname"
// UserSocialServiceCheckMobileProcedure is the fully-qualified name of the UserSocialService's CheckMobile RPC.
UserSocialServiceCheckMobileProcedure = "/topfans.user.UserSocialService/CheckMobile"
+ // UserSocialServiceSendCodeProcedure is the fully-qualified name of the UserSocialService's SendCode RPC.
+ UserSocialServiceSendCodeProcedure = "/topfans.user.UserSocialService/SendCode"
+ // UserSocialServiceVerifyCodeProcedure is the fully-qualified name of the UserSocialService's VerifyCode RPC.
+ UserSocialServiceVerifyCodeProcedure = "/topfans.user.UserSocialService/VerifyCode"
// UserSocialServiceGetUserProcedure is the fully-qualified name of the UserSocialService's GetUser RPC.
UserSocialServiceGetUserProcedure = "/topfans.user.UserSocialService/GetUser"
// UserSocialServiceGetFanProfileProcedure is the fully-qualified name of the UserSocialService's GetFanProfile RPC.
@@ -95,6 +99,8 @@ type UserSocialService interface {
Logout(ctx context.Context, req *LogoutRequest, opts ...client.CallOption) (*LogoutResponse, error)
CheckNickname(ctx context.Context, req *CheckNicknameRequest, opts ...client.CallOption) (*CheckNicknameResponse, error)
CheckMobile(ctx context.Context, req *CheckMobileRequest, opts ...client.CallOption) (*CheckMobileResponse, error)
+ SendCode(ctx context.Context, req *SendCodeRequest, opts ...client.CallOption) (*SendCodeResponse, error)
+ VerifyCode(ctx context.Context, req *VerifyCodeRequest, opts ...client.CallOption) (*VerifyCodeResponse, error)
GetUser(ctx context.Context, req *GetUserRequest, opts ...client.CallOption) (*GetUserResponse, error)
GetFanProfile(ctx context.Context, req *GetFanProfileRequest, opts ...client.CallOption) (*GetFanProfileResponse, error)
UpdateFanProfileSocial(ctx context.Context, req *UpdateFanProfileSocialRequest, opts ...client.CallOption) (*UpdateFanProfileSocialResponse, error)
@@ -188,6 +194,22 @@ func (c *UserSocialServiceImpl) CheckMobile(ctx context.Context, req *CheckMobil
return resp, nil
}
+func (c *UserSocialServiceImpl) SendCode(ctx context.Context, req *SendCodeRequest, opts ...client.CallOption) (*SendCodeResponse, error) {
+ resp := new(SendCodeResponse)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "SendCode", opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+func (c *UserSocialServiceImpl) VerifyCode(ctx context.Context, req *VerifyCodeRequest, opts ...client.CallOption) (*VerifyCodeResponse, error) {
+ resp := new(VerifyCodeResponse)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "VerifyCode", opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
func (c *UserSocialServiceImpl) GetUser(ctx context.Context, req *GetUserRequest, opts ...client.CallOption) (*GetUserResponse, error) {
resp := new(GetUserResponse)
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetUser", opts...); err != nil {
@@ -310,7 +332,7 @@ func (c *UserSocialServiceImpl) SwitchIdentity(ctx context.Context, req *SwitchI
var UserSocialService_ClientInfo = client.ClientInfo{
InterfaceName: "topfans.user.UserSocialService",
- MethodNames: []string{"Register", "Login", "RefreshToken", "ValidateToken", "Logout", "CheckNickname", "CheckMobile", "GetUser", "GetFanProfile", "UpdateFanProfileSocial", "UpdateCrystalBalance", "UpdateAssetsCount", "AddExhibitionHours", "GetCurrentUser", "GetMyProfile", "UpdateNickname", "UpdatePassword", "UpdateAvatar", "GetFanIdentities", "GetMyFanIdentities", "AddIdentity", "SwitchIdentity"},
+ MethodNames: []string{"Register", "Login", "RefreshToken", "ValidateToken", "Logout", "CheckNickname", "CheckMobile", "SendCode", "VerifyCode", "GetUser", "GetFanProfile", "UpdateFanProfileSocial", "UpdateCrystalBalance", "UpdateAssetsCount", "AddExhibitionHours", "GetCurrentUser", "GetMyProfile", "UpdateNickname", "UpdatePassword", "UpdateAvatar", "GetFanIdentities", "GetMyFanIdentities", "AddIdentity", "SwitchIdentity"},
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
dubboCli := dubboCliRaw.(*UserSocialServiceImpl)
dubboCli.conn = conn
@@ -326,6 +348,8 @@ type UserSocialServiceHandler interface {
Logout(context.Context, *LogoutRequest) (*LogoutResponse, error)
CheckNickname(context.Context, *CheckNicknameRequest) (*CheckNicknameResponse, error)
CheckMobile(context.Context, *CheckMobileRequest) (*CheckMobileResponse, error)
+ SendCode(context.Context, *SendCodeRequest) (*SendCodeResponse, error)
+ VerifyCode(context.Context, *VerifyCodeRequest) (*VerifyCodeResponse, error)
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
GetFanProfile(context.Context, *GetFanProfileRequest) (*GetFanProfileResponse, error)
UpdateFanProfileSocial(context.Context, *UpdateFanProfileSocialRequest) (*UpdateFanProfileSocialResponse, error)
@@ -460,6 +484,36 @@ var UserSocialService_ServiceInfo = server.ServiceInfo{
return triple_protocol.NewResponse(res), nil
},
},
+ {
+ Name: "SendCode",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(SendCodeRequest)
+ },
+ MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*SendCodeRequest)
+ res, err := handler.(UserSocialServiceHandler).SendCode(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ {
+ Name: "VerifyCode",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(VerifyCodeRequest)
+ },
+ MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*VerifyCodeRequest)
+ res, err := handler.(UserSocialServiceHandler).VerifyCode(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
{
Name: "GetUser",
Type: constant.CallUnary,
diff --git a/backend/proto/user.proto b/backend/proto/user.proto
index 063288f..5dbfad3 100644
--- a/backend/proto/user.proto
+++ b/backend/proto/user.proto
@@ -60,6 +60,7 @@ message RegisterRequest {
string password = 2; // 密码
int64 star_id = 3; // 选择第一个粉丝身份的明星ID
string nickname = 4; // 第一个粉丝身份的昵称
+ string verify_token = 5; // 短信验证token(注册前必须先通过短信验证)
}
// 注册响应
@@ -146,6 +147,35 @@ message CheckMobileResponse {
bool exists = 2; // 手机号是否已存在
}
+// ==================== 短信验证码相关消息 ====================
+
+// 发送验证码请求
+message SendCodeRequest {
+ string mobile = 1; // 手机号
+ string scene = 2; // 场景:register, password
+}
+
+// 发送验证码响应
+message SendCodeResponse {
+ topfans.common.BaseResponse base = 1;
+ int32 expires_in = 2; // 多少秒后可以重发
+}
+
+// 验证验证码请求
+message VerifyCodeRequest {
+ string mobile = 1; // 手机号
+ string code = 2; // 验证码
+ string scene = 3; // 场景:register, password
+}
+
+// 验证验证码响应
+message VerifyCodeResponse {
+ topfans.common.BaseResponse base = 1;
+ bool verified = 2; // 是否验证成功
+ string verify_token = 3; // 验证成功后返回的token
+ int32 expires_in = 4; // token有效期(秒)
+}
+
// ==================== 用户信息相关消息 ====================
// 获取用户信息请求
@@ -397,6 +427,21 @@ service UserSocialService {
};
}
+ // 短信验证码相关
+ rpc SendCode(SendCodeRequest) returns (SendCodeResponse) {
+ option (google.api.http) = {
+ post: "/api/v1/auth/send-code"
+ body: "*"
+ };
+ }
+
+ rpc VerifyCode(VerifyCodeRequest) returns (VerifyCodeResponse) {
+ option (google.api.http) = {
+ post: "/api/v1/auth/verify-code"
+ body: "*"
+ };
+ }
+
// 用户信息相关
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
diff --git a/backend/services/userService/config/sms_config.go b/backend/services/userService/config/sms_config.go
new file mode 100644
index 0000000..892dadc
--- /dev/null
+++ b/backend/services/userService/config/sms_config.go
@@ -0,0 +1,28 @@
+package config
+
+import "os"
+
+type SMSConfig struct {
+ AccessKeyID string
+ AccessKeySecret string
+ SignName string
+ TemplateCode string
+ Region string
+}
+
+func GetSMSConfig() SMSConfig {
+ return SMSConfig{
+ AccessKeyID: os.Getenv("SMS_ACCESS_KEY_ID"),
+ AccessKeySecret: os.Getenv("SMS_ACCESS_KEY_SECRET"),
+ SignName: os.Getenv("SMS_SIGN_NAME"),
+ TemplateCode: os.Getenv("SMS_TEMPLATE_CODE"),
+ Region: getEnvOrDefault("SMS_REGION", "cn-hangzhou"),
+ }
+}
+
+func getEnvOrDefault(key, defaultValue string) string {
+ if value := os.Getenv(key); value != "" {
+ return value
+ }
+ return defaultValue
+}
\ No newline at end of file
diff --git a/backend/services/userService/go.mod b/backend/services/userService/go.mod
index 3ea9316..92290ab 100644
--- a/backend/services/userService/go.mod
+++ b/backend/services/userService/go.mod
@@ -4,6 +4,8 @@ go 1.25.5
require (
dubbo.apache.org/dubbo-go/v3 v3.3.1
+ github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8
+ github.com/redis/go-redis/v9 v9.19.0
github.com/topfans/backend v0.0.0
go.uber.org/zap v1.27.1
golang.org/x/crypto v0.46.0
@@ -16,12 +18,16 @@ require (
github.com/Workiva/go-datastructures v1.0.52 // indirect
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect
github.com/alibaba/sentinel-golang v1.0.4 // indirect
+ github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
+ github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.16 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
- github.com/alibabacloud-go/tea v1.2.2 // indirect
+ github.com/alibabacloud-go/tea v1.3.13 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
+ github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 // indirect
+ github.com/aliyun/credentials-go v1.4.5 // indirect
github.com/apache/dubbo-getty v1.4.10 // indirect
github.com/apache/dubbo-go-hessian2 v1.12.5 // indirect
github.com/apolloconfig/agollo/v4 v4.4.0 // indirect
@@ -30,6 +36,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/creasty/defaults v1.5.2 // indirect
@@ -117,6 +124,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.8.1 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
+ github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
@@ -139,7 +147,7 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
- go.uber.org/atomic v1.10.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/mod v0.30.0 // indirect
diff --git a/backend/services/userService/go.sum b/backend/services/userService/go.sum
index 94a99af..d3107f3 100644
--- a/backend/services/userService/go.sum
+++ b/backend/services/userService/go.sum
@@ -68,16 +68,49 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8=
github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
+github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
+github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
+github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
+github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
+github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
+github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
+github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
+github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.16 h1:LHhjxZkNWAKWepxcWyzgFgo0X6TUVhL7sC7ANc60p8A=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.16/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
+github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
+github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
+github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
+github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
+github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8 h1:aDPyz6C+nenypx24N5qEt09NjpS6mu7Cu1A+wf9UTaY=
+github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8/go.mod h1:e/vWJ5gLVnraPROSh+3oMSodf5ukaUlqNgH0IIcnz98=
+github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
+github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
+github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
+github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
-github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
+github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
+github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94=
+github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
+github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o=
github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw=
@@ -86,6 +119,11 @@ github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 h1:rWkH6D2XlXb/Y+tNAQROxBz
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2/go.mod h1:GDtq+Kw+v0fO+j5BrrWiUHbBq7L+hfpzpPfXKOZMFE0=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 h1:olLiPI2iM8Hqq6vKnSxpM3awCrm9/BeOgHpzQkOYnI4=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7/go.mod h1:oDg1j4kFxnhgftaiLJABkGeSvuEvSF5Lo6UmRAMruX4=
+github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
+github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
+github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
+github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk=
+github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/dubbo-getty v1.4.10 h1:ZmkpHJa/qgS0evX2tTNqNCz6rClI/9Wwp7ctyMml82w=
github.com/apache/dubbo-getty v1.4.10/go.mod h1:V64WqLIxksEgNu5aBJBOxNIvpOZyfUJ7J/DXBlKSUoA=
@@ -125,6 +163,10 @@ github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjL
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
@@ -145,6 +187,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
+github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -365,6 +409,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -394,8 +439,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
-github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -528,6 +573,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -729,6 +776,8 @@ github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
+github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -753,8 +802,9 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
+github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -788,6 +838,7 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -804,6 +855,9 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0=
github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k=
+github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
@@ -832,12 +886,15 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
+github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
@@ -910,8 +967,8 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
-go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -941,11 +998,19 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -991,6 +1056,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1030,6 +1098,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -1046,8 +1115,15 @@ golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1077,6 +1153,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1123,6 +1202,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1168,14 +1248,27 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1189,7 +1282,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1246,6 +1342,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -1266,6 +1363,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1429,6 +1528,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
diff --git a/backend/services/userService/main.go b/backend/services/userService/main.go
index c5a96f5..60aa63f 100644
--- a/backend/services/userService/main.go
+++ b/backend/services/userService/main.go
@@ -29,6 +29,10 @@ var (
dbUser = flag.String("db-user", getEnv("DB_USER", "postgres"), "Database user")
dbPassword = flag.String("db-password", getEnv("DB_PASSWORD", ""), "Database password")
dbName = flag.String("db-name", getEnv("DB_NAME", "top-fans"), "Database name")
+ redisHost = flag.String("redis-host", getEnv("REDIS_HOST", "localhost"), "Redis host")
+ redisPort = flag.Int("redis-port", getEnvInt("REDIS_PORT", 6379), "Redis port")
+ redisDB = flag.Int("redis-db", getEnvInt("REDIS_DB", 0), "Redis db")
+ redisPassword = flag.String("redis-password", getEnv("REDIS_PASSWORD", ""), "Redis password")
healthHandler *health.Handler
)
@@ -73,6 +77,16 @@ func main() {
logger.Sugar.Fatalf("Failed to initialize database: %v", err)
}
+ // 初始化 Redis
+ if err := initRedis(); err != nil {
+ logger.Sugar.Fatalf("Failed to initialize Redis: %v", err)
+ }
+
+ // 初始化 SMS 客户端
+ if err := service.InitSMSClient(); err != nil {
+ logger.Sugar.Fatalf("Failed to initialize SMS client: %v", err)
+ }
+
// 自动迁移数据库表
if err := autoMigrate(); err != nil {
logger.Sugar.Fatalf("Failed to migrate database: %v", err)
@@ -103,6 +117,17 @@ func initDatabase() error {
return database.Init(config)
}
+// initRedis 初始化 Redis 连接
+func initRedis() error {
+ redisConfig := database.RedisConfig{
+ Host: *redisHost,
+ Port: *redisPort,
+ Password: *redisPassword,
+ DB: *redisDB,
+ }
+ return database.InitRedis(redisConfig)
+}
+
// autoMigrate 自动迁移数据库表
func autoMigrate() error {
db := database.GetDB()
diff --git a/backend/services/userService/provider/auth_provider.go b/backend/services/userService/provider/auth_provider.go
index c8fd774..563e93a 100644
--- a/backend/services/userService/provider/auth_provider.go
+++ b/backend/services/userService/provider/auth_provider.go
@@ -2,6 +2,7 @@ package provider
import (
"context"
+ "time"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/logger"
@@ -16,6 +17,13 @@ type AuthProvider struct {
authService service.AuthService
}
+// getClientIP 从 context 中获取客户端 IP(简化实现)
+func getClientIP(ctx context.Context) string {
+ // 在实际部署中,应从网关传递的 attachments 或 header 中获取
+ // 这里返回一个默认值让代码能编译通过
+ return "127.0.0.1"
+}
+
// NewAuthProvider 创建认证Provider实例
func NewAuthProvider(authService service.AuthService) *AuthProvider {
return &AuthProvider{
@@ -32,7 +40,7 @@ func (p *AuthProvider) Register(ctx context.Context, req *pb.RegisterRequest) (*
)
// 调用Service层
- resp, err := p.authService.Register(req)
+ resp, err := p.authService.Register(ctx, req)
if err != nil {
logger.Logger.Error("Register failed",
zap.String("mobile", req.Mobile),
@@ -240,3 +248,81 @@ func (p *AuthProvider) ValidateToken(ctx context.Context, req *pb.ValidateTokenR
return resp, nil
}
+
+// SendCode 发送验证码
+func (p *AuthProvider) SendCode(ctx context.Context, req *pb.SendCodeRequest) (*pb.SendCodeResponse, error) {
+ logger.Logger.Info("Received SendCode request",
+ zap.String("mobile", req.Mobile),
+ zap.String("scene", req.Scene),
+ )
+
+ // 获取客户端IP(从context或 attachments 获取)
+ ip := getClientIP(ctx)
+
+ expiresIn, err := service.SendVerificationCode(ctx, req.Mobile, ip)
+ if err != nil {
+ logger.Logger.Error("SendCode failed",
+ zap.String("mobile", req.Mobile),
+ zap.Error(err),
+ )
+ return &pb.SendCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: appErrors.ToStatusCode(err),
+ Message: err.Error(),
+ Timestamp: time.Now().UnixMilli(),
+ },
+ }, nil
+ }
+
+ logger.Logger.Info("SendCode successful",
+ zap.String("mobile", req.Mobile),
+ )
+
+ return &pb.SendCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_OK,
+ Message: "发送成功",
+ Timestamp: time.Now().UnixMilli(),
+ },
+ ExpiresIn: int32(expiresIn),
+ }, nil
+}
+
+// VerifyCode 验证验证码
+func (p *AuthProvider) VerifyCode(ctx context.Context, req *pb.VerifyCodeRequest) (*pb.VerifyCodeResponse, error) {
+ logger.Logger.Info("Received VerifyCode request",
+ zap.String("mobile", req.Mobile),
+ zap.String("scene", req.Scene),
+ )
+
+ token, err := service.VerifyCode(ctx, req.Mobile, req.Code)
+ if err != nil {
+ logger.Logger.Warn("VerifyCode failed",
+ zap.String("mobile", req.Mobile),
+ zap.Error(err),
+ )
+ return &pb.VerifyCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: appErrors.ToStatusCode(err),
+ Message: err.Error(),
+ Timestamp: time.Now().UnixMilli(),
+ },
+ Verified: false,
+ }, nil
+ }
+
+ logger.Logger.Info("VerifyCode successful",
+ zap.String("mobile", req.Mobile),
+ )
+
+ return &pb.VerifyCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_OK,
+ Message: "验证成功",
+ Timestamp: time.Now().UnixMilli(),
+ },
+ Verified: true,
+ VerifyToken: token,
+ ExpiresIn: 300,
+ }, nil
+}
diff --git a/backend/services/userService/provider/unified_provider.go b/backend/services/userService/provider/unified_provider.go
index 075a34d..ee55a44 100644
--- a/backend/services/userService/provider/unified_provider.go
+++ b/backend/services/userService/provider/unified_provider.go
@@ -69,6 +69,16 @@ func (p *UnifiedProvider) CheckMobile(ctx context.Context, req *pb.CheckMobileRe
return p.userProvider.CheckMobile(ctx, req)
}
+// SendCode 发送验证码
+func (p *UnifiedProvider) SendCode(ctx context.Context, req *pb.SendCodeRequest) (*pb.SendCodeResponse, error) {
+ return p.authProvider.SendCode(ctx, req)
+}
+
+// VerifyCode 验证验证码
+func (p *UnifiedProvider) VerifyCode(ctx context.Context, req *pb.VerifyCodeRequest) (*pb.VerifyCodeResponse, error) {
+ return p.authProvider.VerifyCode(ctx, req)
+}
+
// ==================== 用户信息相关方法(委托给 UserProvider)====================
// GetUser 获取用户信息
diff --git a/backend/services/userService/service/auth_service.go b/backend/services/userService/service/auth_service.go
index e95968e..75d8108 100644
--- a/backend/services/userService/service/auth_service.go
+++ b/backend/services/userService/service/auth_service.go
@@ -1,6 +1,7 @@
package service
import (
+ "context"
"errors"
"fmt"
"time"
@@ -19,8 +20,8 @@ import (
// AuthService 认证Service接口
type AuthService interface {
- // Register 注册
- Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error)
+ // Register 注册(ctx用于验证verify_token)
+ Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error)
// Login 登录
Login(req *pb.LoginRequest) (*pb.LoginResponse, error)
@@ -59,7 +60,17 @@ func NewAuthService(
}
// Register 用户注册
-func (s *authService) Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
+func (s *authService) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
+ // 0. 验证明ver_token(新增)
+ if req.VerifyToken != "" {
+ if err := VerifyToken(ctx, req.Mobile, req.VerifyToken); err != nil {
+ logger.Logger.Warn("Verify token validation failed",
+ zap.String("mobile", req.Mobile),
+ zap.Error(err))
+ return nil, fmt.Errorf("invalid verify_token: %w", err)
+ }
+ }
+
// 1. 参数验证
if !validator.ValidateMobile(req.Mobile) {
logger.Logger.Warn("Invalid mobile format",
diff --git a/backend/services/userService/service/sms_redis.go b/backend/services/userService/service/sms_redis.go
new file mode 100644
index 0000000..7d6fd1a
--- /dev/null
+++ b/backend/services/userService/service/sms_redis.go
@@ -0,0 +1,297 @@
+package service
+
+import (
+ "context"
+ "crypto/rand"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/redis/go-redis/v9"
+ "github.com/topfans/backend/pkg/database"
+)
+
+const (
+ SMSCodeKeyPrefix = "sms:register:" // 验证码:sms:register:{mobile}
+ SMSVerifyTokenPrefix = "verify:register:" // 验证Token:verify:register:{mobile}
+ SMSLimitMobilePrefix = "sms:limit:mobile:" // 手机号频率:sms:limit:mobile:register:{mobile}
+ SMSLimitIPPrefix = "sms:limit:ip:send:" // IP频率:sms:limit:ip:send:{ip}
+ SMSBlacklistIPPrefix = "sms:blacklist:ip:" // IP黑名单:sms:blacklist:ip:{ip}
+
+ MaxMobileAttemptsPerHour = 10
+ MaxIPAttemptsPerHour = 30
+)
+
+type SMSCodeData struct {
+ Code string `json:"code"`
+ CreatedAt int64 `json:"created_at"`
+ Attempts int `json:"attempts"`
+ Used bool `json:"used"`
+}
+
+// GenerateCode generates a 6-digit random SMS code
+func GenerateCode() (string, error) {
+ code := make([]byte, 6)
+ for i := range code {
+ n, err := rand.Int(rand.Reader, big.NewInt(10))
+ if err != nil {
+ return "", fmt.Errorf("failed to generate random number: %w", err)
+ }
+ code[i] = byte('0' + n.Int64())
+ }
+ return string(code), nil
+}
+
+// GetRedisClient returns the Redis client instance
+func GetRedisClient() *redis.Client {
+ return database.GetRedis()
+}
+
+// smsCodeKey generates the Redis key for SMS code
+func smsCodeKey(mobile string) string {
+ return SMSCodeKeyPrefix + mobile
+}
+
+// verifyTokenKey generates the Redis key for verify token
+func verifyTokenKey(mobile string) string {
+ return SMSVerifyTokenPrefix + mobile
+}
+
+// mobileLimitKey generates the Redis key for mobile rate limit
+func mobileLimitKey(mobile string) string {
+ return SMSLimitMobilePrefix + mobile
+}
+
+// ipLimitKey generates the Redis key for IP rate limit
+func ipLimitKey(ip string) string {
+ return SMSLimitIPPrefix + ip
+}
+
+// blacklistIPKey generates the Redis key for IP blacklist
+func blacklistIPKey(ip string) string {
+ return SMSBlacklistIPPrefix + ip
+}
+
+// SaveSMSCode saves the SMS code as a Hash with fields: code, created_at, attempts, used
+func SaveSMSCode(ctx context.Context, mobile, code string, ttl time.Duration) error {
+ client := GetRedisClient()
+ if client == nil {
+ return fmt.Errorf("redis client is not initialized")
+ }
+
+ key := smsCodeKey(mobile)
+ now := time.Now().Unix()
+
+ pipe := client.Pipeline()
+ pipe.HSet(ctx, key, map[string]interface{}{
+ "code": code,
+ "created_at": now,
+ "attempts": 0,
+ "used": false,
+ })
+ pipe.Expire(ctx, key, ttl)
+ _, err := pipe.Exec(ctx)
+ return err
+}
+
+// GetSMSCode retrieves the SMS code data for a mobile number
+func GetSMSCode(ctx context.Context, mobile string) (*SMSCodeData, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return nil, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := smsCodeKey(mobile)
+ data, err := client.HGetAll(ctx, key).Result()
+ if err != nil {
+ return nil, err
+ }
+
+ if len(data) == 0 {
+ return nil, nil
+ }
+
+ smsData := &SMSCodeData{}
+
+ if code, ok := data["code"]; ok {
+ smsData.Code = code
+ }
+ if createdAt, ok := data["created_at"]; ok {
+ fmt.Sscanf(createdAt, "%d", &smsData.CreatedAt)
+ }
+ if attempts, ok := data["attempts"]; ok {
+ fmt.Sscanf(attempts, "%d", &smsData.Attempts)
+ }
+ if used, ok := data["used"]; ok {
+ smsData.Used = used == "true" || used == "1"
+ }
+
+ return smsData, nil
+}
+
+// DeleteSMSCode deletes the SMS code key
+func DeleteSMSCode(ctx context.Context, mobile string) error {
+ client := GetRedisClient()
+ if client == nil {
+ return fmt.Errorf("redis client is not initialized")
+ }
+
+ key := smsCodeKey(mobile)
+ return client.Del(ctx, key).Err()
+}
+
+// IncrementAttempts increments the attempts counter and returns the new count
+func IncrementAttempts(ctx context.Context, mobile string) (int, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return 0, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := smsCodeKey(mobile)
+ count, err := client.HIncrBy(ctx, key, "attempts", 1).Result()
+ if err != nil {
+ return 0, err
+ }
+ return int(count), nil
+}
+
+// MarkCodeUsed marks the SMS code as used
+func MarkCodeUsed(ctx context.Context, mobile string) error {
+ client := GetRedisClient()
+ if client == nil {
+ return fmt.Errorf("redis client is not initialized")
+ }
+
+ key := smsCodeKey(mobile)
+ return client.HSet(ctx, key, "used", "true").Err()
+}
+
+// SaveVerifyToken saves the verify token as a String
+func SaveVerifyToken(ctx context.Context, mobile, token string, ttl time.Duration) error {
+ client := GetRedisClient()
+ if client == nil {
+ return fmt.Errorf("redis client is not initialized")
+ }
+
+ key := verifyTokenKey(mobile)
+ return client.Set(ctx, key, token, ttl).Err()
+}
+
+// GetVerifyToken retrieves the verify token for a mobile number
+func GetVerifyToken(ctx context.Context, mobile string) (string, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return "", fmt.Errorf("redis client is not initialized")
+ }
+
+ key := verifyTokenKey(mobile)
+ token, err := client.Get(ctx, key).Result()
+ if err == redis.Nil {
+ return "", nil
+ }
+ return token, err
+}
+
+// DeleteVerifyToken deletes the verify token
+func DeleteVerifyToken(ctx context.Context, mobile string) error {
+ client := GetRedisClient()
+ if client == nil {
+ return fmt.Errorf("redis client is not initialized")
+ }
+
+ key := verifyTokenKey(mobile)
+ return client.Del(ctx, key).Err()
+}
+
+// CheckRateLimitMobile checks if the mobile has sent less than 10 times in the current hour
+func CheckRateLimitMobile(ctx context.Context, mobile string) (bool, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return false, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := mobileLimitKey(mobile)
+ count, err := client.Get(ctx, key).Int()
+ if err == redis.Nil {
+ return true, nil
+ }
+ if err != nil {
+ return false, err
+ }
+ return count < MaxMobileAttemptsPerHour, nil
+}
+
+// IncrMobileCount increments the mobile send count with 1 hour TTL
+func IncrMobileCount(ctx context.Context, mobile string) (int, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return 0, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := mobileLimitKey(mobile)
+ pipe := client.Pipeline()
+
+ incr := pipe.Incr(ctx, key)
+ pipe.Expire(ctx, key, time.Hour)
+
+ _, err := pipe.Exec(ctx)
+ if err != nil {
+ return 0, err
+ }
+
+ return int(incr.Val()), nil
+}
+
+// CheckRateLimitIP checks if the IP has sent less than 30 times in the current hour
+func CheckRateLimitIP(ctx context.Context, ip string) (bool, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return false, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := ipLimitKey(ip)
+ count, err := client.Get(ctx, key).Int()
+ if err == redis.Nil {
+ return true, nil
+ }
+ if err != nil {
+ return false, err
+ }
+ return count < MaxIPAttemptsPerHour, nil
+}
+
+// IncrIPCount increments the IP send count with 1 hour TTL
+func IncrIPCount(ctx context.Context, ip string) (int, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return 0, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := ipLimitKey(ip)
+ pipe := client.Pipeline()
+
+ incr := pipe.Incr(ctx, key)
+ pipe.Expire(ctx, key, time.Hour)
+
+ _, err := pipe.Exec(ctx)
+ if err != nil {
+ return 0, err
+ }
+
+ return int(incr.Val()), nil
+}
+
+// CheckIPBlacklist checks if the IP is blacklisted
+func CheckIPBlacklist(ctx context.Context, ip string) (bool, error) {
+ client := GetRedisClient()
+ if client == nil {
+ return false, fmt.Errorf("redis client is not initialized")
+ }
+
+ key := blacklistIPKey(ip)
+ exists, err := client.Exists(ctx, key).Result()
+ if err != nil {
+ return false, err
+ }
+ return exists > 0, nil
+}
\ No newline at end of file
diff --git a/backend/services/userService/service/sms_service.go b/backend/services/userService/service/sms_service.go
new file mode 100644
index 0000000..e92d4db
--- /dev/null
+++ b/backend/services/userService/service/sms_service.go
@@ -0,0 +1,262 @@
+package service
+
+import (
+ "context"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "math/big"
+ "time"
+
+ dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20180501/v2/client"
+ "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+ "github.com/topfans/backend/pkg/logger"
+ "github.com/topfans/backend/services/userService/config"
+ "go.uber.org/zap"
+)
+
+var smsClient *dysmsapi20170525.Client
+
+// InitSMSClient initializes the SMS client as a singleton
+func InitSMSClient() error {
+ cfg := config.GetSMSConfig()
+
+ // If credentials are not set, skip initialization
+ if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
+ logger.Logger.Warn("SMS credentials not configured, SMS client not initialized")
+ return nil
+ }
+
+ openapiConfig := &client.Config{
+ AccessKeyId: tea.String(cfg.AccessKeyID),
+ AccessKeySecret: tea.String(cfg.AccessKeySecret),
+ }
+ openapiConfig.Endpoint = tea.String("dysmsapi.aliyuncs.com")
+
+ var err error
+ smsClient, err = dysmsapi20170525.NewClient(openapiConfig)
+ if err != nil {
+ return fmt.Errorf("failed to initialize SMS client: %w", err)
+ }
+
+ logger.Logger.Info("SMS client initialized successfully")
+ return nil
+}
+
+// generateVerifyToken generates a verify token with format "vtf_" + 29 random chars
+func generateVerifyToken() (string, error) {
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ const tokenLen = 29
+
+ b := make([]byte, tokenLen)
+ charsLen := big.NewInt(int64(len(charset)))
+
+ for i := range b {
+ n, err := rand.Int(rand.Reader, charsLen)
+ if err != nil {
+ return "", fmt.Errorf("failed to generate random token: %w", err)
+ }
+ b[i] = charset[n.Int64()]
+ }
+
+ return "vtf_" + string(b), nil
+}
+
+// maskMobile masks the middle digits of a mobile number
+// Example: 13812345678 -> 138****5678
+func maskMobile(mobile string) string {
+ if len(mobile) < 7 {
+ return mobile
+ }
+ return mobile[:3] + "****" + mobile[len(mobile)-4:]
+}
+
+// SendVerificationCode sends a verification code to the given mobile number
+func SendVerificationCode(ctx context.Context, mobile, ip string) (int, error) {
+ logger := logger.Logger.With(zap.String("mobile", maskMobile(mobile)), zap.String("ip", ip))
+
+ // Step 1: Check IP blacklist
+ blacklisted, err := CheckIPBlacklist(ctx, ip)
+ if err != nil {
+ logger.Error("Failed to check IP blacklist", zap.Error(err))
+ return 0, fmt.Errorf("failed to check IP blacklist: %w", err)
+ }
+ if blacklisted {
+ logger.Warn("IP is blacklisted")
+ return 0, errors.New("IP黑名单")
+ }
+
+ // Step 2: Check if code was recently sent (TTL check via GetSMSCode)
+ smsData, err := GetSMSCode(ctx, mobile)
+ if err != nil {
+ logger.Error("Failed to get SMS code from Redis", zap.Error(err))
+ return 0, fmt.Errorf("failed to check recent SMS: %w", err)
+ }
+ if smsData != nil {
+ elapsed := time.Now().Unix() - smsData.CreatedAt
+ if elapsed < 60 {
+ logger.Warn("SMS code recently sent", zap.Int64("elapsed_seconds", elapsed))
+ return 0, errors.New("发送过于频繁")
+ }
+ }
+
+ // Step 3: Check mobile hourly limit (10/hour)
+ allowed, err := CheckRateLimitMobile(ctx, mobile)
+ if err != nil {
+ logger.Error("Failed to check mobile rate limit", zap.Error(err))
+ return 0, fmt.Errorf("failed to check mobile rate limit: %w", err)
+ }
+ if !allowed {
+ logger.Warn("Mobile hourly limit exceeded")
+ return 0, errors.New("当前手机号发送次数超限")
+ }
+
+ // Step 4: Check IP hourly limit (30/hour)
+ allowed, err = CheckRateLimitIP(ctx, ip)
+ if err != nil {
+ logger.Error("Failed to check IP rate limit", zap.Error(err))
+ return 0, fmt.Errorf("failed to check IP rate limit: %w", err)
+ }
+ if !allowed {
+ logger.Warn("IP hourly limit exceeded")
+ return 0, errors.New("请求过于频繁")
+ }
+
+ // Step 5: Generate 6-digit code
+ code, err := GenerateCode()
+ if err != nil {
+ logger.Error("Failed to generate SMS code", zap.Error(err))
+ return 0, fmt.Errorf("failed to generate code: %w", err)
+ }
+
+ // Step 6: Call Aliyun SMS API to send (if client is initialized)
+ if smsClient != nil {
+ cfg := config.GetSMSConfig()
+ sendReq := &dysmsapi20170525.SendMessageWithTemplateRequest{
+ To: tea.String("86" + mobile), // China area code + mobile
+ From: tea.String(cfg.SignName),
+ TemplateCode: tea.String(cfg.TemplateCode),
+ TemplateParam: tea.String(fmt.Sprintf(`{"code":"%s"}`, code)),
+ }
+
+ _, err := smsClient.SendMessageWithTemplate(sendReq)
+ if err != nil {
+ logger.Error("Failed to send SMS via Aliyun", zap.Error(err))
+ return 0, fmt.Errorf("failed to send SMS: %w", err)
+ }
+
+ logger.Info("SMS sent successfully via Aliyun")
+ } else {
+ logger.Warn("SMS client not initialized, skipping actual SMS send")
+ }
+
+ // Step 7: Save code to Redis with 60s TTL
+ err = SaveSMSCode(ctx, mobile, code, 60*time.Second)
+ if err != nil {
+ logger.Error("Failed to save SMS code to Redis", zap.Error(err))
+ return 0, fmt.Errorf("failed to save code: %w", err)
+ }
+
+ // Step 8: Increment mobile and IP counters
+ _, err = IncrMobileCount(ctx, mobile)
+ if err != nil {
+ logger.Error("Failed to increment mobile count", zap.Error(err))
+ }
+
+ _, err = IncrIPCount(ctx, ip)
+ if err != nil {
+ logger.Error("Failed to increment IP count", zap.Error(err))
+ }
+
+ logger.Info("Verification code sent successfully")
+ return 60, nil
+}
+
+// VerifyCode verifies the SMS code and returns a verify token on success
+func VerifyCode(ctx context.Context, mobile, code string) (string, error) {
+ logger := logger.Logger.With(zap.String("mobile", maskMobile(mobile)))
+
+ // Step 1: Get stored code data
+ smsData, err := GetSMSCode(ctx, mobile)
+ if err != nil {
+ logger.Error("Failed to get SMS code from Redis", zap.Error(err))
+ return "", fmt.Errorf("failed to get SMS code: %w", err)
+ }
+ if smsData == nil {
+ logger.Warn("No SMS code found for mobile")
+ return "", errors.New("验证码不存在或已过期")
+ }
+
+ // Step 2: Check if already used
+ if smsData.Used {
+ logger.Warn("SMS code already used")
+ return "", errors.New("验证码已使用")
+ }
+
+ // Step 3: Compare codes
+ if smsData.Code != code {
+ attempts, _ := IncrementAttempts(ctx, mobile)
+ logger.Warn("SMS code mismatch", zap.Int("attempts", attempts))
+
+ // Delete code if attempts >= 3
+ if attempts >= 3 {
+ DeleteSMSCode(ctx, mobile)
+ logger.Info("SMS code deleted due to max attempts")
+ return "", errors.New("验证失败次数过多,请重新获取验证码")
+ }
+ return "", errors.New("验证码错误")
+ }
+
+ // Step 4: On success - delete code, generate verify_token, save with 300s TTL
+ if err := DeleteSMSCode(ctx, mobile); err != nil {
+ logger.Error("Failed to delete SMS code", zap.Error(err))
+ }
+
+ verifyToken, err := generateVerifyToken()
+ if err != nil {
+ logger.Error("Failed to generate verify token", zap.Error(err))
+ return "", fmt.Errorf("failed to generate verify token: %w", err)
+ }
+
+ err = SaveVerifyToken(ctx, mobile, verifyToken, 300*time.Second)
+ if err != nil {
+ logger.Error("Failed to save verify token", zap.Error(err))
+ return "", fmt.Errorf("failed to save verify token: %w", err)
+ }
+
+ logger.Info("SMS code verified successfully, verify token issued")
+ return verifyToken, nil
+}
+
+// VerifyToken verifies the verify_token during registration (one-time use)
+func VerifyToken(ctx context.Context, mobile, token string) error {
+ logger := logger.Logger.With(zap.String("mobile", maskMobile(mobile)))
+
+ // Step 1: Get stored token from Redis
+ storedToken, err := GetVerifyToken(ctx, mobile)
+ if err != nil {
+ logger.Error("Failed to get verify token from Redis", zap.Error(err))
+ return fmt.Errorf("failed to get verify token: %w", err)
+ }
+
+ // Step 2: Compare with provided token
+ if storedToken == "" {
+ logger.Warn("No verify token found for mobile")
+ return errors.New("验证令牌不存在或已过期")
+ }
+
+ if storedToken != token {
+ logger.Warn("Verify token mismatch")
+ return errors.New("验证令牌无效")
+ }
+
+ // Step 3: If match, delete the token (one-time use)
+ if err := DeleteVerifyToken(ctx, mobile); err != nil {
+ logger.Error("Failed to delete verify token", zap.Error(err))
+ return fmt.Errorf("failed to delete verify token: %w", err)
+ }
+
+ logger.Info("Verify token validated and consumed successfully")
+ return nil
+}
\ No newline at end of file
diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml
index 3eb97ab..ad8b523 100644
--- a/docker/docker-compose.local.yml
+++ b/docker/docker-compose.local.yml
@@ -37,6 +37,9 @@ services:
environment:
<<: *common-env
PORT: 20000
+ REDIS_HOST: host.docker.internal
+ REDIS_PORT: 6379
+ REDIS_DB: 0
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
diff --git a/docker/sql/migrations/migrate_create_sms_send_log_table.sql b/docker/sql/migrations/migrate_create_sms_send_log_table.sql
new file mode 100644
index 0000000..9d333a1
--- /dev/null
+++ b/docker/sql/migrations/migrate_create_sms_send_log_table.sql
@@ -0,0 +1,35 @@
+-- ============================================================================
+-- SMS发送日志表 (sms_send_log)
+-- ============================================================================
+-- 用途:记录每次短信发送情况,用于资源核算和成本分析
+-- 业务场景:
+-- - 用户注册时的手机号验证 (scene: register)
+-- - 忘记密码时的手机号验证 (scene: password)
+-- 相关服务:userService (短信验证码功能)
+-- 维护注意事项:
+-- - mobile 字段脱敏存储(前端只显示138****5678格式)
+-- - 验证码内容不记录,只记录发送状态
+-- - 索引:mobile(查询), scene(统计), send_time(报表)
+-- ============================================================================
+
+-- SMS发送日志表
+-- 用于记录每次短信发送情况,便于资源核算和成本分析
+CREATE TABLE IF NOT EXISTS sms_send_log (
+ id BIGSERIAL PRIMARY KEY,
+ mobile VARCHAR(20) NOT NULL,
+ scene VARCHAR(20) NOT NULL DEFAULT 'register',
+ template_code VARCHAR(50) NOT NULL,
+ sign_name VARCHAR(50) NOT NULL,
+ message_id VARCHAR(64) DEFAULT '',
+ response_code VARCHAR(20) DEFAULT '',
+ response_description VARCHAR(255) DEFAULT '',
+ status SMALLINT NOT NULL DEFAULT 1,
+ send_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 索引
+CREATE INDEX idx_sms_send_log_mobile ON sms_send_log(mobile);
+CREATE INDEX idx_sms_send_log_scene ON sms_send_log(scene);
+CREATE INDEX idx_sms_send_log_send_time ON sms_send_log(send_time);
diff --git a/docs/superpowers/plans/2026-05-26-sms-register-implementation.md b/docs/superpowers/plans/2026-05-26-sms-register-implementation.md
new file mode 100644
index 0000000..de8f777
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-26-sms-register-implementation.md
@@ -0,0 +1,1140 @@
+# 注册短信验证码功能实现计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 在用户注册流程中加入阿里云短信验证码验证,确保手机号真实有效,防止恶意注册。
+
+**Architecture:**
+- 采用方案一(userService直接集成):前端 → 网关 → userService → 阿里云SMS/Redis
+- 注册验证码存储在Redis(Hash结构,60秒TTL)
+- 验证成功后生成verify_token存入Redis(300秒TTL),注册时校验
+- 限流策略:60秒内不能重复发送,每小时最多10次,验证失败3次强制删除验证码
+
+**Tech Stack:** Go (Dubbo-go), Redis, 阿里云SMS SDK, Vue/uni-app
+
+---
+
+## 文件结构
+
+### 后端改动
+
+**新增文件:**
+- `backend/services/userService/config/sms_config.go` - SMS配置结构体
+- `backend/services/userService/service/sms_service.go` - 短信发送服务
+- `backend/services/userService/pkg/redis/sms_redis.go` - Redis操作(验证码存储、限流)
+- `backend/pkg/proto/user/auth.pb.go` - Proto生成文件(新增SendCode/VerifyCode方法)
+
+**修改文件:**
+- `backend/services/userService/main.go` - 初始化SMS配置和依赖
+- `backend/services/userService/service/auth_service.go` - 添加verify_token校验逻辑
+- `backend/services/userService/service/auth_service.go:62-96` - Register方法需增加verify_token验证
+- `backend/services/userService/provider/auth_provider.go` - 实现SendCode/VerifyCode的RPC处理
+- `backend/services/userService/provider/unified_provider.go` - 注册新方法到Dubbo
+
+**网关改动:**
+- `backend/gateway/controller/auth_controller.go` - 添加send-code/verify-code HTTP端点
+- `backend/gateway/dto/auth_dto.go` - 添加请求/响应DTO
+- `backend/gateway/router/` - 路由配置
+
+**数据库:**
+- `backend/migrations/` - 新增 `sms_send_log` 表
+
+### 前端改动
+
+**修改文件:**
+- `frontend/pages/register/register.vue` - 添加短信验证码交互流程
+- `frontend/utils/api.js` - 添加send-code/verify-code API
+
+---
+
+## 阶段一:后端基础设施
+
+### Task 1: 添加阿里云SMS SDK依赖
+
+**Files:**
+- Modify: `backend/services/userService/go.mod`
+
+- [ ] **Step 1: 添加阿里云SMS SDK依赖**
+
+```bash
+cd /Users/liulujian/Documents/code/TopFansByGithub/backend/services/userService
+go get github.com/alibabacloud-go/dysmsapi-20180501/v2/client
+go get github.com/alibabacloud-go/darabonba-openapi/v2/client
+go get github.com/alibabacloud-go/tea/tea
+go get github.com/alibabacloud-go/tea-utils/v2/service
+```
+
+- [ ] **Step 2: 验证依赖安装**
+
+```bash
+cd /Users/liulujian/Documents/code/TopFansByGithub/backend/services/userService
+go mod tidy
+go build -o userService .
+```
+
+---
+
+### Task 2: 创建SMS配置
+
+**Files:**
+- Create: `backend/services/userService/config/sms_config.go`
+
+- [ ] **Step 1: 编写SMSConfig配置结构体**
+
+```go
+package config
+
+// SMSConfig 短信配置
+type SMSConfig struct {
+ AccessKeyID string
+ AccessKeySecret string
+ SignName string // 短信签名
+ TemplateCode string // 短信模板CODE
+ Region string // 区域(默认 cn-hangzhou)
+}
+
+// GetSMSConfig 获取SMS配置(从环境变量)
+func GetSMSConfig() *SMSConfig {
+ return &SMSConfig{
+ AccessKeyID: getEnv("SMS_ACCESS_KEY_ID", ""),
+ AccessKeySecret: getEnv("SMS_ACCESS_KEY_SECRET", ""),
+ SignName: getEnv("SMS_SIGN_NAME", "TopFans"),
+ TemplateCode: getEnv("SMS_TEMPLATE_CODE", ""),
+ Region: getEnv("SMS_REGION", "cn-hangzhou"),
+ }
+}
+
+func getEnv(key, fallback string) string {
+ if v := os.Getenv(key); v != "" {
+ return v
+ }
+ return fallback
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add backend/services/userService/config/sms_config.go
+git commit -m "feat: add SMS config structure"
+```
+
+---
+
+### Task 3: 创建Redis操作层(验证码存储+限流)
+
+**Files:**
+- Create: `backend/services/userService/pkg/redis/sms_redis.go`
+
+- [ ] **Step 1: 编写Redis操作函数**
+
+```go
+package redis
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "strconv"
+ "time"
+
+ "github.com/redis/go-redis/v9"
+ "github.com/topfans/backend/pkg/logger"
+)
+
+// SMS Redis Key 前缀
+const (
+ SMSCodeKeyPrefix = "sms:register:" // 验证码:sms:register:{mobile}
+ SMSVerifyTokenPrefix = "verify:register:" // 验证Token:verify:register:{mobile}
+ SMSLimitMobilePrefix = "sms:limit:mobile:" // 手机号频率:sms:limit:mobile:register:{mobile}
+ SMSLimitIPPrefix = "sms:limit:ip:send:" // IP频率:sms:limit:ip:send:{ip}
+ SMSBlacklistIPPrefix = "sms:blacklist:ip:" // IP黑名单:sms:blacklist:ip:{ip}
+)
+
+// SMSCodeData 验证码数据结构
+type SMSCodeData struct {
+ Code string `json:"code"`
+ CreatedAt int64 `json:"created_at"`
+ Attempts int `json:"attempts"`
+ Used bool `json:"used"`
+}
+
+// GenerateCode 生成6位数字验证码
+func GenerateCode() string {
+ rand.Seed(time.Now().UnixNano())
+ code := rand.Intn(900000) + 100000 // 100000-999999
+ return strconv.Itoa(code)
+}
+
+// SaveSMSCode 保存验证码到Redis
+func SaveSMSCode(ctx context.Context, mobile, code string, ttl time.Duration) error {
+ key := SMSCodeKeyPrefix + mobile
+ data := &SMSCodeData{
+ Code: code,
+ CreatedAt: time.Now().Unix(),
+ Attempts: 0,
+ Used: false,
+ }
+ // 使用Hash存储
+ err := GetRedisClient().HSet(ctx, key, map[string]interface{}{
+ "code": data.Code,
+ "created_at": data.CreatedAt,
+ "attempts": data.Attempts,
+ "used": data.Used,
+ }).Err()
+ if err != nil {
+ return err
+ }
+ return GetRedisClient().Expire(ctx, key, ttl).Err()
+}
+
+// GetSMSCode 获取验证码数据
+func GetSMSCode(ctx context.Context, mobile string) (*SMSCodeData, error) {
+ key := SMSCodeKeyPrefix + mobile
+ result, err := GetRedisClient().HGetAll(ctx, key).Result()
+ if err != nil {
+ return nil, err
+ }
+ if len(result) == 0 {
+ return nil, fmt.Errorf("验证码不存在或已过期")
+ }
+ attempts, _ := strconv.Atoi(result["attempts"])
+ return &SMSCodeData{
+ Code: result["code"],
+ CreatedAt: parseInt64(result["created_at"]),
+ Attempts: attempts,
+ Used: result["used"] == "true",
+ }, nil
+}
+
+// DeleteSMSCode 删除验证码
+func DeleteSMSCode(ctx context.Context, mobile string) error {
+ key := SMSCodeKeyPrefix + mobile
+ return GetRedisClient().Del(ctx, key).Err()
+}
+
+// IncrementAttempts 递增验证失败次数
+func IncrementAttempts(ctx context.Context, mobile string) (int, error) {
+ key := SMSCodeKeyPrefix + mobile
+ count, err := GetRedisClient().HIncrBy(ctx, key, "attempts", 1).Result()
+ return int(count), err
+}
+
+// MarkCodeUsed 标记验证码已使用
+func MarkCodeUsed(ctx context.Context, mobile string) error {
+ key := SMSCodeKeyPrefix + mobile
+ return GetRedisClient().HSet(ctx, key, "used", "true").Err()
+}
+
+// SaveVerifyToken 保存验证Token
+func SaveVerifyToken(ctx context.Context, mobile, token string, ttl time.Duration) error {
+ key := SMSVerifyTokenPrefix + mobile
+ return GetRedisClient().Set(ctx, key, token, ttl).Err()
+}
+
+// GetVerifyToken 获取验证Token
+func GetVerifyToken(ctx context.Context, mobile string) (string, error) {
+ key := SMSVerifyTokenPrefix + mobile
+ return GetRedisClient().Get(ctx, key).Result()
+}
+
+// DeleteVerifyToken 删除验证Token
+func DeleteVerifyToken(ctx context.Context, mobile string) error {
+ key := SMSVerifyTokenPrefix + mobile
+ return GetRedisClient().Del(ctx, key).Err()
+}
+
+// CheckRateLimitMobile 检查手机号发送频率限制
+func CheckRateLimitMobile(ctx context.Context, mobile string) (bool, int, error) {
+ key := SMSLimitMobilePrefix + mobile
+ count, err := GetRedisClient().Get(ctx, key).Result()
+ if err == redis.Nil {
+ return true, 0, nil // 没有限制记录
+ }
+ if err != nil {
+ return false, 0, err
+ }
+ limitCount, _ := strconv.Atoi(count)
+ return limitCount < 10, limitCount, nil // 每小时最多10次
+}
+
+// IncrMobileCount 增加手机号发送计数
+func IncrMobileCount(ctx context.Context, mobile string) error {
+ key := SMSLimitMobilePrefix + mobile
+ pipe := GetRedisClient().Pipeline()
+ pipe.Incr(ctx, key)
+ pipe.Expire(ctx, key, time.Hour) // 1小时后过期
+ _, err := pipe.Exec(ctx)
+ return err
+}
+
+// CheckRateLimitIP 检查IP发送频率限制
+func CheckRateLimitIP(ctx context.Context, ip string) (bool, int, error) {
+ key := SMSLimitIPPrefix + ip
+ count, err := GetRedisClient().Get(ctx, key).Result()
+ if err == redis.Nil {
+ return true, 0, nil
+ }
+ if err != nil {
+ return false, 0, err
+ }
+ limitCount, _ := strconv.Atoi(count)
+ return limitCount < 30, limitCount, nil // 每小时最多30次
+}
+
+// IncrIPCount 增加IP发送计数
+func IncrIPCount(ctx context.Context, ip string) error {
+ key := SMSLimitIPPrefix + ip
+ pipe := GetRedisClient().Pipeline()
+ pipe.Incr(ctx, key)
+ pipe.Expire(ctx, key, time.Hour)
+ _, err := pipe.Exec(ctx)
+ return err
+}
+
+// CheckIPBlacklist 检查IP是否在黑名单
+func CheckIPBlacklist(ctx context.Context, ip string) (bool, error) {
+ key := SMSBlacklistIPPrefix + ip
+ exists, err := GetRedisClient().Exists(ctx, key).Result()
+ return exists > 0, err
+}
+
+func parseInt64(s string) int64 {
+ v, _ := strconv.ParseInt(s, 10, 64)
+ return v
+}
+```
+
+- [ ] **Step 2: 检查是否已有Redis客户端实现**
+
+```bash
+grep -r "go-redis" /Users/liulujian/Documents/code/TopFansByGithub/backend/services/userService/
+```
+
+如果没有,需要添加依赖并创建Redis客户端初始化代码。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add backend/services/userService/pkg/redis/sms_redis.go
+git commit -m "feat: add SMS Redis operations for code storage and rate limiting"
+```
+
+---
+
+### Task 4: 创建SMS服务
+
+**Files:**
+- Create: `backend/services/userService/service/sms_service.go`
+
+- [ ] **Step 1: 编写SMS服务**
+
+```go
+package service
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20180501/v2/client"
+ util "github.com/alibabacloud-go/tea-utils/v2/service"
+ "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+ "github.com/topfans/backend/pkg/logger"
+ "github.com/topfans/backend/services/userService/config"
+ "github.com/topfans/backend/services/userService/pkg/redis"
+ "go.uber.org/zap"
+)
+
+var smsClient *dysmsapi20170525.Client
+
+// InitSMSClient 初始化SMS客户端(单例模式)
+func InitSMSClient() error {
+ cfg := config.GetSMSConfig()
+ if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
+ logger.Logger.Warn("SMS credentials not configured, SMS service disabled")
+ return nil
+ }
+
+ openapiConfig := &client.Config{
+ AccessKeyId: tea.String(cfg.AccessKeyID),
+ AccessKeySecret: tea.String(cfg.AccessKeySecret),
+ }
+ openapiConfig.Endpoint = tea.String("dysmsapi.aliyuncs.com")
+
+ var err error
+ smsClient, err = dysmsapi20170525.NewClient(openapiConfig)
+ if err != nil {
+ return fmt.Errorf("failed to create SMS client: %w", err)
+ }
+
+ logger.Logger.Info("SMS client initialized successfully")
+ return nil
+}
+
+// SendVerificationCode 发送注册验证码
+func SendVerificationCode(ctx context.Context, mobile, ip string) (int, error) {
+ // 1. 检查IP黑名单
+ isBlacklisted, err := redis.CheckIPBlacklist(ctx, ip)
+ if err != nil {
+ logger.Logger.Error("Failed to check IP blacklist", zap.Error(err))
+ }
+ if isBlacklisted {
+ return 0, fmt.Errorf("暂时无法操作,请稍后再试")
+ }
+
+ // 2. 检查手机号发送频率(60秒内不能重复发送)
+ // 这里用Redis TTL来检查:如果sms:register:{mobile}存在,说明60秒内发过
+ existingCode, _ := redis.GetSMSCode(ctx, mobile)
+ if existingCode != nil {
+ return 0, fmt.Errorf("发送过于频繁,请稍后再试")
+ }
+
+ // 3. 检查每小时发送次数限制
+ allowed, count, err := redis.CheckRateLimitMobile(ctx, mobile)
+ if err != nil {
+ logger.Logger.Error("Failed to check mobile rate limit", zap.Error(err))
+ }
+ if !allowed {
+ return 0, fmt.Errorf("当前手机号发送次数超限,请稍后再试")
+ }
+
+ // 4. 检查IP发送频率
+ allowed, _, err = redis.CheckRateLimitIP(ctx, ip)
+ if err != nil {
+ logger.Logger.Error("Failed to check IP rate limit", zap.Error(err))
+ }
+ if !allowed {
+ return 0, fmt.Errorf("请求过于频繁,请稍后再试")
+ }
+
+ // 5. 生成6位验证码
+ code := redis.GenerateCode()
+
+ // 6. 发送短信
+ if smsClient != nil {
+ cfg := config.GetSMSConfig()
+ request := &dysmsapi20170525.SendMessageWithTemplateRequest{
+ ToNumber: tea.String("86" + mobile), // 中国区号+手机号
+ FromNumber: tea.String(cfg.SignName),
+ TemplateCode: tea.String(cfg.TemplateCode),
+ TemplateParam: tea.String(fmt.Sprintf(`{"code":"%s"}`, code)),
+ }
+
+ runtime := &util.RuntimeOptions{}
+ response, err := smsClient.SendMessageWithTemplateWithOptions(request, runtime)
+ if err != nil {
+ logger.Logger.Error("Failed to send SMS",
+ zap.String("mobile", maskMobile(mobile)),
+ zap.Error(err))
+ return 0, fmt.Errorf("服务暂不可用,请稍后重试")
+ }
+
+ if response.Body.ResponseCode != nil && *response.Body.ResponseCode != "OK" {
+ logger.Logger.Error("SMS API returned error",
+ zap.String("code", *response.Body.ResponseCode),
+ zap.String("description", *response.Body.ResponseDescription))
+ return 0, fmt.Errorf("发送失败,请稍后重试")
+ }
+
+ logger.Logger.Info("SMS sent successfully",
+ zap.String("mobile", maskMobile(mobile)),
+ zap.String("message_id", tea.StringValue(response.Body.MessageId)))
+ }
+
+ // 7. 存储验证码到Redis(60秒TTL)
+ if err := redis.SaveSMSCode(ctx, mobile, code, 60*time.Second); err != nil {
+ logger.Logger.Error("Failed to save SMS code to Redis",
+ zap.String("mobile", maskMobile(mobile)),
+ zap.Error(err))
+ return 0, fmt.Errorf("服务暂不可用,请稍后重试")
+ }
+
+ // 8. 增加发送计数
+ redis.IncrMobileCount(ctx, mobile)
+ redis.IncrIPCount(ctx, ip)
+
+ return 60, nil // 返回60秒后可以重发
+}
+
+// VerifyCode 验证验证码
+func VerifyCode(ctx context.Context, mobile, code string) (string, error) {
+ // 1. 获取存储的验证码
+ storedData, err := redis.GetSMSCode(ctx, mobile)
+ if err != nil {
+ return "", fmt.Errorf("验证码已过期,请重新获取")
+ }
+
+ // 2. 检查是否已使用
+ if storedData.Used {
+ return "", fmt.Errorf("验证码已使用,请重新获取")
+ }
+
+ // 3. 验证code是否正确
+ if storedData.Code != code {
+ // 验证失败,递增失败次数
+ attempts, _ := redis.IncrementAttempts(ctx, mobile)
+ if attempts >= 3 {
+ // 失败3次,删除验证码
+ redis.DeleteSMSCode(ctx, mobile)
+ return "", fmt.Errorf("验证失败次数过多,请重新获取")
+ }
+ remaining := 3 - attempts
+ return "", fmt.Errorf("验证码错误,剩余%d次", remaining)
+ }
+
+ // 4. 验证成功,删除验证码
+ redis.DeleteSMSCode(ctx, mobile)
+
+ // 5. 生成verify_token
+ token := generateVerifyToken()
+
+ // 6. 存储verify_token(300秒TTL)
+ if err := redis.SaveVerifyToken(ctx, mobile, token, 300*time.Second); err != nil {
+ logger.Logger.Error("Failed to save verify token",
+ zap.String("mobile", maskMobile(mobile)),
+ zap.Error(err))
+ return "", fmt.Errorf("验证服务暂不可用")
+ }
+
+ logger.Logger.Info("SMS code verified successfully",
+ zap.String("mobile", maskMobile(mobile)))
+
+ return token, nil
+}
+
+// VerifyToken 验证verify_token(注册时调用)
+func VerifyToken(ctx context.Context, mobile, token string) error {
+ storedToken, err := redis.GetVerifyToken(ctx, mobile)
+ if err != nil {
+ return fmt.Errorf("验证码已失效,请重新获取")
+ }
+ if storedToken != token {
+ return fmt.Errorf("验证失败,请重新获取验证码")
+ }
+ // 验证成功后删除token
+ redis.DeleteVerifyToken(ctx, mobile)
+ return nil
+}
+
+// generateVerifyToken 生成verify_token
+func generateVerifyToken() string {
+ const prefix = "vtf_"
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ rand.Seed(time.Now().UnixNano())
+ b := make([]byte, 29)
+ for i := range b {
+ b[i] = chars[rand.Intn(len(chars))]
+ }
+ return prefix + string(b)
+}
+
+// maskMobile 手机号脱敏
+func maskMobile(mobile string) string {
+ if len(mobile) < 11 {
+ return mobile
+ }
+ return mobile[:3] + "****" + mobile[7:]
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add backend/services/userService/service/sms_service.go
+git commit -m "feat: add SMS service for sending and verifying codes"
+```
+
+---
+
+### Task 5: 在AuthService中集成verify_token校验
+
+**Files:**
+- Modify: `backend/services/userService/service/auth_service.go`
+
+- [ ] **Step 1: 在Register方法开头添加verify_token校验**
+
+在 `Register` 方法的参数验证之后、手机号重复检查之前,添加:
+
+```go
+// 在Register方法中,参数验证之后,添加:
+
+// 3. 验证明ver_token(新增)
+if req.VerifyToken != "" {
+ if err := VerifyToken(ctx, req.Mobile, req.VerifyToken); err != nil {
+ logger.Logger.Warn("Verify token validation failed",
+ zap.String("mobile", req.Mobile),
+ zap.Error(err))
+ return nil, fmt.Errorf("invalid verify_token: %w", err)
+ }
+}
+```
+
+需要修改Register方法签名添加context参数。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add backend/services/userService/service/auth_service.go
+git commit -m "feat: add verify_token validation in Register method"
+```
+
+---
+
+### Task 6: 添加Proto定义(SendCode/VerifyCode方法)
+
+**Files:**
+- Modify: `backend/pkg/proto/user/auth.proto`(如果存在)
+- 或查看现有的proto文件结构
+
+- [ ] **Step 1: 查看现有proto文件**
+
+```bash
+ls -la /Users/liulujian/Documents/code/TopFansByGithub/backend/pkg/proto/user/
+cat /Users/liulujian/Documents/code/TopFansByGithub/backend/pkg/proto/user/auth.proto
+```
+
+- [ ] **Step 2: 添加新的RPC方法定义**
+
+```protobuf
+// 发送验证码请求
+message SendCodeRequest {
+ string mobile = 1;
+ string scene = 2; // register, password
+}
+
+// 发送验证码响应
+message SendCodeResponse {
+ common.BaseResponse base = 1;
+ int32 expires_in = 2; // 多少秒后可以重发
+}
+
+// 验证验证码请求
+message VerifyCodeRequest {
+ string mobile = 1;
+ string code = 2;
+ string scene = 3;
+}
+
+// 验证验证码响应
+message VerifyCodeResponse {
+ common.BaseResponse base = 1;
+ bool verified = 2;
+ string verify_token = 3;
+ int32 expires_in = 4; // token有效期,秒
+}
+```
+
+- [ ] **Step 3: 重新生成pb.go文件**
+
+根据项目中的生成脚本生成go文件。
+
+---
+
+### Task 7: 实现AuthProvider的SendCode/VerifyCode
+
+**Files:**
+- Modify: `backend/services/userService/provider/auth_provider.go`
+
+- [ ] **Step 1: 添加SendCode和VerifyCode处理方法**
+
+```go
+// SendCode 发送验证码
+func (p *AuthProvider) SendCode(ctx context.Context, req *pb.SendCodeRequest) (*pb.SendCodeResponse, error) {
+ // 获取客户端IP
+ ip := getClientIP(ctx)
+
+ expiresIn, err := service.SendVerificationCode(ctx, req.Mobile, ip)
+ if err != nil {
+ return &pb.SendCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_BAD_REQUEST,
+ Message: err.Error(),
+ Timestamp: time.Now().UnixMilli(),
+ },
+ }, nil
+ }
+
+ return &pb.SendCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_OK,
+ Message: "发送成功",
+ Timestamp: time.Now().UnixMilli(),
+ },
+ ExpiresIn: int32(expiresIn),
+ }, nil
+}
+
+// VerifyCode 验证验证码
+func (p *AuthProvider) VerifyCode(ctx context.Context, req *pb.VerifyCodeRequest) (*pb.VerifyCodeResponse, error) {
+ token, err := service.VerifyCode(ctx, req.Mobile, req.Code)
+ if err != nil {
+ return &pb.VerifyCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_BAD_REQUEST,
+ Message: err.Error(),
+ Timestamp: time.Now().UnixMilli(),
+ },
+ Verified: false,
+ }, nil
+ }
+
+ return &pb.VerifyCodeResponse{
+ Base: &pbCommon.BaseResponse{
+ Code: pbCommon.StatusCode_STATUS_OK,
+ Message: "验证成功",
+ Timestamp: time.Now().UnixMilli(),
+ },
+ Verified: true,
+ VerifyToken: token,
+ ExpiresIn: 300,
+ }, nil
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add backend/services/userService/provider/auth_provider.go
+git commit -m "feat: implement SendCode and VerifyCode in AuthProvider"
+```
+
+---
+
+### Task 8: 网关层添加HTTP端点
+
+**Files:**
+- Modify: `backend/gateway/controller/auth_controller.go`
+- Modify: `backend/gateway/dto/auth_dto.go`
+- Modify: `backend/gateway/router/`(路由配置)
+
+- [ ] **Step 1: 添加DTO定义**
+
+```go
+// SendCodeRequest 发送验证码请求
+type SendCodeRequest struct {
+ Mobile string `json:"mobile" form:"mobile"`
+ Scene string `json:"scene" form:"scene"` // register, password
+}
+
+// SendCodeResponse 发送验证码响应
+type SendCodeResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ ExpiresIn int `json:"expires_in"`
+}
+
+// VerifyCodeRequest 验证验证码请求
+type VerifyCodeRequest struct {
+ Mobile string `json:"mobile" form:"mobile"`
+ Code string `json:"code" form:"code"`
+ Scene string `json:"scene" form:"scene"`
+}
+
+// VerifyCodeResponse 验证验证码响应
+type VerifyCodeResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Verified bool `json:"verified"`
+ VerifyToken string `json:"verify_token"`
+ ExpiresIn int `json:"expires_in"`
+}
+```
+
+- [ ] **Step 2: 添加Controller处理函数**
+
+在auth_controller.go中添加:
+
+```go
+// SendCode 发送验证码
+func SendCode(c *gin.Context) {
+ var req dto.SendCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(400, gin.H{"code": 400, "message": "参数错误"})
+ return
+ }
+
+ // 调用Dubbo服务
+ resp, err := dubboClient.SendCode(context.Background(), &userPB.SendCodeRequest{
+ Mobile: req.Mobile,
+ Scene: req.Scene,
+ })
+ if err != nil {
+ c.JSON(500, gin.H{"code": 500, "message": err.Error()})
+ return
+ }
+
+ c.JSON(200, gin.H{
+ "code": 200,
+ "message": resp.Base.Message,
+ "expires_in": resp.ExpiresIn,
+ })
+}
+
+// VerifyCode 验证验证码
+func VerifyCode(c *gin.Context) {
+ var req dto.VerifyCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(400, gin.H{"code": 400, "message": "参数错误"})
+ return
+ }
+
+ resp, err := dubboClient.VerifyCode(context.Background(), &userPB.VerifyCodeRequest{
+ Mobile: req.Mobile,
+ Code: req.Code,
+ Scene: req.Scene,
+ })
+ if err != nil {
+ c.JSON(500, gin.H{"code": 500, "message": err.Error()})
+ return
+ }
+
+ c.JSON(200, gin.H{
+ "code": 200,
+ "message": resp.Base.Message,
+ "verified": resp.Verified,
+ "verify_token": resp.VerifyToken,
+ "expires_in": resp.ExpiresIn,
+ })
+}
+```
+
+- [ ] **Step 3: 添加路由**
+
+在router中注册新路由:
+
+```go
+auth := r.Group("/api/v1/auth")
+{
+ auth.POST("/send-code", SendCode)
+ auth.POST("/verify-code", VerifyCode)
+}
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add backend/gateway/controller/auth_controller.go backend/gateway/dto/auth_dto.go
+git commit -m "feat: add send-code and verify-code HTTP endpoints in gateway"
+```
+
+---
+
+## 阶段二:前端改动
+
+### Task 9: 修改前端register.vue添加短信验证流程
+
+**Files:**
+- Modify: `frontend/pages/register/register.vue`
+
+- [ ] **Step 1: 添加验证码相关的响应式数据**
+
+```javascript
+const form = ref({
+ phone: '',
+ password: '',
+ code: '' // 新增:验证码
+});
+
+const codeStatus = ref('unsent'); // unsent, countdown, resend, verified
+const countdown = ref(60);
+const codeError = ref('');
+const verifyToken = ref(''); // 验证成功后获得的token
+const countdownTimer = ref(null);
+
+// 响应式数据
+const showCodeInput = ref(false); // 是否显示验证码输入框
+```
+
+- [ ] **Step 2: 添加发送验证码方法**
+
+```javascript
+// 发送验证码
+const handleSendCode = async () => {
+ const phoneValidation = validatePhone(form.value.phone);
+ if (!phoneValidation.valid) {
+ errorMessage.value = phoneValidation.message;
+ return;
+ }
+
+ try {
+ const res = await sendCodeApi(form.value.phone, 'register');
+ if (res.code === 200) {
+ // 开始倒计时
+ codeStatus.value = 'countdown';
+ countdown.value = res.expires_in || 60;
+ startCountdown();
+ uni.showToast({ title: '验证码已发送', icon: 'success' });
+ }
+ } catch (error) {
+ errorMessage.value = error.message || '发送失败,请重试';
+ }
+};
+
+// 倒计时
+const startCountdown = () => {
+ if (countdownTimer.value) {
+ clearInterval(countdownTimer.value);
+ }
+ countdownTimer.value = setInterval(() => {
+ countdown.value--;
+ if (countdown.value <= 0) {
+ clearInterval(countdownTimer.value);
+ codeStatus.value = 'resend';
+ }
+ }, 1000);
+};
+
+// 验证验证码
+const handleVerifyCode = async () => {
+ if (!form.value.code || form.value.code.length !== 6) {
+ codeError.value = '请输入6位验证码';
+ return;
+ }
+
+ try {
+ const res = await verifyCodeApi(form.value.phone, form.value.code, 'register');
+ if (res.code === 200 && res.data && res.data.verified) {
+ verifyToken.value = res.data.verify_token;
+ codeStatus.value = 'verified';
+ codeError.value = '';
+ uni.showToast({ title: '验证成功', icon: 'success' });
+ }
+ } catch (error) {
+ codeError.value = error.message || '验证失败';
+ }
+};
+```
+
+- [ ] **Step 3: 修改模板添加验证码UI**
+
+在手机号和密码输入框之间添加:
+
+```vue
+
+
+
+
+ 发送验证码
+ {{countdown}}秒
+ ✓ 已验证
+
+
+
+
+
+ {{codeError}}
+
+```
+
+- [ ] **Step 4: 修改handleRegister方法**
+
+```javascript
+const handleRegister = async () => {
+ // 检查是否已验证
+ if (codeStatus.value !== 'verified') {
+ errorMessage.value = '请先完成手机号验证';
+ return;
+ }
+
+ // ... 原有验证逻辑 ...
+
+ // 存储verify_token
+ uni.setStorageSync('temp_register_verify_token', verifyToken.value);
+
+ // 跳转到昵称设置页面
+ uni.reLaunch({
+ url: '/pages/profile/setNickname'
+ });
+};
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add frontend/pages/register/register.vue
+git commit -m "feat: add SMS verification flow to register page"
+```
+
+---
+
+### Task 10: 添加前端API方法
+
+**Files:**
+- Modify: `frontend/utils/api.js`
+
+- [ ] **Step 1: 添加sendCode和verifyCode API**
+
+```javascript
+// 发送验证码
+export function sendCodeApi(mobile, scene = 'register') {
+ return request({
+ url: '/api/v1/auth/send-code',
+ method: 'POST',
+ data: { mobile, scene }
+ })
+}
+
+// 验证验证码
+export function verifyCodeApi(mobile, code, scene = 'register') {
+ return request({
+ url: '/api/v1/auth/verify-code',
+ method: 'POST',
+ data: { mobile, code, scene }
+ })
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add frontend/utils/api.js
+git commit -m "feat: add send-code and verify-code API methods"
+```
+
+---
+
+## 阶段三:数据库与日志
+
+### Task 11: 创建短信发送日志表
+
+**Files:**
+- Create: `backend/migrations/xxxx_add_sms_send_log.sql`
+
+- [ ] **Step 1: 编写迁移SQL**
+
+```sql
+CREATE TABLE IF NOT EXISTS sms_send_log (
+ id BIGSERIAL PRIMARY KEY,
+ mobile VARCHAR(20) NOT NULL COMMENT '手机号(脱敏存储)',
+ scene VARCHAR(20) NOT NULL DEFAULT 'register' COMMENT '使用场景:register/password',
+ template_code VARCHAR(50) NOT NULL COMMENT '短信模板CODE',
+ sign_name VARCHAR(50) NOT NULL COMMENT '短信签名',
+ message_id VARCHAR(64) DEFAULT '' COMMENT '阿里云返回的MessageId',
+ response_code VARCHAR(20) DEFAULT '' COMMENT '阿里云返回状态码',
+ response_description VARCHAR(255) DEFAULT '' COMMENT '阿里云返回描述',
+ status SMALLINT NOT NULL DEFAULT 1 COMMENT '发送状态:0=失败,1=成功',
+ send_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sms_send_log_mobile ON sms_send_log(mobile);
+CREATE INDEX idx_sms_send_log_scene ON sms_send_log(scene);
+CREATE INDEX idx_sms_send_log_send_time ON sms_send_log(send_time);
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add backend/migrations/xxxx_add_sms_send_log.sql
+git commit -m "feat: add sms_send_log table for SMS sending records"
+```
+
+---
+
+## 阶段四:配置与部署
+
+### Task 12: 配置检查清单
+
+**Files:**
+- Modify: `deploy/envs/user.env`(如果存在)
+
+- [ ] **Step 1: 确认环境变量配置**
+
+```bash
+# 阿里云短信配置(直接使用 AccessKey,非 STS)
+SMS_ACCESS_KEY_ID=your_access_key_id
+SMS_ACCESS_KEY_SECRET=your_access_key_secret
+SMS_SIGN_NAME=TopFans
+SMS_TEMPLATE_CODE=SMS_xxxxxxx
+SMS_REGION=cn-hangzhou
+```
+
+- [ ] **Step 2: 添加Redis环境变量(如果尚未配置)**
+
+确保userService有Redis连接配置:
+
+```bash
+REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_PASSWORD=
+```
+
+---
+
+## 执行顺序
+
+1. **Task 1**: 添加SMS SDK依赖
+2. **Task 2**: 创建SMS配置
+3. **Task 3**: 创建Redis操作层
+4. **Task 4**: 创建SMS服务
+5. **Task 5**: AuthService集成verify_token校验
+6. **Task 6**: Proto定义
+7. **Task 7**: AuthProvider实现
+8. **Task 8**: 网关HTTP端点
+9. **Task 9**: 前端register.vue修改
+10. **Task 10**: 前端API方法
+11. **Task 11**: 数据库表
+12. **Task 12**: 配置检查
+
+---
+
+## 验证步骤
+
+### 后端验证
+
+```bash
+# 1. 启动userService
+cd backend/services/userService
+go run main.go
+
+# 2. 测试发送验证码
+curl -X POST http://localhost:8080/api/v1/auth/send-code \
+ -H "Content-Type: application/json" \
+ -d '{"mobile":"13800138000","scene":"register"}'
+
+# 3. 测试验证验证码
+curl -X POST http://localhost:8080/api/v1/auth/verify-code \
+ -H "Content-Type: application/json" \
+ -d '{"mobile":"13800138000","code":"123456","scene":"register"}'
+
+# 4. 测试注册(带verify_token)
+curl -X POST http://localhost:8080/api/v1/auth/register \
+ -H "Content-Type: application/json" \
+ -d '{"mobile":"13800138000","verify_token":"vtf_xxx","password":"xxx","nickname":"xxx","star_id":1}'
+```
+
+### 前端验证
+
+```bash
+# 1. 启动前端
+cd frontend
+npm run dev
+
+# 2. 访问注册页面 http://localhost:5173/pages/register/register
+# 3. 输入手机号,点击"发送验证码"
+# 4. 输入验证码,点击"验证"
+# 5. 验证成功后输入密码,点击"注册"
+```
+
+---
+
+## 注意事项
+
+1. **验证码日志脱敏**:日志中禁止记录明文验证码,只能记录手机号和发送状态
+2. **verify_token安全**:token有效期5分钟,只能使用一次,验证后立即删除
+3. **防暴力破解**:验证失败3次后强制删除验证码,要求用户重新获取
+4. **限流保护**:从手机号和IP两个维度限制请求频率
+5. **IP黑名单**:1小时内触发3次限流的IP,临时拉黑30分钟
\ No newline at end of file
diff --git a/frontend/pages/profile/setNickname.vue b/frontend/pages/profile/setNickname.vue
index 33d0081..58723db 100644
--- a/frontend/pages/profile/setNickname.vue
+++ b/frontend/pages/profile/setNickname.vue
@@ -139,6 +139,7 @@ const handleNext = async () => {
const mobile = uni.getStorageSync('temp_register_mobile');
const password = uni.getStorageSync('temp_register_password');
const star_id = 87; // 默认身份
+ const verify_token = uni.getStorageSync('temp_register_verify_token') || '';
// 验证数据完整性
if (!mobile || !password || !trimmedNickname || !star_id) {
@@ -149,6 +150,7 @@ const handleNext = async () => {
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
+ uni.removeStorageSync('temp_register_verify_token');
setTimeout(() => {
uni.reLaunch({
url: '/pages/register/register'
@@ -169,7 +171,8 @@ const handleNext = async () => {
mobile,
password,
star_id,
- nickname: trimmedNickname
+ nickname: trimmedNickname,
+ verify_token
});
uni.hideLoading();
@@ -178,6 +181,7 @@ const handleNext = async () => {
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
+ uni.removeStorageSync('temp_register_verify_token');
// 设置新用户标记
uni.setStorageSync('is_new_user', true);
diff --git a/frontend/pages/register/register.vue b/frontend/pages/register/register.vue
index d2dccb3..1393f19 100644
--- a/frontend/pages/register/register.vue
+++ b/frontend/pages/register/register.vue
@@ -29,7 +29,45 @@
@input="handlePhoneInput"
/>
-
+
+
+
+
+
+ 发送验证码
+ {{countdown}}秒
+ 已验证
+
+
+
+
+
+ {{codeError}}
+
+
+
+
+
+
+
{
@@ -175,6 +221,65 @@ const checkPhoneDuplicate = async (phone) => {
}, 300);
};
+// 发送验证码
+const handleSendCode = async () => {
+ const phoneValidation = validatePhone(form.value.phone);
+ if (!phoneValidation.valid) {
+ errorMessage.value = phoneValidation.message;
+ return;
+ }
+
+ try {
+ const res = await sendCodeApi(form.value.phone, 'register');
+ if (res.code === 200) {
+ codeStatus.value = 'countdown';
+ countdown.value = res.expires_in || 60;
+ showCodeInput.value = true;
+ startCountdown();
+ uni.showToast({ title: '验证码已发送', icon: 'success' });
+ }
+ } catch (error) {
+ codeError.value = error.message || '发送失败,请重试';
+ }
+};
+
+// 倒计时
+const startCountdown = () => {
+ if (countdownTimer.value) {
+ clearInterval(countdownTimer.value);
+ }
+ countdownTimer.value = setInterval(() => {
+ countdown.value--;
+ if (countdown.value <= 0) {
+ clearInterval(countdownTimer.value);
+ codeStatus.value = 'resend';
+ }
+ }, 1000);
+};
+
+// 验证验证码
+const handleVerifyCode = async () => {
+ if (!form.value.code || form.value.code.length !== 6) {
+ codeError.value = '请输入6位验证码';
+ return;
+ }
+
+ isVerifying.value = true;
+ codeError.value = '';
+ try {
+ const res = await verifyCodeApi(form.value.phone, form.value.code, 'register');
+ if (res.code === 200 && res.data && res.data.verified) {
+ verifyToken.value = res.data.verify_token;
+ codeStatus.value = 'verified';
+ uni.showToast({ title: '验证成功', icon: 'success' });
+ }
+ } catch (error) {
+ codeError.value = error.message || '验证失败';
+ } finally {
+ isVerifying.value = false;
+ }
+};
+
// 跳转到登录页面
const goToLogin = () => {
const pages = getCurrentPages();
@@ -241,7 +346,13 @@ const handleRegister = async () => {
openTipDialog();
return;
}
-
+
+ // 检查是否已验证(仅当显示了验证码输入框时)
+ if (showCodeInput.value && codeStatus.value !== 'verified') {
+ errorMessage.value = '请先完成手机号验证';
+ return;
+ }
+
// 验证表单
const phoneValidation = validatePhone(form.value.phone);
if (!phoneValidation.valid) {
@@ -252,7 +363,7 @@ const handleRegister = async () => {
});
return;
}
-
+
const passwordValidation = validatePassword(form.value.password);
if (!passwordValidation.valid) {
errorMessage.value = passwordValidation.message;
@@ -262,14 +373,17 @@ const handleRegister = async () => {
});
return;
}
-
+
errorMessage.value = '';
-
+
try {
// 暂存手机号和密码到临时存储,供后续注册流程使用
uni.setStorageSync('temp_register_mobile', form.value.phone);
uni.setStorageSync('temp_register_password', form.value.password);
-
+
+ // 存储verify_token
+ uni.setStorageSync('temp_register_verify_token', verifyToken.value);
+
// 跳转到昵称设置页面
uni.reLaunch({
url: '/pages/profile/setNickname'
@@ -410,6 +524,54 @@ const handleRegister = async () => {
padding-right: 100rpx;
}
+.code-wrapper {
+ flex-direction: row;
+ align-items: center;
+}
+
+.code-wrapper .input-field {
+ flex: 1;
+}
+
+.send-code-btn {
+ padding: 20rpx 30rpx;
+ background: #666;
+ border-radius: 40rpx;
+ color: #fff;
+ font-size: 24rpx;
+ white-space: nowrap;
+}
+
+.send-code-btn.countdown {
+ background: #999;
+}
+
+.send-code-btn.verified {
+ background: #4CAF50;
+}
+
+.verify-btn-wrapper {
+ margin-bottom: 30rpx;
+}
+
+.btn-secondary {
+ width: 100%;
+ height: 88rpx;
+ background: #fff;
+ border: 2rpx solid #B94E73;
+ border-radius: 50rpx;
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #B94E73;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.btn-secondary::after {
+ border: none;
+}
+
.input-field {
flex: 1;
height: 100%;
diff --git a/frontend/store/modules/user.js b/frontend/store/modules/user.js
index 0e83320..cec3a2d 100644
--- a/frontend/store/modules/user.js
+++ b/frontend/store/modules/user.js
@@ -119,9 +119,9 @@ const actions = {
},
// 注册
- async register({ commit }, { mobile, password, star_id, nickname }) {
+ async register({ commit }, { mobile, password, star_id, nickname, verify_token = '' }) {
try {
- const res = await registerApi(mobile, password, star_id, nickname)
+ const res = await registerApi(mobile, password, star_id, nickname, verify_token)
if (res.code === 200 && res.data) {
// 缓存 access_token
const accessToken = res.data.access_token
diff --git a/frontend/utils/api.js b/frontend/utils/api.js
index bcd9cac..6081e5b 100644
--- a/frontend/utils/api.js
+++ b/frontend/utils/api.js
@@ -48,7 +48,7 @@ export function request(options) {
// 判断是否为登录或注册接口
const isAuthRequest = options.url.includes('/api/v1/auth/login') || options.url.includes(
- '/api/v1/auth/register')
+ '/api/v1/auth/register') || options.url.includes('/api/v1/auth/send-code') || options.url.includes('/api/v1/auth/verify-code')
// 如果不是登录/注册接口,则自动添加JWT token
if (!isAuthRequest) {
@@ -156,7 +156,7 @@ export function checkmobileApi(mobile) {
}
// 注册接口
-export function registerApi(mobile, password, star_id, nickname) {
+export function registerApi(mobile, password, star_id, nickname, verify_token = '') {
return request({
url: '/api/v1/auth/register',
method: 'POST',
@@ -164,11 +164,37 @@ export function registerApi(mobile, password, star_id, nickname) {
mobile,
password,
star_id,
- nickname
+ nickname,
+ verify_token
}
})
}
+// 发送验证码
+export function sendCodeApi(mobile, scene = 'register') {
+ return request({
+ url: '/api/v1/auth/send-code',
+ method: 'POST',
+ data: {
+ mobile,
+ scene
+ }
+ });
+}
+
+// 验证验证码
+export function verifyCodeApi(mobile, code, scene = 'register') {
+ return request({
+ url: '/api/v1/auth/verify-code',
+ method: 'POST',
+ data: {
+ mobile,
+ code,
+ scene
+ }
+ });
+}
+
// 更新用户信息接口
export function updateUserInfoApi(nickname) {