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.
 
 
 

270 lines
6.8 KiB

package logic
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang/protobuf/proto"
"github.com/o1egl/govatar"
"math/rand"
"net/http"
"os"
"strings"
"sync"
"texas-poker-bk/api"
"texas-poker-bk/internal/conf"
"texas-poker-bk/internal/dao"
"texas-poker-bk/internal/logic/event"
"texas-poker-bk/internal/logic/store"
"texas-poker-bk/internal/model/entity"
"texas-poker-bk/internal/session"
"texas-poker-bk/tool/collect"
"texas-poker-bk/tool/security"
"time"
)
// http: 登录注册、创建房间
// ws: 大厅、房间、房间状态
var (
SubjectKey = "auth:session"
aesTokenKeyBytes []byte // token aes 加密 key
)
func init() {
keyBytes, err := base64.StdEncoding.DecodeString(conf.Conf.Auth.AesTokenKey)
if err != nil {
panic(err)
}
aesTokenKeyBytes = keyBytes
// 头像文件夹
_ = os.MkdirAll(conf.Conf.Game.AvatarPath, os.ModePerm)
}
// Authorize 登录或注册
func Authorize(ctx *gin.Context) {
username := ctx.PostForm("username")
password := ctx.PostForm("password")
//username := ctx.Param("username")
//password := ctx.Param("password")
if username == "" || password == "" {
ctx.JSON(http.StatusBadRequest, gin.H{"msg": "用户名或密码不能为空"})
return
}
user := dao.UserDao.FindUserByName(username)
unregister := user == nil
if unregister {
var err error = nil
// 注册
user, err = registerUser(username, password)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
} else {
// 校验密码
if user.Password != password {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "用户名或密码有误"})
return
}
}
sub := &session.Subject{
Id: user.Id,
Name: user.Username,
Time: time.Now().UnixMilli(),
Avatar: user.Avatar,
TokenVersion: user.TokenVersion + 1,
}
// dao.UserDao.UpdateTokenVersion(user.Id, sub.TokenVersion, user.TokenVersion)
tVersion, err := dao.UserDao.IncrementTokenVersion(user.Id)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": err.Error()})
return
}
store.TokenVersions.SetDefault(user.Id, tVersion)
token, err := EncodeSubject(sub)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": err.Error()})
return
}
msg := "登录成功"
if unregister {
msg = "注册成功"
}
ctx.JSON(http.StatusOK, gin.H{"msg": msg, "data": gin.H{
"token": token,
"user": gin.H{
"id": user.Id,
"username": user.Username,
"avatar": user.Avatar,
},
}})
}
// 注册用户到DB
func registerUser(username string, password string) (*entity.User, error) {
// 生成头像
avatar := fmt.Sprintf("%d%d%s", time.Now().UnixMilli(), rand.Intn(90)+10, ".jpg")
err := govatar.GenerateFile(govatar.MALE, conf.Conf.Game.AvatarPath+avatar)
// 保存用户信息到db
if err != nil {
return nil, err
}
user := &entity.User{
Username: username,
Password: password,
Balance: conf.Conf.Game.GiftChip,
Avatar: avatar,
Version: 0,
TokenVersion: 0,
}
tx := dao.UserDao.DB.Save(user)
if tx.Error != nil {
return nil, tx.Error
}
return user, nil
}
// SubjectAuthFilter 认证过滤器
func SubjectAuthFilter(ctx *gin.Context) {
// 检查是不需要登录的 url
// ignore auth urls
if collect.IsNotEmptySlice(conf.Conf.Auth.IgnoreUrl) {
for _, ignoreUrl := range conf.Conf.Auth.IgnoreUrl {
if strings.HasPrefix(ctx.Request.URL.Path, ignoreUrl) {
return
}
}
}
auth := ctx.GetHeader("Authorization")
if auth == "" {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "请登录后进行操作"})
ctx.Abort()
return
}
defer func() {
// 捕获aes解析错误
if r := recover(); r != nil {
fmt.Println("request error:", r)
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"})
ctx.Abort()
}
}()
// 这里返回 error,不然后捕获后续执行 handler 的 panic
subject, err := DecodeSubject(auth)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"})
ctx.Abort()
return
}
if subject.TokenVersion != store.TokenVersions.Get(subject.Id) {
ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "登录已过期"})
ctx.Abort()
return
}
// 设置token用户到请求上下文
ctx.Set(SubjectKey, subject)
}
// EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token
// session 客户端对象
func EncodeSubject(subject *session.Subject) (string, error) {
bytes, err := json.Marshal(subject)
if err != nil {
return "", err
}
encode, err := security.EncryptAesCBC(bytes, aesTokenKeyBytes)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(encode), nil
}
// DecodeSubject 解码并解密token返回subject对象
// auth base64Std 编码的 token
func DecodeSubject(auth string) (*session.Subject, error) {
bytes, err := base64.URLEncoding.DecodeString(auth)
if err != nil {
return nil, err
}
decode, err := security.DecryptAesCBC(bytes, aesTokenKeyBytes)
if err != nil {
return nil, err
}
subject := &session.Subject{}
err = json.Unmarshal(decode, subject)
if err != nil {
return nil, err
}
return subject, nil
}
// GetSubject 从请求上下文中获取客户端对象, 请求经过了认证过滤器
func GetSubject(ctx *gin.Context) *session.Subject {
subject, exists := ctx.Get(SubjectKey)
if !exists {
panic("request subject not exists!")
}
return subject.(*session.Subject)
}
func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.Message, error) {
subject, err := DecodeSubject(msg.Token)
if err != nil {
// client.Close("authorize failed!")
return &api.ResFail{Code: 401, Msg: err.Error()}, nil
}
if subject.TokenVersion != store.TokenVersions.Get(subject.Id) {
return &api.ResFail{Code: 401, Msg: "登录已过期"}, nil
}
dbUser := &entity.User{Id: subject.Id}
row := dao.UserDao.DB.First(dbUser).RowsAffected
if row == 0 {
return &api.ResFail{Code: 401, Msg: "账户不存在"}, nil
}
account := store.NetAccounts.Get(subject.Id)
if account == nil {
// 查询DB 账户情况
account = &session.NetAccount{
Id: subject.Id,
UserName: dbUser.Username,
Avatar: dbUser.Avatar,
Balance: dbUser.Balance,
Client: client,
BalanceLock: &sync.RWMutex{},
Lock: &sync.Mutex{},
}
store.NetAccounts.SetDefault(account.Id, account)
} else {
// 掉线重连的用户, 重复登录的用户
account.Client.Write(&api.ResFail{Code: 403, Msg: "该账号在其他地方登录,您已下线"})
account.Client = client
if account.Player != nil {
account.Player.Client = client
}
}
client.Account = account
// 在线通知消息
event.OnlineWatcher.Publish(subject.Id, true)
// response
res := &api.ResIdentity{
Id: subject.Id,
Username: account.UserName,
Avatar: account.Avatar,
Balance: account.Balance,
}
return res, nil
}