Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/baseService/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.2

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240508200655-46a4cf4ba109.2
github.com/TremblingV5/box v0.0.7
github.com/bufbuild/protovalidate-go v0.6.3
github.com/bwmarrin/snowflake v0.3.0
github.com/bytedance/sonic v1.12.3
Expand All @@ -29,7 +30,6 @@ require (

require (
dario.cat/mergo v1.0.0 // indirect
github.com/TremblingV5/box v0.0.7 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/apache/rocketmq-client-go/v2 v2.1.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func (s *Service) checkAccountUnique(ctx context.Context, account *account.Accou
}

func (s *Service) Create(ctx context.Context, mobile, email, password string) (int64, error) {
if mobile == "" && email == "" {
return 0, errors.New("mobile or email is required")
}

account := account.NewAccount(
account.WithMobile(mobile),
account.WithEmail(email),
Expand Down Expand Up @@ -177,12 +181,30 @@ func (s *Service) Bind(ctx context.Context, id int64, voucherType api.VoucherTyp
return errors.New("账户id不能为空")
}

if voucher == "" {
return errors.New("绑定值不能为空")
}

var column field.Expr
switch voucherType {
case api.VoucherType_VOUCHER_EMAIL:
column = query.Q.Account.Email
case api.VoucherType_VOUCHER_PHONE:
exist, err := s.account.IsMobileExist(ctx, voucher)
if err != nil {
return err
}
if exist {
return errors.New("该手机号已被其他账户绑定")
}
column = query.Q.Account.Mobile
case api.VoucherType_VOUCHER_EMAIL:
exist, err := s.account.IsEmailExist(ctx, voucher)
if err != nil {
return err
}
if exist {
return errors.New("该邮箱已被其他账户绑定")
}
column = query.Q.Account.Email
default:
return errors.New("不支持的类型")
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (r *PersistRepository) IsEmailExist(ctx context.Context, email string) (boo

func (r *PersistRepository) ClearColumn(ctx context.Context, column field.Expr) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
_, err := tx.WithContext(ctx).Account.Update(column, nil)
_, err := tx.WithContext(ctx).Account.Update(column, "")
return err
})
}
Expand Down
6 changes: 3 additions & 3 deletions backend/shortVideoApiService/api/svapi/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ message RegisterRequest {
string mobile = 1;
string email = 2;
string password = 3 [
(buf.validate.field).string.min_len = 6,
(buf.validate.field).string.min_len = 8,
(buf.validate.field).string.max_len = 50
];
// @gotags: json:"code_id,omitempty,string"
Expand All @@ -111,8 +111,8 @@ message RegisterResponse {
}

message LoginRequest {
string mobile = 1 [(buf.validate.field).string.pattern = "^\\+?[1-9]\\d{1,14}$"];
string email = 2 [(buf.validate.field).string.email = true];
string mobile = 1;
string email = 2;
string password = 3 [
(buf.validate.field).string.min_len = 8,
(buf.validate.field).string.max_len = 50
Expand Down
3 changes: 2 additions & 1 deletion backend/shortVideoApiService/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.6.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/samber/lo v1.46.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
Expand Down Expand Up @@ -111,3 +110,5 @@ require (
)

replace github.com/cloudzenith/DouTok/backend/gopkgs v0.0.9 => github.com/cloudzenith/DouTok/backend/gopkgs v0.0.0-20241103032449-fe0152ac484a

replace github.com/cloudzenith/DouTok/backend/baseService v0.0.1 => ../baseService
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package userapp
import (
"context"
"fmt"
baseapi "github.com/cloudzenith/DouTok/backend/baseService/api"
"github.com/cloudzenith/DouTok/backend/shortVideoApiService/api/svapi"
"github.com/cloudzenith/DouTok/backend/shortVideoApiService/internal/infrastructure/adapter/baseadapter"
"github.com/cloudzenith/DouTok/backend/shortVideoApiService/internal/infrastructure/adapter/baseadapter/accountoptions"
Expand Down Expand Up @@ -114,6 +115,16 @@ func (a *Application) setToken2Header(ctx context.Context, claim *claims.Claims)
}

func (a *Application) Login(ctx context.Context, request *svapi.LoginRequest) (*svapi.LoginResponse, error) {
if request.GetMobile() == "" && request.GetEmail() == "" {
return nil, errorx.New(1, "mobile or email is required")
}
if request.GetMobile() != "" && !isValidMobile(request.GetMobile()) {
return nil, errorx.New(1, "invalid mobile format")
}
if request.GetEmail() != "" && !isValidEmail(request.GetEmail()) {
return nil, errorx.New(1, "invalid email format")
}

accountId, err := a.base.CheckAccount(
ctx,
accountoptions.CheckAccountWithMobile(request.GetMobile()),
Expand All @@ -130,7 +141,7 @@ func (a *Application) Login(ctx context.Context, request *svapi.LoginRequest) (*
return nil, errorx.New(1, "failed to get user info")
}

token, err := a.setToken2Header(ctx, claims.New(user.Id))
token, err := a.setToken2Header(ctx, claims.NewWithAccount(user.Id, accountId))
if err != nil {
log.Context(ctx).Error("failed to generate token: %v", err)
return nil, errorx.New(1, "failed to generate token")
Expand All @@ -141,6 +152,16 @@ func (a *Application) Login(ctx context.Context, request *svapi.LoginRequest) (*
}

func (a *Application) Register(ctx context.Context, request *svapi.RegisterRequest) (*svapi.RegisterResponse, error) {
if request.Mobile == "" && request.Email == "" {
return nil, errorx.New(1, "mobile or email is required")
}
if request.Mobile != "" && !isValidMobile(request.Mobile) {
return nil, errorx.New(1, "invalid mobile format")
}
if request.Email != "" && !isValidEmail(request.Email) {
return nil, errorx.New(1, "invalid email format")
}

if err := a.base.ValidateVerificationCode(ctx, request.CodeId, request.Code); err != nil {
return nil, errorx.New(1, "invalid verification code")
}
Expand Down Expand Up @@ -198,13 +219,59 @@ func (a *Application) UpdateUserInfo(ctx context.Context, request *svapi.UpdateU
}

func (a *Application) BindUserVoucher(ctx context.Context, request *svapi.BindUserVoucherRequest) (*svapi.BindUserVoucherResponse, error) {
//TODO implement me
panic("implement me")
accountId, err := claims.GetAccountId(ctx)
if err != nil {
return nil, errorx.New(1, "failed to get account id from token")
}

if request.Voucher == "" {
return nil, errorx.New(1, "voucher value is required")
}

var vt baseapi.VoucherType
switch request.VoucherType {
case svapi.VoucherType_PHONE:
if !isValidMobile(request.Voucher) {
return nil, errorx.New(1, "invalid mobile format")
}
vt = baseapi.VoucherType_VOUCHER_PHONE
case svapi.VoucherType_EMAIL:
if !isValidEmail(request.Voucher) {
return nil, errorx.New(1, "invalid email format")
}
vt = baseapi.VoucherType_VOUCHER_EMAIL
default:
return nil, errorx.New(1, "unsupported voucher type")
}

if err := a.base.Bind(ctx, accountId, vt, request.Voucher); err != nil {
log.Context(ctx).Errorf("failed to bind voucher: %v", err)
return nil, errorx.New(1, "failed to bind voucher")
}
return &svapi.BindUserVoucherResponse{}, nil
}

func (a *Application) UnbindUserVoucher(ctx context.Context, request *svapi.UnbindUserVoucherRequest) (*svapi.UnbindUserVoucherResponse, error) {
//TODO implement me
panic("implement me")
accountId, err := claims.GetAccountId(ctx)
if err != nil {
return nil, errorx.New(1, "failed to get account id from token")
}

var vt baseapi.VoucherType
switch request.VoucherType {
case svapi.VoucherType_PHONE:
vt = baseapi.VoucherType_VOUCHER_PHONE
case svapi.VoucherType_EMAIL:
vt = baseapi.VoucherType_VOUCHER_EMAIL
default:
return nil, errorx.New(1, "unsupported voucher type")
}

if err := a.base.Unbind(ctx, accountId, vt); err != nil {
log.Context(ctx).Errorf("failed to unbind voucher: %v", err)
return nil, errorx.New(1, "failed to unbind voucher")
}
return &svapi.UnbindUserVoucherResponse{}, nil
}

var _ svapi.UserServiceHTTPServer = (*Application)(nil)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package userapp

import (
"net/mail"
"regexp"
)

var mobilePattern = regexp.MustCompile(`^\+?[1-9]\d{1,14}$`)

func isValidMobile(mobile string) bool {
return mobilePattern.MatchString(mobile)
}

func isValidEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,20 @@ func (a *Adapter) CheckAccount(ctx context.Context, options ...accountoptions.Ch
},
)
}

func (a *Adapter) Bind(ctx context.Context, accountId int64, voucherType api.VoucherType, voucher string) error {
resp, err := a.account.Bind(ctx, &api.BindRequest{
AccountId: accountId,
VoucherType: voucherType,
Voucher: voucher,
})
return respcheck.Check[*api.Metadata](resp, err)
}

func (a *Adapter) Unbind(ctx context.Context, accountId int64, voucherType api.VoucherType) error {
resp, err := a.account.Unbind(ctx, &api.UnbindRequest{
AccountId: accountId,
VoucherType: voucherType,
})
return respcheck.Check[*api.Metadata](resp, err)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (

type Claims struct {
jwt5.RegisteredClaims
UserId int64 `json:"user_id"`
UserId int64 `json:"user_id"`
AccountId int64 `json:"account_id,omitempty"`
}

func New(userId int64) *Claims {
Expand All @@ -19,6 +20,13 @@ func New(userId int64) *Claims {
}
}

func NewWithAccount(userId, accountId int64) *Claims {
return &Claims{
UserId: userId,
AccountId: accountId,
}
}

func GetUserId(ctx context.Context) (int64, error) {
anyClaims, ok := jwt.FromContext(ctx)
if !ok {
Expand All @@ -33,6 +41,24 @@ func GetUserId(ctx context.Context) (int64, error) {
return claims.UserId, nil
}

func GetAccountId(ctx context.Context) (int64, error) {
anyClaims, ok := jwt.FromContext(ctx)
if !ok {
return 0, errors.New("no claims in context")
}

claims, ok := anyClaims.(*Claims)
if !ok {
return 0, errors.New("claims type error")
}

if claims.AccountId == 0 {
return 0, errors.New("account_id not found in token, please re-login")
}

return claims.AccountId, nil
}

func GenerateToken(claim *Claims) (string, error) {
token := jwt5.NewWithClaims(jwt5.SigningMethodHS256, claim)
tokenString, err := token.SignedString([]byte("token"))
Expand Down
41 changes: 41 additions & 0 deletions sql/20260527000001_v0_3_0_auth_upgrade.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- +goose Up

-- ============================================================
-- v0.3.0 Auth Upgrade: 支持仅手机号 / 仅邮箱 / 混合注册
-- ============================================================
--
-- 迁移前请先执行以下检查 SQL,确认无重复数据:
--
-- SELECT mobile, COUNT(*) AS cnt
-- FROM account
-- WHERE mobile != '' AND is_deleted = 0
-- GROUP BY mobile HAVING cnt > 1;
--
-- SELECT email, COUNT(*) AS cnt
-- FROM account
-- WHERE email != '' AND is_deleted = 0
-- GROUP BY email HAVING cnt > 1;
--
-- 如有重复行,请先手动处理后再执行迁移。
-- ============================================================

-- 使用 GENERATED COLUMN + UNIQUE INDEX 实现条件唯一约束:
-- mobile/email 为空串时 → 生成列值为 NULL → UNIQUE 允许多个 NULL
-- mobile/email 非空时 → 生成列保留原值 → UNIQUE 约束生效

-- +goose StatementBegin
ALTER TABLE account
ADD COLUMN `mobile_unique` VARCHAR(20) GENERATED ALWAYS AS (NULLIF(mobile, '')) STORED AFTER `mobile`,
ADD COLUMN `email_unique` VARCHAR(100) GENERATED ALWAYS AS (NULLIF(email, '')) STORED AFTER `email`,
ADD UNIQUE INDEX `uk_account_mobile` (`mobile_unique`),
ADD UNIQUE INDEX `uk_account_email` (`email_unique`);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
ALTER TABLE account
DROP INDEX `uk_account_email`,
DROP INDEX `uk_account_mobile`,
DROP COLUMN `email_unique`,
DROP COLUMN `mobile_unique`;
-- +goose StatementEnd