user weixinLogin #1

This commit is contained in:
Havoc412 2024-10-17 11:39:05 +08:00
parent a9e8e818be
commit 45d72de8e5
8 changed files with 177 additions and 11 deletions

View File

@ -6,10 +6,13 @@ import (
"catface/app/model"
"catface/app/service/users/curd"
userstoken "catface/app/service/users/token"
"catface/app/service/weixin"
"catface/app/utils/response"
"fmt"
"time"
"github.com/gin-gonic/gin"
)
type Users struct {
@ -17,12 +20,12 @@ type Users struct {
// 1.用户注册
func (u *Users) Register(context *gin.Context) {
// 由于本项目骨架已经将表单验证器的字段(成员)绑定在上下文,因此可以按照 GetString()、context.GetBool()、GetFloat64等快捷获取需要的数据类型注意相关键名规则 前缀+验证器结构体中的 json 标签
// 注意:在 ginskeleton 中获取表单参数验证器中的数字键(字段),请统一使用 GetFloat64(),其它获取数字键字段的函数无效例如GetInt()、GetInt64()等
// 由于本项目骨架已经将表单验证器的字段(成员)绑定在上下文,因此可以按照 GetString()、context.GetBool()、GetFloat64等快捷获取需要的数据类型注意相关键名规则 前缀+验证器结构体中的 json 标签
// ATT 注意:在 ginskeleton 中获取表单参数验证器中的数字键(字段),请统一使用 GetFloat64(),其它获取数字键字段的函数无效例如GetInt()、GetInt64()等
// 当然也可以通过gin框架的上下文原始方法获取例如 context.PostForm("user_name") 获取,这样获取的数据格式为文本,需要自己继续转换
userName := context.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass")
userIp := context.ClientIP()
userIp := context.ClientIP() // INFO 通过上下文获取 IP 信息。
if curd.CreateUserCurdFactory().Register(userName, pass, userIp) {
response.Success(context, consts.CurdStatusOkMsg, "")
} else {
@ -35,6 +38,8 @@ func (u *Users) Login(context *gin.Context) {
userName := context.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass")
phone := context.GetString(consts.ValidatorPrefix + "phone")
// 1. 先检查 账号密码是否正确,然后再检查 Token 状态。
userModelFact := model.CreateUserFactory("")
userModel := userModelFact.Login(userName, pass)
@ -143,3 +148,39 @@ func (u *Users) Destroy(context *gin.Context) {
response.Fail(context, consts.CurdDeleteFailCode, consts.CurdDeleteFailMsg, "")
}
}
// MARK Start by Hav;
func (u *Users) WeixinLogin(context *gin.Context) {
code := context.GetString(consts.ValidatorPrefix + "code")
userAvatar := context.GetString(consts.ValidatorPrefix + "user_avatar")
userName := context.GetString(consts.ValidatorPrefix + "user_name")
userIp := context.ClientIP() // INFO 通过上下文获取 IP 信息。
// 1. 访问 微信 API 获取 openid
openId, err := weixin.Code2Session(code)
if err != nil {
// 解析微信登录成功,返回用户信息
fmt.Println(err) // TODO 换成 LOG
response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "")
}
// 2. 执行 CURD
if UsersModel, err := curd.CreateUserFactory().WeixinLogin(openId, userName, userAvatar) {
if userId > 0 {
// 3. 生成 token
token, err := userstoken.CreateUserFactory().GenerateToken(userId, userName, "", 0)
if err != nil {
response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "")
}
// 4. 返回 token
res := gin.H{
"userId": UsersModel.userId,
"permission": UsersModel.Permission
"token": token,
}
}
} else {
response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "")
}
}

View File

@ -21,6 +21,10 @@ func WebRegisterValidator() {
containers.Set(key, users.Register{})
key = consts.ValidatorPrefix + "UsersLogin"
containers.Set(key, users.Login{})
key = consts.ValidatorPrefix + "UsersWeixinLogin"
containers.Set(key, users.WeixinLogin{})
key = consts.ValidatorPrefix + "RefreshToken"
containers.Set(key, users.RefreshToken{})

View File

@ -32,5 +32,4 @@ func (l Login) CheckParams(context *gin.Context) {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).Login(extraAddBindDataContext)
}
}

View File

