✨ Init
This commit is contained in:
178
oauth2/handle/oauth2.go
Normal file
178
oauth2/handle/oauth2.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
er "errors"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/logger/log"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/go-oauth2/oauth2/v4/errors"
|
||||
"miniapp/client"
|
||||
"miniapp/model/app"
|
||||
"miniapp/model/cache"
|
||||
"miniapp/model/common/constant"
|
||||
"miniapp/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserAuthorizationHandler 获取用户Id
|
||||
func UserAuthorizationHandler(w http.ResponseWriter, r *http.Request) (userId string, err error) {
|
||||
loginType := constant.LoginType(r.FormValue("type")) // 登录类型
|
||||
userIdentity := constant.UserIdentity(r.FormValue("identity")) // 身份类型
|
||||
account := r.FormValue("username") // 用户传入账号(或者授权Code)
|
||||
nikeName := r.FormValue("nickName") // 昵称
|
||||
avatarUrl := r.FormValue("avatarUrl") // 头像
|
||||
|
||||
log.Debugf("预处理用户登录请求,身份类型: %v => 登录类型: %s => 账号: %v", userIdentity, loginType, account)
|
||||
|
||||
var roleCode []string
|
||||
// 处理用户Id
|
||||
switch userIdentity {
|
||||
case constant.UserIdentityAdmin:
|
||||
// 管理员
|
||||
case constant.UserIdentityUser:
|
||||
// 普通用户
|
||||
userId, err = getUser(account, loginType, nikeName, avatarUrl)
|
||||
default:
|
||||
err = er.New("未知的用户身份类型")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 组装缓存用户信息
|
||||
m := cache.UserInfo{
|
||||
RoleCodes: strings.Join(roleCode, ","),
|
||||
UserId: userId,
|
||||
UserType: userIdentity.String(),
|
||||
}
|
||||
userInfo, err := m.String()
|
||||
if err != nil {
|
||||
err = errors.New("登录失败,请联系管理员")
|
||||
return
|
||||
}
|
||||
if err = client.Redis.Set(context.Background(), fmt.Sprintf("%s%v", constant.OAuth2UserCacheKey, userId), userInfo, time.Hour*24*7).Err(); err != nil {
|
||||
log.Errorf("缓存用户信息失败,用户ID:%v,错误信息:%s", userId, err.Error())
|
||||
err = errors.New("登录失败,请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoginWithPassword 账号密码登录模式
|
||||
func LoginWithPassword(ctx context.Context, clientId, userId, password string) (userID string, err error) {
|
||||
log.Debugf("[%v]处理登录请求,用户Id:%s --> %s", clientId, userId, password)
|
||||
|
||||
userID = userId
|
||||
return
|
||||
}
|
||||
|
||||
// CheckClient 检查是否允许该客户端通过该授权模式请求令牌
|
||||
func CheckClient(clientID string, grant oauth2.GrantType) (allowed bool, err error) {
|
||||
// 解出租户Id和传入的客户端Id
|
||||
c := app.OAuth2Client{ClientId: clientID}
|
||||
|
||||
// 查询客户端配置信息
|
||||
if err = service.ServiceGroupApp.AppServiceGroup.Oauth2ClientService.FindOne(&c); err != nil {
|
||||
log.Errorf("客户端信息查询失败: %v", err.Error())
|
||||
err = errors.New("客户端信息查询失败: " + err.Error())
|
||||
allowed = false
|
||||
return
|
||||
}
|
||||
// 判断是否包含授权范围
|
||||
allowed = strings.Contains(c.Grant, string(grant))
|
||||
if !allowed {
|
||||
err = errors.New("不受允许的grant_type")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExtensionFields 自定义响应Token的扩展字段
|
||||
func ExtensionFields(ti oauth2.TokenInfo) (fieldsValue map[string]any) {
|
||||
fieldsValue = map[string]any{}
|
||||
fieldsValue["license"] = "Made By Lee"
|
||||
|
||||
// 取出用户信息
|
||||
var userInfo app.User
|
||||
tid, _ := strconv.Atoi(ti.GetUserID())
|
||||
userInfo.ID = uint(tid)
|
||||
//if err := repository.User().GetUser(&userInfo); err != nil {
|
||||
// return
|
||||
//}
|
||||
service.ServiceGroupApp.AppServiceGroup.UserService.GetUser(&userInfo)
|
||||
fieldsValue["newUser"] = time.Now().Sub(userInfo.CreatedAt).Minutes() <= 1
|
||||
|
||||
fieldsValue["nickname"] = userInfo.Nickname
|
||||
fieldsValue["phone"] = userInfo.Phone
|
||||
fieldsValue["userId"] = userInfo.ID
|
||||
fieldsValue["avatar"] = userInfo.Avatar
|
||||
fieldsValue["TimeNote"] = userInfo.Avatar
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ResponseToken 返回Token生成结果
|
||||
func ResponseToken(w http.ResponseWriter, data map[string]any, header http.Header, statusCode ...int) error {
|
||||
log.Debugf("返回Token原始数据: %+v", data)
|
||||
type response struct {
|
||||
Code int `json:"code"`
|
||||
Data map[string]any `json:"data"`
|
||||
Msg string `json:"message"`
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
msg := "login success"
|
||||
if len(statusCode) > 0 && statusCode[0] > 0 {
|
||||
status = statusCode[0]
|
||||
msg = fmt.Sprintf("%v", data["error_description"])
|
||||
// 处理特殊返回 - 刷新Token到期了
|
||||
switch data["error"] {
|
||||
case "invalid_grant":
|
||||
msg = "登录已过期,请重新授权登录"
|
||||
case "invalid_request":
|
||||
msg = "登录参数错误"
|
||||
default:
|
||||
log.Errorf("收到未定义的登录错误: %v", data["error_description"])
|
||||
}
|
||||
data = nil
|
||||
}
|
||||
|
||||
res := response{
|
||||
Code: status,
|
||||
Msg: msg,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
||||
for key := range header {
|
||||
w.Header().Set(key, header.Get(key))
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
_, err = w.Write(jsonBytes)
|
||||
if err != nil {
|
||||
log.Errorf("返回Token失败: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// InternalErrorHandler 自定义内部错误处理
|
||||
func InternalErrorHandler(err error) (re *errors.Response) {
|
||||
re = errors.NewResponse(err, http.StatusUnauthorized)
|
||||
re.Description = err.Error()
|
||||
return
|
||||
}
|
65
oauth2/handle/token_gen.go
Normal file
65
oauth2/handle/token_gen.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/logger/log"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/google/uuid"
|
||||
"miniapp/client"
|
||||
"miniapp/model/common/constant"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewAccessGenerate() *AccessGenerate {
|
||||
return &AccessGenerate{}
|
||||
}
|
||||
|
||||
type AccessGenerate struct {
|
||||
}
|
||||
|
||||
// Token 手动实现Token生成,直接生成UUID,替换掉自带的那个憨得一批的长长的字符串
|
||||
func (ag *AccessGenerate) Token(ctx context.Context, data *oauth2.GenerateBasic, isGenRefresh bool) (string, string, error) {
|
||||
u, _ := uuid.NewUUID()
|
||||
access := strings.ReplaceAll(u.String(), "-", "")
|
||||
|
||||
refresh := ""
|
||||
if isGenRefresh {
|
||||
u, _ = uuid.NewUUID()
|
||||
refresh = strings.ReplaceAll(u.String(), "-", "")
|
||||
}
|
||||
// 生成新的,清理掉旧的
|
||||
ag.clearOldToken(ctx, data.UserID)
|
||||
// 返回结果
|
||||
return access, refresh, nil
|
||||
}
|
||||
|
||||
// 清理掉旧的Token和RefreshToken
|
||||
func (ag *AccessGenerate) clearOldToken(ctx context.Context, userId string) {
|
||||
rdsKey := constant.OAuth2RedisKey + "*-*"
|
||||
// 获取所有符合条件的Key
|
||||
keys, err := client.Redis.Keys(ctx, rdsKey).Result()
|
||||
if err != nil {
|
||||
log.Errorf("清理失败,跳过清理")
|
||||
return
|
||||
}
|
||||
for _, key := range keys {
|
||||
dataStr, err := client.Redis.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var m map[string]any
|
||||
if err = json.Unmarshal([]byte(dataStr), &m); err != nil {
|
||||
continue
|
||||
}
|
||||
// 找到匹配的数据
|
||||
if m["UserID"] == userId {
|
||||
// 删除AccessToken
|
||||
client.Redis.Del(ctx, fmt.Sprintf("%v%v", constant.OAuth2RedisKey, m["Access"]))
|
||||
client.Redis.Del(ctx, fmt.Sprintf("%v%v", constant.OAuth2RedisKey, m["Refresh"]))
|
||||
client.Redis.Del(ctx, key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
69
oauth2/handle/user.go
Normal file
69
oauth2/handle/user.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.echol.cn/loser/logger/log"
|
||||
"miniapp/model/app"
|
||||
"miniapp/model/common/constant"
|
||||
"miniapp/service"
|
||||
"miniapp/utils"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 获取普通用户信息
|
||||
func getUser(account string, loginType constant.LoginType, nikeName string, avatarUrl string) (userId string, err error) {
|
||||
// 根据登录类型获取用户信息
|
||||
|
||||
// 定义微信小程序信息
|
||||
//var unionId, openId, sessionKey string
|
||||
var mobile string
|
||||
// 定义用户信息
|
||||
var user app.User
|
||||
//user.InviteCode = &inviteCode
|
||||
switch loginType {
|
||||
case constant.LoginTypeWeChatMiniApp:
|
||||
mobile, err = utils.WeChatUtils().GetPhoneNumber(account)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if mobile == "" {
|
||||
err = errors.New("获取手机号失败")
|
||||
return
|
||||
}
|
||||
user.Phone = mobile
|
||||
user.Nickname = nikeName
|
||||
user.Avatar = avatarUrl
|
||||
default:
|
||||
user.Phone = account
|
||||
user.Nickname = nikeName
|
||||
user.Avatar = avatarUrl
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
if err = service.ServiceGroupApp.AppServiceGroup.UserService.GetOrCreate(&user); err != nil {
|
||||
log.Errorf("获取用户信息或创建用户失败,错误信息:%s", err.Error())
|
||||
err = errors.New("登录失败,请联系管理员")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验用户状态
|
||||
if user.Status == constant.UserStatusDisabled {
|
||||
err = errors.New("账户已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
// 异步缓存小程序SessionKey
|
||||
//go func() {
|
||||
// if loginType == constant.LoginTypeWeChatMiniApp {
|
||||
// // 缓存SessionKey
|
||||
// if client.Redis.Set(context.Background(), constant.WeChatSessionKey+user.Id, sessionKey, 3*24*time.Hour).Err() != nil {
|
||||
// log.Errorf("缓存SessionKey失败,用户Id:%s", user.Id)
|
||||
// }
|
||||
// }
|
||||
//}()
|
||||
|
||||
// 返回用户Id
|
||||
userId = strconv.Itoa(int(user.ID))
|
||||
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user