浏览代码

feat: add 绑定手机号码

2637309949 4 年之前
父节点
当前提交
46117734ea

+ 32 - 18
i2bill.api

@@ -21,16 +21,17 @@ type Watermark {
 }
 
 type WXUserInfo {
-	OpenID    string    `json:"openId,omitempty"`
-	NickName  string    `json:"nickName"`
-	AvatarUrl string    `json:"avatarUrl"`
-	Gender    int64     `json:"gender"`
-	Country   string    `json:"country"`
-	Province  string    `json:"province"`
-	City      string    `json:"city"`
-	UnionID   string    `json:"unionId,omitempty"`
-	Language  string    `json:"language"`
-	Watermark Watermark `json:"watermark,omitempty"`
+	OpenID     string    `json:"openId,omitempty"`
+	SessionKey string    `json:"SessionKey"`
+	NickName   string    `json:"nickName"`
+	AvatarUrl  string    `json:"avatarUrl"`
+	Gender     int64     `json:"gender"`
+	Country    string    `json:"country"`
+	Province   string    `json:"province"`
+	City       string    `json:"city"`
+	UnionID    string    `json:"unionId,omitempty"`
+	Language   string    `json:"language"`
+	Watermark  Watermark `json:"watermark,omitempty"`
 }
 
 type ResUserInfo {
@@ -41,7 +42,7 @@ type ResUserInfo {
 	IV            string     `json:"iv"`
 }
 
-type authLoginBody {
+type loginByWeixinRequest {
 	Code string `json:"code"`
 	// UserInfo ResUserInfo `json:"userInfo"`
 }
@@ -57,7 +58,7 @@ type AuthUserInfo {
 	Birthday int64  `json:"birthday"`
 }
 
-type authResponse {
+type loginByWeixinResponse {
 	Token    string       `json:"token"`
 	UserInfo AuthUserInfo `json:"userInfo"`
 }
@@ -67,6 +68,15 @@ type jzQrcodeResponse {
 	FileId string `json:"fileId"`
 }
 
+type bindMobileRequest {
+	Iv            string `json:"iv"`
+	EncryptedData string `json:"encryptedData"`
+}
+
+type bindMobileResponse {
+	Id int64 `json:"id"`
+}
+
 type request {
 	// TODO: add members here and delete this comment
 	Id int64 `form:"id"`
@@ -78,18 +88,22 @@ type response {
 }
 
 service i2bill-api {
-	@handler Hello // TODO
+	@handler Hello
 	get /api/hello
-	@handler LoginByWeixin // TODO
-	post /api/auth/loginByWeixin(authLoginBody) returns(authResponse)
+	@handler LoginByWeixin
+	post /api/auth/loginByWeixin(loginByWeixinRequest) returns(loginByWeixinResponse)
 }
 
 @server(
 	jwt: JwtAuth
 )
 service i2bill-api {
-	@handler JzQrCode // TODO: set handler name and delete this comment
-	get /api/users/share/jz_qrcode returns(jzQrcodeResponse)
-	@handler GetUser // TODO: set handler name and delete this comment
+	@handler JzQrCode
+	get /api/users/share/jzQrcode returns(jzQrcodeResponse)
+	
+	@handler BindMobile
+	post /api/users/bindMobile(bindMobileRequest) returns(bindMobileResponse)
+	
+	@handler GetUser
 	get /api/users/id/:userId(request) returns(response)
 }

+ 41 - 0
internal/handler/bindmobilehandler.go

@@ -0,0 +1,41 @@
+package handler
+
+import (
+	"net/http"
+
+	"git.i2edu.net/i2/i2-bill-api/internal/logic"
+	"git.i2edu.net/i2/i2-bill-api/internal/svc"
+	"git.i2edu.net/i2/i2-bill-api/internal/types"
+	"git.i2edu.net/i2/i2-bill-api/internal/utils"
+
+	"git.i2edu.net/i2/go-zero/rest/httpx"
+)
+
+func BindMobileHandler(ctx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.BindMobileRequest
+		if err := httpx.Parse(r, &req); err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		uid, err := ctx.GetUserId(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		sid, err := ctx.GetSessionKey(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		l := logic.NewBindMobileLogic(r.Context(), ctx)
+		l.UserId = uid
+		l.SessionKey = sid
+		resp, err := l.BindMobile(req)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+		} else {
+			httpx.OkJson(w, utils.ReturnHTTPSuccess(resp))
+		}
+	}
+}

+ 16 - 13
internal/handler/getuserhandler.go

@@ -2,37 +2,40 @@ package handler
 
 import (
 	"net/http"
-	"time"
 
 	"git.i2edu.net/i2/i2-bill-api/internal/logic"
 	"git.i2edu.net/i2/i2-bill-api/internal/svc"
 	"git.i2edu.net/i2/i2-bill-api/internal/types"
-	"github.com/dgrijalva/jwt-go"
+	"git.i2edu.net/i2/i2-bill-api/internal/utils"
 
-	"git.i2edu.net/i2/go-zero/core/logx"
 	"git.i2edu.net/i2/go-zero/rest/httpx"
-	"git.i2edu.net/i2/go-zero/rest/token"
 )
 
 func GetUserHandler(ctx *svc.ServiceContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var req types.Request
 		if err := httpx.Parse(r, &req); err != nil {
-			httpx.OkJson(w, err)
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		uid, err := ctx.GetUserId(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		sid, err := ctx.GetSessionKey(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
 			return
 		}
-		token.NewTokenParser()
-		parser := token.NewTokenParser(token.WithResetDuration(time.Minute))
-		tok, err := parser.ParseToken(r, ctx.Config.JwtAuth.AccessSecret, "")
-		// user_id
-		logx.Info(tok.Claims.(jwt.MapClaims)["user_id"])
-		logx.Info(err)
 		l := logic.NewGetUserLogic(r.Context(), ctx)
+		l.UserId = uid
+		l.SessionKey = sid
 		resp, err := l.GetUser(req)
 		if err != nil {
-			httpx.Error(w, err)
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
 		} else {
-			httpx.OkJson(w, resp)
+			httpx.OkJson(w, utils.ReturnHTTPSuccess(resp))
 		}
 	}
 }

+ 14 - 12
internal/handler/jzqrcodehandler.go

@@ -2,31 +2,33 @@ package handler
 
 import (
 	"net/http"
-	"time"
 
 	"git.i2edu.net/i2/go-zero/rest/httpx"
-	"git.i2edu.net/i2/go-zero/rest/token"
 	"git.i2edu.net/i2/i2-bill-api/internal/logic"
 	"git.i2edu.net/i2/i2-bill-api/internal/svc"
 	"git.i2edu.net/i2/i2-bill-api/internal/utils"
-	"github.com/dgrijalva/jwt-go"
 )
 
 func JzQrCodeHandler(ctx *svc.ServiceContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
+		uid, err := ctx.GetUserId(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
+		sid, err := ctx.GetSessionKey(r)
+		if err != nil {
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
+			return
+		}
 		l := logic.NewJzQrCodeLogic(r.Context(), ctx)
-		token.NewTokenParser()
-		parser := token.NewTokenParser(token.WithResetDuration(time.Minute))
-		tok, err := parser.ParseToken(r, ctx.Config.JwtAuth.AccessSecret, "")
+		l.UserId = uid
+		l.SessionKey = sid
+		resp, err := l.JzQrCode()
 		if err != nil {
 			httpx.OkJson(w, utils.ReturnHTTPFail(err))
 		} else {
-			res, err := l.JzQrCode(tok.Claims.(jwt.MapClaims)["userId"].(int64))
-			if err != nil {
-				httpx.OkJson(w, utils.ReturnHTTPFail(err))
-			} else {
-				httpx.OkJson(w, utils.ReturnHTTPSuccess(res))
-			}
+			httpx.OkJson(w, utils.ReturnHTTPSuccess(resp))
 		}
 	}
 }

+ 4 - 4
internal/handler/loginbyweixinhandler.go

@@ -12,17 +12,17 @@ import (
 
 func LoginByWeixinHandler(ctx *svc.ServiceContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
-		var req types.AuthLoginBody
+		var req types.LoginByWeixinRequest
 		if err := httpx.Parse(r, &req); err != nil {
-			httpx.Error(w, err)
+			httpx.OkJson(w, utils.ReturnHTTPFail(err))
 			return
 		}
 		l := logic.NewLoginByWeixinLogic(r.Context(), ctx)
-		res, err := l.LoginByWeixin(req)
+		resp, err := l.LoginByWeixin(req)
 		if err != nil {
 			httpx.OkJson(w, utils.ReturnHTTPFail(err))
 		} else {
-			httpx.OkJson(w, utils.ReturnHTTPSuccess(res))
+			httpx.OkJson(w, utils.ReturnHTTPSuccess(resp))
 		}
 	}
 }

+ 10 - 6
internal/handler/routes.go

@@ -22,17 +22,21 @@ func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
 				Path:    "/api/auth/loginByWeixin",
 				Handler: LoginByWeixinHandler(serverCtx),
 			},
-			{
-				Method:  http.MethodGet,
-				Path:    "/api/users/share/jz_qrcode",
-				Handler: JzQrCodeHandler(serverCtx),
-			},
 		},
 	)
 
 	engine.AddRoutes(
 		[]rest.Route{
-
+			{
+				Method:  http.MethodGet,
+				Path:    "/api/users/share/jzQrcode",
+				Handler: JzQrCodeHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/api/users/bindMobile",
+				Handler: BindMobileHandler(serverCtx),
+			},
 			{
 				Method:  http.MethodGet,
 				Path:    "/api/users/id/:userId",

+ 43 - 0
internal/logic/bindmobilelogic.go

@@ -0,0 +1,43 @@
+package logic
+
+import (
+	"context"
+
+	"git.i2edu.net/i2/i2-bill-api/internal/svc"
+	"git.i2edu.net/i2/i2-bill-api/internal/types"
+
+	"git.i2edu.net/i2/go-zero/core/logx"
+)
+
+type BindMobileLogic struct {
+	logx.Logger
+	ctx        context.Context
+	svcCtx     *svc.ServiceContext
+	UserId     int64
+	SessionKey string
+}
+
+func NewBindMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) BindMobileLogic {
+	return BindMobileLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+	}
+}
+
+func (l *BindMobileLogic) BindMobile(req types.BindMobileRequest) (*types.BindMobileResponse, error) {
+	mobile, err := l.svcCtx.Wechat.DecryptMobile(l.SessionKey, req.EncryptedData, req.Iv)
+	if err != nil {
+		logx.Error(err)
+		return nil, err
+	}
+	_, err = l.svcCtx.SqlConn.Exec(`update user set 
+	mobile = ? where id = ?`, mobile, l.UserId)
+	if err != nil {
+		logx.Error(err)
+		return nil, err
+	}
+	return &types.BindMobileResponse{
+		Id: l.UserId,
+	}, nil
+}

+ 4 - 2
internal/logic/getuserlogic.go

@@ -11,8 +11,10 @@ import (
 
 type GetUserLogic struct {
 	logx.Logger
-	ctx    context.Context
-	svcCtx *svc.ServiceContext
+	ctx        context.Context
+	svcCtx     *svc.ServiceContext
+	UserId     int64
+	SessionKey string
 }
 
 func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) GetUserLogic {

+ 6 - 4
internal/logic/jzqrcodelogic.go

@@ -11,8 +11,10 @@ import (
 
 type JzQrCodeLogic struct {
 	logx.Logger
-	ctx    context.Context
-	svcCtx *svc.ServiceContext
+	ctx        context.Context
+	svcCtx     *svc.ServiceContext
+	UserId     int64
+	SessionKey string
 }
 
 var qrcodeJzPrefix = "qrcode:jz:%v"
@@ -25,9 +27,9 @@ func NewJzQrCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) JzQrCodeL
 	}
 }
 
-func (l *JzQrCodeLogic) JzQrCode(u int64) (*types.JzQrcodeResponse, error) {
+func (l *JzQrCodeLogic) JzQrCode() (*types.JzQrcodeResponse, error) {
 	uid, err := l.svcCtx.Wechat.GenQrId(qrcodeJzPrefix, map[string]interface{}{
-		"userId": u,
+		"userId": l.UserId,
 	})
 	if err != nil {
 		return nil, err

+ 5 - 4
internal/logic/loginbyweixinlogic.go

@@ -29,8 +29,8 @@ func NewLoginByWeixinLogic(ctx context.Context, svcCtx *svc.ServiceContext) Logi
 	}
 }
 
-func (l *LoginByWeixinLogic) LoginByWeixin(req types.AuthLoginBody) (*types.AuthResponse, error) {
-	alb, rtnInfo := req, types.AuthResponse{}
+func (l *LoginByWeixinLogic) LoginByWeixin(req types.LoginByWeixinRequest) (*types.LoginByWeixinResponse, error) {
+	alb, rtnInfo := req, types.LoginByWeixinResponse{}
 	userInfo, err := l.Login(alb.Code)
 	if err != nil {
 		logx.Error(err)
@@ -88,7 +88,8 @@ func (l *LoginByWeixinLogic) LoginByWeixin(req types.AuthLoginBody) (*types.Auth
 	var accessExpire = l.svcCtx.Config.JwtAuth.AccessExpire
 	now := time.Now().Unix()
 	payloads := map[string]interface{}{
-		"userId": rtnInfo.UserInfo.ID,
+		"userId":     rtnInfo.UserInfo.ID,
+		"sessionKey": userInfo.SessionKey,
 	}
 	accessToken, err := l.CreateJWT(now, l.svcCtx.Config.JwtAuth.AccessSecret, payloads, accessExpire)
 	if err != nil {
@@ -122,9 +123,9 @@ func (l *LoginByWeixinLogic) Login(code string) (*types.WXUserInfo, error) {
 
 	var res types.WXLoginResponse
 	req.ToJSON(&res)
-
 	userinfo, err := l.DecryptUserInfoData(res.SessionKey)
 	userinfo.OpenID = res.OpenID
+	userinfo.SessionKey = res.SessionKey
 	return userinfo, err
 
 }

+ 71 - 4
internal/svc/servicecontext.go

@@ -1,17 +1,23 @@
 package svc
 
 import (
+	"crypto/aes"
+	"crypto/cipher"
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"net/http"
 	"path"
 	"time"
 
 	"git.i2edu.net/i2/go-zero/core/stores/sqlx"
+	"git.i2edu.net/i2/go-zero/rest/token"
 	"git.i2edu.net/i2/go-zero/zrpc"
 	"git.i2edu.net/i2/i2-bill-api/internal/config"
 	"git.i2edu.net/i2/i2-bill-api/internal/utils"
 	"git.i2edu.net/i2/i2-bill-api/model"
 	"git.i2edu.net/i2/i2-bill-erp/transformclient"
+	"github.com/dgrijalva/jwt-go"
 )
 
 const cacheWechatTokenPrefix = "cache:wechat:token:"
@@ -29,7 +35,18 @@ type Wechat struct {
 	c     config.Config
 }
 
-// GetAccessToken defined
+// WechatPhonedecrypt defined TODO
+type WechatPhonedecrypt struct {
+	PhoneNumber     string `json:"phoneNumber"`
+	PurePhoneNumber string `json:"purePhoneNumber"`
+	CountryCode     string `json:"countryCode"`
+	Watermark       struct {
+		Appid     string `json:"appid"`
+		Timestamp int32  `json:"timestamp"`
+	} `json:"watermark"`
+}
+
+// GetAccessToken defined TODO
 func (wc *Wechat) GetAccessToken() (string, error) {
 	token := ""
 	err := wc.RdCli.GetCache(cacheWechatTokenPrefix, &token)
@@ -55,6 +72,7 @@ func (wc *Wechat) GetAccessToken() (string, error) {
 	return token, nil
 }
 
+// GenQrId defined TODO
 func (wc *Wechat) GenQrId(prefix string, params interface{}) (string, error) {
 	uuid := utils.GetUUID()
 	ube, err := json.Marshal(params)
@@ -65,6 +83,7 @@ func (wc *Wechat) GenQrId(prefix string, params interface{}) (string, error) {
 	return uuid, err
 }
 
+// GetQrParams defined TODO
 func (wc *Wechat) GetQrParams(prefix string, uuid string, params interface{}) error {
 	ube := ""
 	err := wc.RdCli.GetCache(fmt.Sprintf(prefix, uuid), &ube)
@@ -78,9 +97,39 @@ func (wc *Wechat) GetQrParams(prefix string, uuid string, params interface{}) er
 	return err
 }
 
-// GenQrCode defined
-// 接口 B:适用于需要的码数量极多的业务场景
-// 生成小程序码,可接受页面参数较短,生成个数不受限。
+// GetQrParams defined TODO
+func (wc *Wechat) DecryptMobile(sessionKey, encryptedData, iv string) (string, error) {
+	key, err := base64.StdEncoding.DecodeString(sessionKey)
+	if err != nil {
+		return "", err
+	}
+	bIv, err := base64.StdEncoding.DecodeString(iv)
+	if err != nil {
+		return "", err
+	}
+	bEncryptedData, err := base64.StdEncoding.DecodeString(encryptedData)
+	if err != nil {
+		return "", err
+	}
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return "", err
+	}
+	var decrypt = bEncryptedData
+	blockMode := cipher.NewCBCDecrypter(block, bIv)
+	blockMode.CryptBlocks(decrypt, bEncryptedData)
+	length := len(decrypt)
+	unpadding := int(decrypt[length-1])
+	decData := decrypt[:(length - unpadding)]
+
+	decryptData := WechatPhonedecrypt{}
+	err = json.Unmarshal(decData, &decryptData)
+	if err != nil {
+		return "", err
+	}
+	return decryptData.PhoneNumber, err
+}
+
 func (wc *Wechat) GenQrCode(scene, page string) (string, error) {
 	token, err := wc.GetAccessToken()
 	if err != nil {
@@ -105,6 +154,24 @@ type ServiceContext struct {
 	Transformer transformclient.Transform
 }
 
+func (sc *ServiceContext) GetUserId(r *http.Request) (int64, error) {
+	parser := token.NewTokenParser(token.WithResetDuration(time.Minute))
+	tok, err := parser.ParseToken(r, sc.Config.JwtAuth.AccessSecret, "")
+	if err != nil {
+		return 0, err
+	}
+	return tok.Claims.(jwt.MapClaims)["user_id"].(int64), err
+}
+
+func (sc *ServiceContext) GetSessionKey(r *http.Request) (string, error) {
+	parser := token.NewTokenParser(token.WithResetDuration(time.Minute))
+	tok, err := parser.ParseToken(r, sc.Config.JwtAuth.AccessSecret, "")
+	if err != nil {
+		return "", err
+	}
+	return tok.Claims.(jwt.MapClaims)["sessionKey"].(string), err
+}
+
 func NewServiceContext(c config.Config) *ServiceContext {
 	svc := &ServiceContext{
 		Config:      c,

+ 22 - 12
internal/types/types.go

@@ -15,16 +15,17 @@ type Watermark struct {
 }
 
 type WXUserInfo struct {
-	OpenID    string    `json:"openId,omitempty"`
-	NickName  string    `json:"nickName"`
-	AvatarUrl string    `json:"avatarUrl"`
-	Gender    int64     `json:"gender"`
-	Country   string    `json:"country"`
-	Province  string    `json:"province"`
-	City      string    `json:"city"`
-	UnionID   string    `json:"unionId,omitempty"`
-	Language  string    `json:"language"`
-	Watermark Watermark `json:"watermark,omitempty"`
+	OpenID     string    `json:"openId,omitempty"`
+	SessionKey string    `json:"SessionKey"`
+	NickName   string    `json:"nickName"`
+	AvatarUrl  string    `json:"avatarUrl"`
+	Gender     int64     `json:"gender"`
+	Country    string    `json:"country"`
+	Province   string    `json:"province"`
+	City       string    `json:"city"`
+	UnionID    string    `json:"unionId,omitempty"`
+	Language   string    `json:"language"`
+	Watermark  Watermark `json:"watermark,omitempty"`
 }
 
 type ResUserInfo struct {
@@ -35,7 +36,7 @@ type ResUserInfo struct {
 	IV            string     `json:"iv"`
 }
 
-type AuthLoginBody struct {
+type LoginByWeixinRequest struct {
 	Code string `json:"code"`
 }
 
@@ -50,7 +51,7 @@ type AuthUserInfo struct {
 	Birthday int64  `json:"birthday"`
 }
 
-type AuthResponse struct {
+type LoginByWeixinResponse struct {
 	Token    string       `json:"token"`
 	UserInfo AuthUserInfo `json:"userInfo"`
 }
@@ -60,6 +61,15 @@ type JzQrcodeResponse struct {
 	FileId string `json:"fileId"`
 }
 
+type BindMobileRequest struct {
+	Iv            string `json:"iv"`
+	EncryptedData string `json:"encryptedData"`
+}
+
+type BindMobileResponse struct {
+	Id int64 `json:"id"`
+}
+
 type Request struct {
 	Id int64 `form:"id"`
 }