@ -0,0 +1,33 @@
package users
import (
"catface/app/global/consts"
"catface/app/http/controller/web"
"catface/app/http/validator/core/data_transfer"
"catface/app/utils/response"
"github.com/gin-gonic/gin"
)
type WeixinLogin struct {
Code string `json:"code"`
UserName string `json:"user_name"`
UserAvatar string `json:"user_avatar"` // INFO 本地缓存,不确定 url 的类型。
}
func (w WeixinLogin) CheckParams(context *gin.Context) {
//1.先按照验证器提供的基本语法基本可以校验90%以上的不合格参数
if err := context.ShouldBind(&w); err != nil {
response.ValidatorError(context, err)
return
}
// INFO 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(w, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil {
response.ErrorSystem(context, "userLogin表单验证器json化失败", "")
} else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).WeixinLogin(extraAddBindDataContext)
}
}

View File

@ -7,6 +7,7 @@ import (
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
// 操作数据库喜欢使用gorm自带语法的开发者可以参考 GinSkeleton-Admin 系统相关代码
@ -22,24 +23,25 @@ func CreateUserFactory(sqlType string) *UsersModel {
type UsersModel struct {
BaseModel
UserName string `gorm:"column:user_name" json:"user_name"`
UserName string `gorm:"column:user_name;size:20" json:"user_name"`
Pass string `json:"-"` // INFO 暂时用不到,但先保留。
Phone string `json:"phone"`
RealName string `gorm:"column:real_name" json:"real_name"`
// TAG 状态管理
Status int `json:"status"` // QUESTION
Status uint8 `json:"status"` // QUESTION
Token string `json:"token"`
LastLoginIp string `gorm:"column:last_login_ip" json:"last_login_ip"`
// TAG MySELF
Permissions int `json:"permissions"`
UserAvatar string `gorm:"column:user_avatar;size:255" json:"user_avatar"` // TODO 暂时存储 url之后考虑需要把文件上传到 Nginx
Permission uint8 `json:"permission" gorm:"default:9"`
// TAG 微信登录相关
OpenId string `gorm:"column:open_id;size:35" json:"open_id"`
OpenId string `gorm:"column:open_id;size:35;index" json:"open_id"`
SessionKey string `gorm:"column:session_key;size:35" json:"session_key"`
}
// 表名
func (u *UsersModel) TableName() string {
return "tb_users"
func (u *UsersModel) TableName() string { // TIP GORM 也会自动调用这个函数。
return "users"
}
// 用户注册(写一个最简单的使用账号、密码注册即可)
@ -306,3 +308,26 @@ func (u *UsersModel) DelTokenCacheFromRedis(userId int64) {
tokenCacheRedisFact.ClearUserToken()
tokenCacheRedisFact.ReleaseRedisConn()
}
/**
* @description
* @return {*}
*/
func (u *UsersModel) WeixinLogin(openId string, name string, avatar string) (temp *UsersModel, err error) {
db := u.DB
var user UsersModel
if result := db.Where("open_id = ?", openId).First(&user); result.Error != nil {
temp = &user
} else if result.Error == gorm.ErrRecordNotFound {
newUser := UsersModel{OpenId: openId, UserName: name, UserAvatar: avatar}
if err := db.Create(&newUser).Error; err != nil {
return nil, err
}
temp = &newUser // INFO 这里应该就是 GORM 插入后得到的对象。
} else {
return nil, result.Error
}
return temp, nil
}

View File

@ -0,0 +1,39 @@
package weixin
import (
"catface/app/global/variable"
"fmt"
"io"
"net/http"
)
func Code2Session(js_code string) (string, error) {
appid := variable.ConfigYml.GetString("Weixin.AppId")
appSecret := variable.ConfigYml.GetString("Weixin.AppSecret")
grantType := variable.ConfigYml.GetString("Weixin.Code2Session.GrantType")
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=%s", appid, appSecret, js_code, grantType)
// 创建一个新的HTTP请求
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return "", fmt.Errorf("error creating request: %v", err)
}
// 发送HTTP请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("error reading response body: %v", err)
}
// 返回响应体
return string(body), nil
}

View File

@ -140,4 +140,12 @@ RabbitMq:
Captcha:
captchaId: "captcha_id" # 验证码id提交时的键名
captchaValue: "captcha_value" #验证码值提交时的键名
length: 4 # 验证码生成时的长度
length: 4 # 验证码生成时的长度
WeixinServer:
AppId: "wxe1ff76a57cc6eed3"
AppSecret: "46a3557653462da34c6e69f17a472c7c"
Code2Session:
grant_type: "authorization_code" # 主要就是想避免硬编码。

17
test/usersModel_test.go Normal file
View File

@ -0,0 +1,17 @@
// add_test.go
package test
import (
"catface/app/model"
"testing"
)
func TestUsers(t *testing.T) {
Init()
user := model.UsersModel{}
err := DB.AutoMigrate(&user)
if err != nil {
t.Error(err)
}
}