diff --git a/controllers/app/sendVerifyCode.go b/controllers/app/sendVerifyCode.go new file mode 100644 index 0000000..63a643e --- /dev/null +++ b/controllers/app/sendVerifyCode.go @@ -0,0 +1,46 @@ +package app + +import ( + "git.rosy.net.cn/jx-print/controllers" + "git.rosy.net.cn/jx-print/model" + "git.rosy.net.cn/jx-print/model/app_model" + "git.rosy.net.cn/jx-print/services/print_server/app_server" + "github.com/gin-gonic/gin" + "net/http" +) + +type Verification struct{} + +var VerificationController = new(Verification) + +// SendVerifyCode 获取短信验证码 +// @Title 登录接口 +// @Description 登录接口(微信与公众号登录不能直接调用此接口) +// @Param data body app_model.VerificationPhoneCode true "请求参数" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /sendVerifyCode [post] +func (a *Verification) SendVerifyCode(c *gin.Context) { + // 参数绑定 + var ( + err error + params *app_model.VerificationPhoneCode + service = app_server.SendVerifyCodeServer + ) + + if err = c.ShouldBind(¶ms); err != nil { + c.JSON(http.StatusOK, &model.CallBack{ + Code: model.ErrCodeNormal, + Desc: err.Error(), + }) + return + } + + controllers.CallFunc(c, func() (retVal interface{}, errCode string, err error) { + bizId, err := service.SendCode(params.PhoneNumber) + if err != nil { + return nil, "", err + } + return map[string]interface{}{"bizId": bizId}, "", nil + }) +} diff --git a/controllers/app/wx_login.go b/controllers/app/wx_login.go index a6d299e..f13b0a2 100644 --- a/controllers/app/wx_login.go +++ b/controllers/app/wx_login.go @@ -16,15 +16,15 @@ var Auth2ControllerController = new(Auth2Controller) // Login 登录接口 // @Title 登录接口 // @Description 登录接口(微信与公众号登录不能直接调用此接口) -// @Param data body app_model.WeChatPhoneNumberParam true "请求参数" +// @Param data body app_model.WxLoginReq true "请求参数" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult -// @router /Login [post] +// @router /login [post] func (a *Auth2Controller) Login(c *gin.Context) { // 参数绑定 var ( err error - params *app_model.WeChatPhoneNumberParam + params *app_model.WxLoginReq service = app_server.UserLogin{} ) @@ -51,6 +51,44 @@ func (a *Auth2Controller) Login(c *gin.Context) { }) } +// Login4Mobile 登录接口验证码登录 +// @Title 登录接口验证码登录 +// @Description 登录接口(验证码登录) +// @Param data body app_model.MobileLogin true "请求参数" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /loginMobile [post] +func (a *Auth2Controller) Login4Mobile(c *gin.Context) { + // 参数绑定 + var ( + err error + params *app_model.MobileLogin + service = app_server.UserLogin{} + ) + + if err = c.ShouldBind(¶ms); err != nil { + c.JSON(http.StatusOK, &model.CallBack{ + Code: model.ErrCodeNormal, + Desc: err.Error(), + }) + return + } + + controllers.CallFunc(c, func() (retVal interface{}, errCode string, err error) { + user, err := service.MobileLogin(c, params) + if err != nil { + return nil, "", err + } + // 获取token + token, err := controllers.SetToken(user) + if err != nil { + return nil, "", err + } + + return map[string]interface{}{"token": token, "user": user}, "", nil + }) +} + // GetUserPhoneByWeChat 获取用户电话号码 // @Title 获取用户电话号码 // @Description 获取用户电话号码 @@ -75,12 +113,12 @@ func (a *Auth2Controller) GetUserPhoneByWeChat(c *gin.Context) { } controllers.CallFunc(c, func() (retVal interface{}, errCode string, err error) { - phone, err := service.GetUserPhoneNum(params) + phone, isRegister, err := service.GetUserPhoneNum(params) if err != nil { return nil, "", err } - return map[string]interface{}{"phone": phone}, "", nil + return map[string]interface{}{"phone": phone, "isRegister": isRegister}, "", nil }) } diff --git a/controllers/app/wx_print.go b/controllers/app/wx_print.go index 94da8b4..83fcb71 100644 --- a/controllers/app/wx_print.go +++ b/controllers/app/wx_print.go @@ -45,6 +45,10 @@ func (p *Print) AddPrinters(c *gin.Context) { } controllers.CallFunc(c, func() (retVal interface{}, errCode string, err error) { + // 校验打印机绑定用户 + if err := printServer.QueryPrintKeyIsExit(printInfo[0], param.Phone, param.BizId, param.Code, param.AppID); err != nil { + return nil, "", err + } // 添加打印机 if err := printServer.AddPrinters(tokenInfo, param.AppID, printInfo); err != nil { return nil, "", err diff --git a/controllers/controller.go b/controllers/controller.go index 4767419..b1f4140 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -154,6 +154,6 @@ func createToken(user *model.User) (token string) { user.UserID, time.Now().Format("20060102-150405"), utils.GetUUID(), - user.Name, + user.Mobile, }, wxConst.TokenTypeSep) } diff --git a/dao/user_dao.go b/dao/user_dao.go index 9074708..cbaf5de 100644 --- a/dao/user_dao.go +++ b/dao/user_dao.go @@ -96,12 +96,12 @@ func GetMenuDetail(db *sqlx.DB, id int) (menu *model.MenuDetail, err error) { // CreateUserWx 创建微信用户 func CreateUserWx(user *model.User) error { db := globals.GetDB() - return Insert(db, &user) + return Insert(db, user) } // UpdateUserWx 更新微信用户 func UpdateUserWx(user *model.User, filed []string) error { db := globals.GetDB() // 用户存在,判断用户 - return Update(db, &user, filed...) + return Update(db, user, filed...) } diff --git a/globals/globals.go b/globals/globals.go index aa02e04..5a2e9d5 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -8,9 +8,13 @@ import ( ) var ( - SugarLogger *zap.SugaredLogger - db *sqlx.DB - err error + SugarLogger *zap.SugaredLogger + db *sqlx.DB + err error + AliKey = "LTAI4FwZN7pp4dACQHoapkZQ" + AliSecret = "NTegceUFX0FdfMovqCDzqcIKmhcoOu" + SmsSignName = "京西菜市" + SmsMobileVerifyTemplate = "SMS_175583158" ) func init() { diff --git a/main.go b/main.go index f332db9..b894f75 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,6 @@ import ( func main() { gin.DisableConsoleColor() - globals.SugarLogger.Debug("print--------------main") r := gin.New() //r.Use(session(model.SessionKey)) f, _ := os.Create("jx-print.log") diff --git a/model/app_model/app_print.go b/model/app_model/app_print.go index 8fff33f..81842c0 100644 --- a/model/app_model/app_print.go +++ b/model/app_model/app_print.go @@ -3,6 +3,10 @@ package app_model type AddPrintReq struct { AppID int `json:"app_id" form:"app_id" binding:"required"` Prints string `json:"prints" form:"prints" binding:"required"` + + Phone string `json:"phone" form:"phone" binding:"required"` + Code string `json:"code" form:"code" binding:"required"` + BizId string `json:"biz_id" form:"biz_id" binding:"required"` } type QueryPrintReq struct { diff --git a/model/app_model/app_user_login.go b/model/app_model/app_user_login.go index e03fb24..6ad2fcc 100644 --- a/model/app_model/app_user_login.go +++ b/model/app_model/app_user_login.go @@ -21,28 +21,33 @@ const ( // WeChatPhoneNumberParam 微信登陆 type WeChatPhoneNumberParam struct { - EncryptedData string `json:"encrypted_data" form:"encrypted_data" binding:"required"` // 加密信息 - IV string `json:"iv" form:"iv" binding:"required"` // 加密算法初始量 - NickName string `json:"nick_name" form:"nick_name" binding:"required"` // 昵称 - HeadUrl string `json:"head_url" form:"head_url" binding:"required"` //头像图片地址 - Code string `json:"code" form:"code" binding:"required"` //code + Code string `json:"code" form:"code" binding:"required"` //code } type WxLoginReq struct { - Code string `json:"code" form:"code"` // 微信授权登录使用 - Phone string `json:"phone" form:"phone"` // 微信和电话登录使用 - Password string `json:"password" form:"password"` // 密码登录 + EncryptedData string `json:"encrypted_data" form:"encrypted_data" binding:"required"` // 加密信息 + IV string `json:"iv" form:"iv" binding:"required"` // 加密算法初始量 + NickName string `json:"nick_name" form:"nick_name"` // 昵称 + HeadUrl string `json:"head_url" form:"head_url"` //头像图片地址 + Code string `json:"code" form:"code" binding:"required"` // code + Phone string `json:"phone" form:"phone" binding:"required"` // 电话 } -// 微信小程序解密后 用户手机号结构体 -type UserPhone struct { - PhoneNumber string `json:"phoneNumber,omitempty"` - PurePhoneNumber string `json:"purePhoneNumber,omitempty"` - CountryCode string `json:"countryCode,omitempty"` - Watermark *watermarkInfo `json:"watermark,omitempty"` +type MobileLogin struct { + Phone string `json:"phone" form:"phone" binding:"required"` // 电话 + BizId string `json:"biz_id" form:"biz_id" binding:"required"` // 短信id + Code string `json:"code" form:"code" binding:"required"` // code } -type watermarkInfo struct { - Appid string `json:"appid,omitempty"` - Timestamp int `json:"timestamp,omitempty"` -} +//// 微信小程序解密后 用户手机号结构体 +//type UserPhone struct { +// PhoneNumber string `json:"phoneNumber,omitempty"` +// PurePhoneNumber string `json:"purePhoneNumber,omitempty"` +// CountryCode string `json:"countryCode,omitempty"` +// Watermark *watermarkInfo `json:"watermark,omitempty"` +//} +// +//type watermarkInfo struct { +// Appid string `json:"appid,omitempty"` +// Timestamp int `json:"timestamp,omitempty"` +//} diff --git a/model/app_model/verification_code.go b/model/app_model/verification_code.go new file mode 100644 index 0000000..606d826 --- /dev/null +++ b/model/app_model/verification_code.go @@ -0,0 +1,5 @@ +package app_model + +type VerificationPhoneCode struct { + PhoneNumber string `json:"phone_number" form:"phone_number" binding:"required"` +} diff --git a/model/model.go b/model/model.go index 7750c75..23cfa72 100644 --- a/model/model.go +++ b/model/model.go @@ -41,6 +41,7 @@ const ( OrderTypeCardValid = "cardValid" //卡有效期 OrderOriginWxMini = "weixinmini" + OrderOriginMobile = "mobile" OrderOriginOpenAPI = "openAPI" OrderStatusWaitPay = 0 //待支付 @@ -179,7 +180,7 @@ type Printer struct { DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"` AppID int `json:"app_id" db:"app_id"` //应用编号 PrintNo string `json:"print_no" db:"print_no"` //打印机编号 - PrintKey string `json:"print_key" db:"print_key"` //打印机识别码 + PrintKey string `json:"print_key" db:"print_key"` //打印机识别码(目前是用户电话号码) Name string `json:"name" db:"name"` //打印机备注名 Status int `json:"status" db:"status"` //打印机状态 IsOnline int `json:"is_online" db:"is_online"` //1在线,0离线 diff --git a/routers/v3_app_router.go b/routers/v3_app_router.go index 7ae5bfc..135dc47 100644 --- a/routers/v3_app_router.go +++ b/routers/v3_app_router.go @@ -6,9 +6,13 @@ import ( ) // InitV3App 小程序端,不需要token -func InitV3App(v2 *gin.RouterGroup) { - appNo := v2.Group("/app_no") +func InitV3App(v1 *gin.RouterGroup) { + appNo := v1.Group("/app_no") appNo.POST("/getUserPhone", app.Auth2ControllerController.GetUserPhoneByWeChat) // 获取微信用户电话 appNo.POST("/login", app.Auth2ControllerController.Login) // 微信登录 + appNo.POST("/loginMobile", app.Auth2ControllerController.Login4Mobile) // 短信登录 + + // 获取短信 + appNo.POST("/sendVerifyCode", app.VerificationController.SendVerifyCode) // 获取短信验证码 } diff --git a/services/print_server/app_server/verify_code.go b/services/print_server/app_server/verify_code.go new file mode 100644 index 0000000..9540464 --- /dev/null +++ b/services/print_server/app_server/verify_code.go @@ -0,0 +1,61 @@ +package app_server + +import ( + "errors" + "fmt" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-print/globals" + "git.rosy.net.cn/jx-print/putils" + "git.rosy.net.cn/jx-print/services/api" + aliyunsmsclient "github.com/KenmyZhang/aliyun-communicate" + "math/rand" +) + +type SendVerifyCode struct { +} + +var SendVerifyCodeServer = new(SendVerifyCode) + +// SendCode 获取手机短信验证码 +func (s *SendVerifyCode) SendCode(mobile string) (string, error) { + // 获取code + smsCode := "" + code := putils.GetKey(mobile) + if code != "" { + smsCode = code.(string) + } else { + smsCode = fmt.Sprintf("%06d", rand.Intn(1000000)) + } + + // 发送短信 + response, err := api.SMSClient.Execute(globals.AliKey, globals.AliSecret, mobile, globals.SmsSignName, globals.SmsMobileVerifyTemplate, string(utils.MustMarshal(map[string]interface{}{ + "code": smsCode, + }))) + + if err != nil { + return "", err + } + if response.Code != aliyunsmsclient.ResponseCodeOk { + return "", fmt.Errorf("发送短信出错:%s", response.Message) + } + + if err := putils.SetKey(response.BizId+"_"+mobile, smsCode, 5*60); err != nil { + globals.SugarLogger.Debugf("redis set key err key[%s] value[%s]:", mobile, smsCode) + return "", err + } + + return response.BizId, nil +} + +// VerifySecret 检查验证码 +func (s *SendVerifyCode) VerifySecret(mobile, bizId, code string) (bool, error) { + result := putils.GetKey(bizId + "_" + mobile) + if result != "" { + return false, errors.New("验证码过期") + } + if result.(string) != code { + return false, errors.New("验证码错误,重新确认") + } + putils.DelKey(bizId + "_" + mobile) + return true, nil +} diff --git a/services/print_server/app_server/wx_login.go b/services/print_server/app_server/wx_login.go index f8688d9..6102b84 100644 --- a/services/print_server/app_server/wx_login.go +++ b/services/print_server/app_server/wx_login.go @@ -1,9 +1,6 @@ package app_server import ( - "crypto/aes" - "crypto/cipher" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -17,7 +14,6 @@ import ( "io/ioutil" "net/http" "net/url" - "reflect" "strconv" "strings" "time" @@ -27,14 +23,14 @@ type UserLogin struct { } // WxLogin 授权登录 -func (u *UserLogin) WxLogin(ctx *gin.Context, param *wxLogin.WeChatPhoneNumberParam) (*model.User, error) { +func (u *UserLogin) WxLogin(ctx *gin.Context, param *wxLogin.WxLoginReq) (*model.User, error) { openObj, err := api.WeixinMiniAPI.SNSCode2Session(param.Code) if err != nil { return nil, err } // 检查用户是否存在 - users, err := dao.GetUsers(globals.GetDB(), "", "", "", openObj.OpenID) + users, err := dao.GetUsers(globals.GetDB(), "", "", param.Phone, openObj.OpenID) if err != nil { return nil, err } @@ -42,11 +38,6 @@ func (u *UserLogin) WxLogin(ctx *gin.Context, param *wxLogin.WeChatPhoneNumberPa return nil, errors.New("数据异常,用户电话不唯一,联系管理员") } - weChatLogin := new(wxLogin.UserPhone) - if err := DecryptOpenDataToStruct(param.EncryptedData, param.IV, openObj.SessionKey, weChatLogin); err != nil { - return nil, err - } - var userObj *model.User timeNow := time.Now() switch len(users) { @@ -60,7 +51,7 @@ func (u *UserLogin) WxLogin(ctx *gin.Context, param *wxLogin.WeChatPhoneNumberPa UserID: utils.GetUUID(), Password: "", Name: param.NickName, - Mobile: weChatLogin.PhoneNumber, + Mobile: param.Phone, Email: "", Avatar: param.HeadUrl, Status: 1, @@ -88,79 +79,106 @@ func (u *UserLogin) WxLogin(ctx *gin.Context, param *wxLogin.WeChatPhoneNumberPa users[0].Avatar = param.HeadUrl users[0].OpenId = openObj.OpenID users[0].UnionId = openObj.UnionID + // 用户存在,判断用户 + users[0].UpdatedAt = &timeNow + users[0].LastOperator = users[0].Name + users[0].LastLoginIP = ctx.ClientIP() + if err := dao.UpdateUserWx(users[0], []string{"updated_at", "last_operator", "last_login_ip", "name", "avatar", "open_id", "union_id"}); err != nil { + return nil, err + } + } else { + // 用户存在,判断用户 + users[0].UpdatedAt = &timeNow + users[0].LastOperator = users[0].Name + users[0].LastLoginIP = ctx.ClientIP() + if err := dao.UpdateUserWx(users[0], []string{"updated_at", "last_operator", "last_login_ip"}); err != nil { + return nil, err + } } - // 用户存在,判断用户 - users[0].UpdatedAt = &timeNow - users[0].LastOperator = users[0].Name - users[0].LastLoginIP = ctx.ClientIP() - if err := dao.UpdateUserWx(users[0], []string{"updated_at", "last_operator", "last_login_ip", "name", "avatar", "open_id", "union_id"}); err != nil { - return nil, err - } + userObj = users[0] } return userObj, err } -// DecryptOpenDataToStruct 解密开放数据到结构体 -// encryptedData:包括敏感数据在内的完整用户信息的加密数据,小程序获取到 -// iv:加密算法的初始向量,小程序获取到 -// sessionKey:会话密钥,通过 gopay.Code2Session() 方法获取到 -// beanPtr:需要解析到的结构体指针,操作完后,声明的结构体会被赋值 -// 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html -func DecryptOpenDataToStruct(encryptedData, iv, sessionKey string, beanPtr interface{}) (err error) { - if encryptedData == "" || iv == "" || sessionKey == "" { - return errors.New("input params can not null") +// MobileLogin 短信验证登录 +func (u *UserLogin) MobileLogin(ctx *gin.Context, param *wxLogin.MobileLogin) (*model.User, error) { + // 校验用户信息 + isHave, err := SendVerifyCodeServer.VerifySecret(param.Phone, param.BizId, param.Code) + if err != nil { + return nil, err } - var ( - cipherText, aesKey, ivKey, plainText []byte - block cipher.Block - blockMode cipher.BlockMode - ) - beanValue := reflect.ValueOf(beanPtr) - if beanValue.Kind() != reflect.Ptr { - return errors.New("传入beanPtr类型必须是以指针形式") + if !isHave { + return nil, errors.New("验证码错误") } - if beanValue.Elem().Kind() != reflect.Struct { - return errors.New("传入interface{}必须是结构体") + // 检查用户是否存在 + users, err := dao.GetUsers(globals.GetDB(), "", "", param.Phone, "") + if err != nil { + return nil, err } - cipherText, _ = base64.StdEncoding.DecodeString(encryptedData) - aesKey, _ = base64.StdEncoding.DecodeString(sessionKey) - ivKey, _ = base64.StdEncoding.DecodeString(iv) - if len(cipherText)%len(aesKey) != 0 { - return errors.New("encryptedData is error") - } - if block, err = aes.NewCipher(aesKey); err != nil { - return fmt.Errorf("aes.NewCipher:%w", err) - } - blockMode = cipher.NewCBCDecrypter(block, ivKey) - plainText = make([]byte, len(cipherText)) - blockMode.CryptBlocks(plainText, cipherText) - if len(plainText) > 0 { - plainText = PKCS7UnPadding(plainText) - } - if err = json.Unmarshal(plainText, beanPtr); err != nil { - return fmt.Errorf("json.Marshal(%s):%w", string(plainText), err) - } - return -} -// PKCS7UnPadding 解密填充模式(去除补全码) PKCS7UnPadding -// 解密时,需要在最后面去掉加密时添加的填充byte -func PKCS7UnPadding(origData []byte) (bs []byte) { - length := len(origData) - unPaddingNumber := int(origData[length-1]) // 找到Byte数组最后的填充byte 数字 - if unPaddingNumber <= 16 { - bs = origData[:(length - unPaddingNumber)] // 只截取返回有效数字内的byte数组 - } else { - bs = origData + var userObj *model.User + timeNow := time.Now() + switch len(users) { + case 1: // 用户存在 + userObj = users[0] + case 0: // 用户不存在 + // 用户不存在,创建并返回用户 + userBase := &model.User{ + CreatedAt: &timeNow, + UpdatedAt: &timeNow, + LastOperator: "系统新增", + DeletedAt: &utils.DefaultTimeValue, + UserID: utils.GetUUID(), + Password: "", + Name: "手机用户_" + param.Phone, + Mobile: param.Phone, + Email: "", + Avatar: "", + Status: 1, + Type: 1, + Company: "", + CityCode: 0, + DistrictCode: 0, + Address: "", + IDCardNo: "", + Remark: "", + LastLoginAt: &timeNow, + LastLoginIP: ctx.ClientIP(), + LastLoginType: model.OrderOriginMobile, + OpenId: "", + UnionId: "", + } + if err := dao.CreateUserWx(userBase); err != nil { + return nil, err + } + userObj = userBase } - return + return userObj, nil } // GetUserPhoneNum 解密用户手机号 -func (u *UserLogin) GetUserPhoneNum(param *wxLogin.WeChatPhoneNumberParam) (string, error) { - return api.WeixinMiniAPI.SNSGetUserPhone(param.Code) +func (u *UserLogin) GetUserPhoneNum(param *wxLogin.WeChatPhoneNumberParam) (string, bool, error) { + phone, err := api.WeixinMiniAPI.SNSGetUserPhone(param.Code) + if err != nil { + return "", false, err + } + // 检查用户是否存在 + users, err := dao.GetUsers(globals.GetDB(), "", "", phone, "") + if err != nil { + return "", false, err + } + if len(users) != 1 { + return phone, false, nil + } + + isRegion := false + if users[0].OpenId != "" { + isRegion = true + } + + return phone, isRegion, nil } // Jxc4UserToken 全局变量,缓存菜市管理系统token diff --git a/services/print_server/app_server/wx_print.go b/services/print_server/app_server/wx_print.go index 63aab28..07adab3 100644 --- a/services/print_server/app_server/wx_print.go +++ b/services/print_server/app_server/wx_print.go @@ -10,6 +10,35 @@ import ( "time" ) +// QueryPrintKeyIsExit 查询用户打印机以及绑定信息 +func QueryPrintKeyIsExit(printInfo *model.PrintInfo, phone, bizId, code string, appId int) error { + var ( + db = globals.GetDB() + ) + // 电话号码校验 + have, err := SendVerifyCodeServer.VerifySecret(phone, bizId, code) + if err != nil { + return err + } + if !have { + return fmt.Errorf("验证码错误") + } + + if printInfo.PrintNo == "" { + return fmt.Errorf("请输入正确的打印机编号!print_no :%s 。", printInfo.PrintNo) + } + + printers, _ := dao.GetPrinters(db, appId, printInfo.PrintNo, 0, 0, 0) + // 打印机已经被绑定 + if len(printers) != 0 { + // 修改绑定 + if printers[0].PrintKey != "" && printers[0].PrintKey != phone { + return fmt.Errorf("打印机已经被其他用户绑定,修改绑定请联系商家") + } + } + return nil +} + func AddPrinters(tokenInfo *model.TokenInfo, appID int, printInfo []*model.PrintInfo) (err error) { var ( db = globals.GetDB()