添加阿里云短信服务支持

This commit is contained in:
2025-04-18 17:47:54 +08:00
parent a7e59fb1d7
commit 999d0b0a1d
8 changed files with 234 additions and 27 deletions

30
web/globals/aliyun.go Normal file
View File

@@ -0,0 +1,30 @@
package globals
import (
"platform/pkg/env"
"platform/pkg/u"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
sms "github.com/alibabacloud-go/dysmsapi-20170525/v4/client"
)
var Aliyun *aliyunClient
type aliyunClient struct {
Sms *sms.Client
}
func InitAliyun() {
client, err := sms.NewClient(&openapi.Config{
AccessKeyId: &env.AliyunAccessKey,
AccessKeySecret: &env.AliyunAccessKeySecret,
Endpoint: u.P("dysmsapi.aliyuncs.com"),
})
if err != nil {
panic(err)
}
Aliyun = &aliyunClient{
Sms: client,
}
}

View File

@@ -2,9 +2,10 @@ package handlers
import (
"errors"
"platform/web/models"
"platform/web/auth"
m "platform/web/models"
q "platform/web/queries"
"platform/web/services"
s "platform/web/services"
"time"
"github.com/gofiber/fiber/v2"
@@ -18,11 +19,11 @@ type LoginReq struct {
}
type LoginResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Expires int64 `json:"expires"`
Auth services.AuthContext `json:"auth"`
Profile *models.User `json:"profile"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Expires int64 `json:"expires"`
Auth s.AuthContext `json:"auth"`
Profile *m.User `json:"profile"`
}
func Login(c *fiber.Ctx) error {
@@ -45,16 +46,16 @@ func Login(c *fiber.Ctx) error {
func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
// 验证验证码
err := services.Verifier.VerifySms(c.Context(), req.Username, req.Password)
err := s.Verifier.VerifySms(c.Context(), req.Username, req.Password)
if err != nil {
if errors.Is(err, services.ErrVerifierServiceInvalid) {
if errors.Is(err, s.ErrVerifierServiceInvalid) {
return fiber.NewError(fiber.StatusBadRequest, "验证码错误")
}
return err
}
// 查找用户 todo 获取权限信息
var user *models.User
var user *m.User
err = q.Q.Transaction(func(tx *q.Query) error {
user, err = tx.User.
Where(tx.User.Phone.Eq(req.Username)).
@@ -65,7 +66,7 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
// 如果用户不存在,初始化用户 todo 保存默认权限信息
if user == nil {
user = &models.User{
user = &m.User{
Phone: req.Username,
Username: req.Username,
}
@@ -86,13 +87,13 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
}
// 保存到会话
auth := services.AuthContext{
auth := s.AuthContext{
Permissions: map[string]struct{}{
"user": {},
},
Payload: services.Payload{
Payload: s.Payload{
Id: user.ID,
Type: services.PayloadUser,
Type: s.PayloadUser,
Name: user.Name,
Avatar: user.Avatar,
},
@@ -101,7 +102,7 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
if req.Remember {
duration *= 7
}
token, err := services.Session.Create(c.Context(), auth)
token, err := s.Session.Create(c.Context(), auth)
if err != nil {
return err
}
@@ -115,3 +116,30 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
Profile: user,
})
}
type LogoutReq struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func Logout(c *fiber.Ctx) error {
_, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
if err != nil {
// 用户未登录
return nil
}
// 解析请求参数
req := new(LogoutReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 删除会话
err = s.Session.Remove(c.Context(), req.AccessToken, req.RefreshToken)
if err != nil {
return err
}
return nil
}

View File

@@ -14,6 +14,7 @@ func ApplyRouters(app *fiber.App) {
auth := api.Group("/auth")
auth.Post("/verify/sms", auth2.PermitDevice(), handlers.SmsCode)
auth.Post("/login/sms", auth2.PermitDevice(), handlers.Login)
auth.Post("/logout", handlers.Logout)
auth.Post("/token", handlers.Token)
// 通道

View File

@@ -2,14 +2,19 @@ package services
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"math/rand"
"platform/pkg/env"
"platform/pkg/rds"
"platform/pkg/u"
g "platform/web/globals"
"strconv"
"time"
"github.com/alibabacloud-go/dysmsapi-20170525/v4/client"
"github.com/redis/go-redis/v9"
)
@@ -44,9 +49,6 @@ func (s *verifierService) SendSms(ctx context.Context, phone string, purpose Ver
key := smsKey(phone, purpose)
keyLock := key + ":lock"
// 生成验证码
code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999
// 检查发送频率1 分钟内只能发送一次
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
result, err := tx.TTL(ctx, keyLock).Result()
@@ -61,7 +63,6 @@ func (s *verifierService) SendSms(ctx context.Context, phone string, purpose Ver
}
pipe := rds.Client.Pipeline()
pipe.Set(ctx, key, code, 10*time.Minute)
pipe.Set(ctx, keyLock, "", 1*time.Minute)
_, err = pipe.Exec(ctx)
if err != nil {
@@ -73,9 +74,39 @@ func (s *verifierService) SendSms(ctx context.Context, phone string, purpose Ver
return err
}
// TODO: 发送短信验证码
slog.Debug("发送验证码", slog.String("phone", phone), slog.String("code", strconv.Itoa(code)))
// 生成验证码
code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999
// 发送短信验证码
params, err := json.Marshal(map[string]string{
"code": strconv.Itoa(code),
})
if err != nil {
return err
}
response, err := g.Aliyun.Sms.SendSms(&client.SendSmsRequest{
PhoneNumbers: &phone,
SignName: &env.AliyunSmsSignature,
TemplateCode: &env.AliyunSmsTemplateLogin,
TemplateParam: u.P(string(params)),
})
if err != nil {
_ = rds.Client.Del(ctx, key, keyLock).Err()
return err
}
if response.Body.Code == nil || *response.Body.Code != "OK" {
_ = rds.Client.Del(ctx, key, keyLock).Err()
return VerifierServiceError("验证码发送失败")
}
// 设置验证码
err = rds.Client.Set(ctx, key, code, 5*time.Minute).Err()
if err != nil {
_ = rds.Client.Del(ctx, key, keyLock).Err()
return err
}
slog.Debug("发送验证码", slog.String("phone", phone), slog.String("code", strconv.Itoa(code)))
return nil
}

View File

@@ -43,6 +43,7 @@ func (s *Server) Run() error {
g.InitBaiyin()
g.InitAlipay()
// g.InitWechatPay()
g.InitAliyun()
// config
s.fiber = fiber.New(fiber.Config{