You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

817 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package users
import (
"crypto/aes"
"crypto/cipher"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"recook/internal/back"
"recook/internal/cache"
"recook/internal/dbc"
. "recook/internal/dbc"
"recook/internal/define"
"recook/internal/model/user"
service "recook/internal/service/app"
"recook/internal/static_path"
"recook/internal/v2/model/company"
"recook/tools"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis"
"github.com/golangkit/formatime"
"github.com/jinzhu/gorm"
)
type weChatLogin struct {
WxType string `json:"wxType"`
Code string `json:"code" validate:"required"`
}
type mobileLogin struct {
Mobile string `json:"mobile" validate:"required,len=11"`
SMS string `json:"sms" validate:"required,len=4"`
UnionID string `json:"unionid"`
}
type autoLoginParam struct {
UserId uint `json:"userId"`
}
type unionIDLoginParam struct {
UnionID string `json:"unionid"`
}
type BasicAuth struct {
ID uint `json:"id" form:"id" validate:"required"`
Token string `json:"token" form:"token" validate:"len=32"`
}
type rtnPack struct {
Auth BasicAuth `json:"auth"`
Info *DetailInfo `json:"info"`
Status int `json:"status"`
}
type DetailInfo struct {
user.Information
UserLevel int8 `json:"userLevel"`
RoleLevel int `json:"roleLevel"`
IsVerified bool `json:"isVerified"`
RealInfoStatus bool `json:"realInfoStatus"`
IsSetPayPwd bool `json:"isSetPayPwd"`
TeacherWechatNo string `gorm:"column:teacher_wechat_no;size:30" json:"teacherWechatNo"`
}
func (r *DetailInfo) ReflectedBy(u *user.Information, v bool) *DetailInfo {
// todo 修改user加字段 或者放缓存
var userWallet user.Wallet
DB.Where("user_id = ?", u.ID).First(&userWallet)
if len(userWallet.Password) > 0 {
r.IsSetPayPwd = true
}
if u.RealInfoStatus == user.RealInfoPass {
r.RealInfoStatus = true
}
r.ID = u.ID
r.Nickname = u.Nickname
r.HeadImgUrl = u.HeadImgUrl
r.WxUnionId = u.WxUnionId
r.Mobile = u.Mobile
r.Gender = u.Gender
r.Birthday = u.Birthday
r.InvitationNo = u.InvitationNo
r.CreatedAt = u.CreatedAt
//r.Role = u.Role
r.IsVerified = v
r.Phone = u.Phone
r.WechatNo = u.WechatNo
r.RealName = u.RealName
r.IDCard = u.IDCard
return r
}
type weChatAccess struct {
AccessToken string `json:"access_token" validate:"required"`
Openid string `json:"openid" validate:"required"`
ErrMsg string `json:"errmsg"`
}
type weChatUserInfo struct {
OpenID string `json:"openid" validate:"required"`
Nickname string `json:"nickname" validate:"required,min=1"`
UnionId string `json:"unionId" validate:"required,min=28"`
Sex uint `json:"sex" validate:"required"`
HeadImgUrl string `json:"headImgUrl" validate:"required"`
}
type miniLoginParam struct {
AuthCode string `json:"auth_code" validator:"required"`
AvatarUrl string `json:"avatar_url" validator:"required"`
Gender uint `json:"gender" validator:"required"`
Nickname string `json:"nickname" validator:"required"`
}
type openidResp struct {
OpenID string `json:"openid" validate:"required"`
UnionId string `json:"unionId" validate:"required"`
SessionKey string `json:"session_key"`
ErrMsg string `json:"errmsg"`
}
const WxMiniUserOpenIDUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=%v&secret=%v&js_code=%v&grant_type=authorization_code"
const WxUnionIDURL = "https://api.weixin.qq.com/sns/userinfo?access_token=%v&openid=%v"
const WxOauth2URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%v&secret=%v&code=%v&grant_type=authorization_code"
// LoginByWeChat 微信登陆 step1
func LoginByWeChat(c *gin.Context) {
var p weChatLogin
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
// 1.获取access_token
var access weChatAccess
{
url := ""
if p.WxType == "" {
url = fmt.Sprintf(WxOauth2URL, define.WxAppID, define.WxAppSecret, p.Code)
} else {
wxConfig := define.WxConfig[p.WxType]
if wxConfig.Type == "wxapp" {
url = fmt.Sprintf(WxMiniUserOpenIDUrl, wxConfig.AppId, wxConfig.AppSecret, p.Code)
} else {
url = fmt.Sprintf(WxOauth2URL, wxConfig.AppId, wxConfig.AppSecret, p.Code)
}
}
response1, err := http.Get(url)
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = response1.Body.Close() }()
body1, err := ioutil.ReadAll(response1.Body)
if err != nil {
log.Println(string(body1))
back.Err(c, err.Error())
return
}
err = json.Unmarshal(body1, &access)
if err != nil {
back.Err(c, err.Error())
return
}
if len(access.ErrMsg) > 0 {
back.Err(c, access.ErrMsg)
return
}
}
/*
2.拿到了微信的openid后 再去请求uinonid 依据unionid判断用户是否存在
*/
var userInfo weChatUserInfo
{
response2, err := http.Get(fmt.Sprintf(WxUnionIDURL, access.AccessToken, access.Openid))
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = response2.Body.Close() }()
body2, err := ioutil.ReadAll(response2.Body)
if err != nil {
back.Err(c, err.Error())
return
}
err = json.Unmarshal(body2, &userInfo)
if err != nil {
back.Err(c, err.Error())
return
}
log.Println(string(body2))
marshal, _ := json.Marshal(access)
log.Println(string(body2))
log.Println(string(marshal))
if userInfo.UnionId == "" {
back.Err(c, "获取UnionId失败")
return
}
}
// 之后开始做用户是否绑定的验证,绑定了手机了, 就直接登陆
// 未绑定,需要绑定手机
var info user.Information
err = DB.Where(user.Information{
WxUnionId: userInfo.UnionId,
}).Last(&info).Error
if err != nil && !gorm.IsRecordNotFoundError(err) {
back.Err(c, err.Error())
return
}
// 在数据库中查找是否存在用户, 且已经有手机号和邀请码, 直接登陆
if info.ID > 0 && len(info.InvitationNo) > 0 && len(info.Mobile) > 0 {
// todo 感觉可以不确定wxAppOpenId
if len(info.WxAppOpenId) == 0 {
err = DB.Model(&info).Update(user.Information{WxMiniOpenId: access.Openid}).Error
if err != nil {
back.Err(c, err.Error())
return
}
}
login(c, &info)
return
}
{
// 在缓存中查找是否已经之前导入过
var ur = service.NewUserWithRedis()
_, err := ur.GetUserInRedis(info.WxUnionId)
// 不为nil的err直接返回。
if err != nil && err != redis.Nil {
back.Err(c, "内部错误602"+err.Error())
return
}
// 未在redis中找到数据----》用户不存在 则获取授权信息
var headImgPath string
// 下载头像
{
if userInfo.HeadImgUrl != "" {
headResp, err := http.Get(userInfo.HeadImgUrl)
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = headResp.Body.Close() }()
hash := tools.MD5(userInfo.HeadImgUrl)
imgName := fmt.Sprintf("%v.jpg", hash)
headImgPath = filepath.Join(static_path.Dir.Photo, imgName)
dst := filepath.Join(static_path.Dir.Root, headImgPath)
out, err := os.Create(dst)
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = out.Close() }()
_, err = io.Copy(out, headResp.Body)
if err != nil {
back.Err(c, err.Error())
return
}
} else {
headImgPath = "/default/officaillogo-1.png"
}
}
// 不存在用户设置信息
info = user.Information{
Nickname: userInfo.Nickname,
HeadImgUrl: headImgPath,
WxUnionId: userInfo.UnionId,
WxAppOpenId: userInfo.OpenID,
Gender: userInfo.Sex,
InvitationNo: "",
Birthday: formatime.NewSecondFrom(define.DefaultBirthday),
}
// redis放入wx信息
ok, err := ur.SetUserInRedis(info.WxUnionId, info, 0)
if ok != "OK" || err != nil {
back.Err(c, "内部错误601"+err.Error())
return
}
// 将wx信息放入缓存中后返回需要填写手机号码
back.Suc(c, "请填写手机号码", gin.H{
"status": 0, //0 -> 填写 手机号 1 -> 完整用户信息
"bindingMobile": 0,
"info": gin.H{
"nickname": info.Nickname,
"wxUnionId": userInfo.UnionId,
},
})
}
}
// LoginByMiniProgram 通过小程序登录
func LoginByMiniProgram(c *gin.Context) {
var p miniLoginParam
if err := tools.ParseParams(&p, c); err != nil {
back.Fail(c, err.Error())
return
}
response, err := http.Get(fmt.Sprintf(WxMiniUserOpenIDUrl, define.WxMiniProgramAppID, define.WxMiniProgramAppSecret, p.AuthCode))
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = response.Body.Close() }()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
back.Fail(c, err.Error())
return
}
var resp openidResp
if err = json.Unmarshal(body, &resp); err != nil {
back.Fail(c, err.Error())
return
}
if len(resp.ErrMsg) > 0 {
back.Fail(c, "微信授权失败"+resp.ErrMsg)
return
}
if len(resp.UnionId) == 0 {
back.Fail(c, "不可用:请等待小程序客户端升级")
return
}
// 获取到openid 判断数据库是否有
var info user.Information
err = DB.Where(user.Information{
WxUnionId: resp.UnionId,
}).First(&info).Error
if err != nil && !gorm.IsRecordNotFoundError(err) {
back.Fail(c, err.Error())
return
}
if info.ID == 0 { // 用户不存在 则获取授权信息
var headImgPath string
// 下载头像
{
headResp, err := http.Get(p.AvatarUrl)
if err != nil {
back.Fail(c, err.Error())
return
}
defer func() { _ = headResp.Body.Close() }()
hash := tools.MD5(p.AvatarUrl)
imgName := fmt.Sprintf("%v.jpg", hash)
headImgPath = filepath.Join(static_path.Dir.Photo, imgName)
dst := filepath.Join(static_path.Dir.Root, headImgPath)
out, err := os.Create(dst)
if err != nil {
back.Err(c, err.Error())
return
}
defer func() { _ = out.Close() }()
_, err = io.Copy(out, headResp.Body)
if err != nil {
back.Err(c, err.Error())
return
}
}
info = user.Information{
Nickname: p.Nickname,
HeadImgUrl: headImgPath,
WxUnionId: resp.UnionId,
WxMiniOpenId: resp.OpenID,
Gender: p.Gender,
InvitationNo: "",
Birthday: formatime.NewSecondFrom(define.DefaultBirthday),
}
err = DB.Create(&info).Error
if err != nil {
back.Err(c, err.Error())
} else {
back.Suc(c, "请填写邀请码", gin.H{
"status": 0,
"info": gin.H{
"id": info.ID,
"nickname": info.Nickname,
},
})
}
} else {
if len(info.InvitationNo) > 0 {
if len(info.Mobile) > 0 {
if len(info.WxMiniOpenId) == 0 {
err = DB.Model(&info).Update(user.Information{WxMiniOpenId: resp.OpenID}).Error
if err != nil {
back.Err(c, err.Error())
return
}
}
// 小程序登录时 直接返回鉴权令牌
var realInfo user.RealInfo
DB.First(&realInfo, "user_id = ?", info.ID)
isVerified := false
if realInfo.ID > 0 {
isVerified = true
}
deviceType := cache.GetDeviceType(c)
var login user.Login
DB.First(&login, "user_id=? and device_type = ?", info.ID, deviceType)
if info.ID == 0 {
login = user.Login{
Token: tools.Token(),
IP: c.ClientIP(),
LoginTime: formatime.NewSecondNow(),
DeviceType: cache.GetDeviceType(c),
UserID: info.ID,
}
err := DB.Create(&login).Error
if err != nil {
back.Err(c, err.Error())
return
}
}
cache.SetUserLoginCache(&login)
back.Suc(c, "登录成功", &rtnPack{
BasicAuth{
ID: login.ID,
Token: login.Token,
},
(&DetailInfo{}).ReflectedBy(&info, isVerified),
1,
})
} else {
back.Suc(c, "请填写手机号码", gin.H{
"status": 1,
"bindingMobile": 0,
"info": gin.H{
"id": info.ID,
"nickname": info.Nickname,
},
})
}
} else {
back.Suc(c, "请填写邀请码", gin.H{
"status": 0,
"info": gin.H{
"id": info.ID,
"nickname": info.Nickname,
},
})
}
}
}
// LoginByMobile 通过手机号登录,如果没有推荐码,就跳转到注册方法
func LoginByMobile(c *gin.Context) {
var p mobileLogin
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, "参数错误:"+err.Error())
return
}
// 留的登陆后门, 在密码在redis中
// redis中设置5分钟更新一次验证码
pwd, _ := Rds.Get(cache.SuperKey).Result()
if pwd != p.SMS {
// 这里是正常用户的业务逻辑
v := cache.GetUserSMSCode(p.Mobile)
if v != p.SMS {
back.Fail(c, "验证码错误")
return
}
}
var info user.Information
err = DB.First(&info, "mobile=?", p.Mobile).Error
if err != nil {
if gorm.IsRecordNotFoundError(err) {
back.Suc(c, "请填写邀请码", gin.H{
"status": 0,
}) // 新用户 没有填写邀请码
} else {
back.Err(c, "用户不存在:"+err.Error())
}
//只要没有数据就去插入用户写一个插入用户的方法指定手机号的并调用login方法用来返回数据
} else {
if len(p.UnionID) > 0 {
DB.Model(&info).Where("id=?", info.ID).Update("wx_union_id", p.UnionID)
DB.First(&info, "mobile=?", p.Mobile)
}
login(c, &info)
}
}
type ArgsLoginInfo struct {
Name string `json:"name"`
Password string `json:"password"`
}
func LoginByName(c *gin.Context) {
var p ArgsLoginInfo
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
var obj company.Info
if err := dbc.DB.First(&obj, "shop_name= ?", p.Name).Error; err != nil {
back.Fail(c, err.Error())
return
}
if obj.State != 2 {
err := errors.New("未通过审核")
back.Fail(c, err.Error())
return
}
if obj.Status != 1 {
err := errors.New("被禁用")
back.Fail(c, err.Error())
return
}
if obj.Password != p.Password {
err := errors.New("密码不正确")
back.Fail(c, err.Error())
return
}
var info user.Information
err = DB.First(&info, "id = ?", obj.ID).Error
if err != nil {
back.Err(c, "用户不存在:"+err.Error())
} else {
login(c, &info)
}
}
// AutoLogin 自动登录
func AutoLogin(c *gin.Context) {
var p autoLoginParam
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
if p.UserId > 0 {
var l = user.Login{ID: p.UserId}
err = DB.Model(&l).Updates(user.Login{LoginTime: formatime.NewSecondNow()}).Error
if err != nil {
back.Fail(c, err.Error())
return
}
}
back.Suc(c, "", nil)
}
// UnionID 通过unionID登录
func UnionID(c *gin.Context) {
var p unionIDLoginParam
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
var info user.Information
err = DB.First(&info, "wx_union_id=?", p.UnionID).Error
if err != nil {
back.Err(c, "用户不存在:"+err.Error())
} else {
login(c, &info)
}
}
//上面的几种方式调用的
func login(c *gin.Context, info *user.Information) {
deviceType := cache.GetDeviceType(c)
// 老用户 当主动退出登录或更换手机号码后 再次登录需要重置
var login user.Login
DB.First(&login, "user_id=? and device_type = ?", info.ID, deviceType)
token := tools.Token()
if login.ID > 0 {
err := DB.Model(&login).Updates(user.Login{
Token: token,
IP: c.ClientIP(),
LoginTime: formatime.NewSecondNow(),
}).Error
if err != nil {
back.Err(c, err.Error())
return
}
} else {
login = user.Login{
Token: token,
IP: c.ClientIP(),
LoginTime: formatime.NewSecondNow(),
DeviceType: cache.GetDeviceType(c),
UserID: info.ID,
}
err := DB.Create(&login).Error
if err != nil {
back.Err(c, err.Error())
return
}
}
cache.SetUserLoginCache(&login)
var realInfo user.RealInfo
DB.First(&realInfo, "user_id = ?", info.ID)
isVerified := false
if realInfo.ID > 0 {
isVerified = true
}
if info.Level == 0 && info.ParentID != 0 {
if err := DB.Table(info.TableName()).Where("id=?", info.ID).Update("level", 1).Error; err != nil {
back.Fail(c, err.Error())
}
}
//追加app登录状态
if err := DB.Table(info.TableName()).Where("id=?", info.ID).Update("login_app", 1).Error; err != nil {
back.Fail(c, err.Error())
}
back.Suc(c, "登录成功", &rtnPack{
BasicAuth{
ID: login.ID,
Token: login.Token,
},
(&DetailInfo{}).ReflectedBy(info, isVerified),
1,
})
}
func Aes128Decrypt(crypted, key []byte, IV []byte) ([]byte, error) {
if key == nil || len(key) != 16 {
return nil, nil
}
if IV != nil && len(IV) != 16 {
return nil, nil
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, IV[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return origData, nil
}
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
const (
AppIDH5 = "wx0a67d8af4a8252b0"
AppSecretH5 = "de4494be68c27fb306448f46718cbd13"
APIKeyH5 = "83f8932eb742257316e3168ba9e920dk"
MchIDH5 = "1545449631"
)
// H5GetCode h5获取code
func H5GetCode(c *gin.Context) {
type req struct {
RedirectUrl string `json:"redirectUrl"`
}
var p req
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
url := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect",
AppIDH5,
url.QueryEscape(p.RedirectUrl))
back.Suc(c, "操作成功", gin.H{
"url": url,
})
return
}
type Response struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
OpenId string `json:"openid"`
Scope string `json:"scope"`
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
// H5GetOpenId h5获取openid
func H5GetOpenId(c *gin.Context) {
type req struct {
Code string `json:"code" validate:"required"`
}
var p req
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
url1 := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
AppIDH5, AppSecretH5, p.Code)
resp, err := http.Get(url1)
if err != nil {
panic(err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
back.Fail(c, err.Error())
return
}
var respStruct Response //iponid的结构体
err = json.Unmarshal(respBody, &respStruct)
unionid := GetUser(respStruct)
back.Suc(c, "", gin.H{
"unionid": unionid,
})
return
}
//获取unionID
type wxUserInfo struct {
UnionID string `json:"unionid"`
}
// GetUser 获取用户信息
func GetUser(respStruct Response) string {
url := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", respStruct.AccessToken, respStruct.OpenId)
resp, err := http.Get(url)
if err != nil {
//panic(err)
//log.Println(err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
}
var user_struct wxUserInfo //iponid的结构体
err = json.Unmarshal(respBody, &user_struct)
return user_struct.UnionID
}
type getTokenByToken struct {
Uid uint `json:"uid"`
}
// GetTokenByUid 通过uid获取token
func GetTokenByUid(c *gin.Context) {
var p getTokenByToken
err := tools.ParseParams(&p, c)
if err != nil {
back.Fail(c, err.Error())
return
}
var userLogin user.Login
DB.First(&userLogin, "user_id=?", p.Uid)
back.Suc(c, "", &gin.H{
"token": userLogin.Token,
})
}