diff --git a/api/oauth_api/oauth.go b/api/oauth_api/oauth.go index 9f9c131..ce86bf8 100644 --- a/api/oauth_api/oauth.go +++ b/api/oauth_api/oauth.go @@ -2,22 +2,30 @@ package oauth_api import ( "encoding/json" + "errors" "fmt" "github.com/gin-gonic/gin" + "github.com/mssola/useragent" + "gorm.io/gorm" "net/http" "schisandra-cloud-album/api/user_api/dto" "schisandra-cloud-album/common/constant" "schisandra-cloud-album/common/redis" "schisandra-cloud-album/global" + "schisandra-cloud-album/model" "schisandra-cloud-album/service" "schisandra-cloud-album/utils" + "sync" "time" ) +var mu sync.Mutex + type OAuthAPI struct{} var userService = service.Service.UserService var userSocialService = service.Service.UserSocialService +var userDeviceService = service.Service.UserDeviceService type Token struct { AccessToken string `json:"access_token"` @@ -116,3 +124,67 @@ func HandelUserLogin(userId string) (bool, map[string]interface{}) { } return true, responseData } + +// GetUserLoginDevice 获取用户登录设备 +func (OAuthAPI) GetUserLoginDevice(c *gin.Context) { + userId := c.Query("user_id") + if userId == "" { + return + } + userAgent := c.GetHeader("User-Agent") + if userAgent == "" { + global.LOG.Errorln("user-agent is empty") + return + } + ua := useragent.New(userAgent) + + ip := utils.GetClientIP(c) + location, err := global.IP2Location.SearchByStr(ip) + location = utils.RemoveZeroAndAdjust(location) + if err != nil { + global.LOG.Errorln(err) + return + } + isBot := ua.Bot() + browser, browserVersion := ua.Browser() + os := ua.OS() + mobile := ua.Mobile() + mozilla := ua.Mozilla() + m := ua.Model() + platform := ua.Platform() + engine, engineVersion := ua.Engine() + device := model.ScaAuthUserDevice{ + UserID: &userId, + IP: &ip, + Location: &location, + Agent: userAgent, + Browser: &browser, + BrowserVersion: &browserVersion, + OperatingSystem: &os, + Mobile: &mobile, + Bot: &isBot, + Mozilla: &mozilla, + Model: &m, + Platform: &platform, + EngineName: &engine, + EngineVersion: &engineVersion, + } + mu.Lock() + defer mu.Unlock() + userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgent(userId, ip, userAgent) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = userDeviceService.AddUserDevice(&device) + if err != nil { + global.LOG.Errorln(err) + return + } + return + } else { + err := userDeviceService.UpdateUserDevice(userDevice.ID, &device) + if err != nil { + global.LOG.Errorln(err) + return + } + return + } +} diff --git a/api/oauth_api/wechat_api.go b/api/oauth_api/wechat_api.go index ae9303f..2f08bc0 100644 --- a/api/oauth_api/wechat_api.go +++ b/api/oauth_api/wechat_api.go @@ -26,12 +26,9 @@ import ( "schisandra-cloud-album/utils" "strconv" "strings" - "sync" "time" ) -var mu sync.Mutex - // GenerateClientId 生成客户端ID // @Summary 生成客户端ID // @Description 生成客户端ID @@ -95,7 +92,7 @@ func (OAuthAPI) CallbackNotify(c *gin.Context) { return "error" } fmt.Dump(msg) - return messages.NewText("再见,我的宝!") + return messages.NewText("ok") case models.CALLBACK_EVENT_SCAN: msg := models.EventScan{} diff --git a/api/user_api/user.go b/api/user_api/user.go index 5b911c8..c0e79d2 100644 --- a/api/user_api/user.go +++ b/api/user_api/user.go @@ -1,3 +1,7 @@ package user_api +import "sync" + type UserAPI struct{} + +var mu sync.Mutex diff --git a/api/user_api/user_api.go b/api/user_api/user_api.go index 8f3317c..0265bb4 100644 --- a/api/user_api/user_api.go +++ b/api/user_api/user_api.go @@ -1,9 +1,12 @@ package user_api import ( + "errors" ginI18n "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" + "github.com/mssola/useragent" "github.com/yitter/idgenerator-go/idgen" + "gorm.io/gorm" "reflect" "schisandra-cloud-album/api/user_api/dto" "schisandra-cloud-album/common/constant" @@ -19,6 +22,7 @@ import ( ) var userService = service.Service.UserService +var userDeviceService = service.Service.UserDeviceService // GetUserList // @Summary 获取所有用户列表 @@ -161,62 +165,87 @@ func (UserAPI) PhoneLogin(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneAndCaptchaNotEmpty"), c) return } - isPhone := utils.IsPhone(phone) - if !isPhone { + if !utils.IsPhone(phone) { result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c) return } - user := userService.QueryUserByPhone(phone) + userChan := make(chan model.ScaAuthUser) + go func() { + user := userService.QueryUserByPhone(phone) + userChan <- user + }() + + user := <-userChan + close(userChan) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { // 未注册 - code := redis.Get(constant.UserLoginSmsRedisKey + phone) + codeChan := make(chan *string) + go func() { + code := redis.Get(constant.UserLoginSmsRedisKey + phone).Val() + codeChan <- &code + }() + + code := <-codeChan + close(codeChan) + if code == nil { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaExpired"), c) return - } else { - uid := idgen.NextId() - uidStr := strconv.FormatInt(uid, 10) - createUser := model.ScaAuthUser{ - UID: &uidStr, - Phone: &phone, - } - addUser, err := userService.AddUser(createUser) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "RegisterUserError"), c) - return - } + } - _, err = global.Casbin.AddRoleForUser(uidStr, enum.User) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "RegisterUserError"), c) - return - } - err = global.Casbin.SavePolicy() - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "RegisterUserError"), c) - return - } - handelUserLogin(addUser, request.AutoLogin, c) + uid := idgen.NextId() + uidStr := strconv.FormatInt(uid, 10) + createUser := model.ScaAuthUser{ + UID: &uidStr, + Phone: &phone, + } + + errChan := make(chan error) + go func() { + err := global.DB.Transaction(func(tx *gorm.DB) error { + addUser, err := userService.AddUser(createUser) + if err != nil { + return err + } + _, err = global.Casbin.AddRoleForUser(uidStr, enum.User) + if err != nil { + return err + } + handelUserLogin(addUser, request.AutoLogin, c) + return nil + }) + errChan <- err + }() + + err := <-errChan + close(errChan) + + if err != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "RegisterUserError"), c) return } } else { - code := redis.Get(constant.UserLoginSmsRedisKey + phone) + codeChan := make(chan *string) + go func() { + code := redis.Get(constant.UserLoginSmsRedisKey + phone).Val() + codeChan <- &code + }() + + code := <-codeChan + close(codeChan) + if code == nil { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaExpired"), c) return - } else { - if captcha != code.Val() { - result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c) - return - } else { - handelUserLogin(user, request.AutoLogin, c) - return - } } - + if &captcha != code { + result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c) + return + } + handelUserLogin(user, request.AutoLogin, c) } - } // RefreshHandler 刷新token @@ -273,6 +302,10 @@ func handelUserLogin(user model.ScaAuthUser, autoLogin bool, c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } + if !getUserLoginDevice(user, c) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID}) if err != nil { result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) @@ -393,3 +426,62 @@ func (UserAPI) ResetPassword(c *gin.Context) { tx.Commit() result.OkWithMessage(ginI18n.MustGetMessage(c, "ResetPasswordSuccess"), c) } + +// getUserLoginDevice 获取用户登录设备 +func getUserLoginDevice(user model.ScaAuthUser, c *gin.Context) bool { + userAgent := c.GetHeader("User-Agent") + if userAgent == "" { + global.LOG.Errorln("user-agent is empty") + return false + } + ua := useragent.New(userAgent) + + ip := utils.GetClientIP(c) + location, err := global.IP2Location.SearchByStr(ip) + location = utils.RemoveZeroAndAdjust(location) + if err != nil { + global.LOG.Errorln(err) + return false + } + isBot := ua.Bot() + browser, browserVersion := ua.Browser() + os := ua.OS() + mobile := ua.Mobile() + mozilla := ua.Mozilla() + m := ua.Model() + platform := ua.Platform() + engine, engineVersion := ua.Engine() + device := model.ScaAuthUserDevice{ + UserID: user.UID, + IP: &ip, + Location: &location, + Agent: userAgent, + Browser: &browser, + BrowserVersion: &browserVersion, + OperatingSystem: &os, + Mobile: &mobile, + Bot: &isBot, + Mozilla: &mozilla, + Model: &m, + Platform: &platform, + EngineName: &engine, + EngineVersion: &engineVersion, + } + mu.Lock() + defer mu.Unlock() + userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgent(*user.UID, ip, userAgent) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = userDeviceService.AddUserDevice(&device) + if err != nil { + global.LOG.Errorln(err) + return false + } + return true + } else { + err := userDeviceService.UpdateUserDevice(userDevice.ID, &device) + if err != nil { + return false + } + return true + } +} diff --git a/api/websocket_api/gws_api.go b/api/websocket_api/gws_api.go index 6d31ce6..1187ad0 100644 --- a/api/websocket_api/gws_api.go +++ b/api/websocket_api/gws_api.go @@ -91,7 +91,7 @@ func (c *WebSocket) OnPing(socket *gws.Conn, payload []byte) { _ = socket.WritePong(payload) } -func (c *WebSocket) OnPong(socket *gws.Conn, payload []byte) {} +func (c *WebSocket) OnPong(_ *gws.Conn, _ []byte) {} func (c *WebSocket) OnMessage(socket *gws.Conn, message *gws.Message) { defer message.Close() diff --git a/core/ip2region.go b/core/ip2region.go new file mode 100644 index 0000000..073f080 --- /dev/null +++ b/core/ip2region.go @@ -0,0 +1,22 @@ +package core + +import ( + "github.com/lionsoul2014/ip2region/binding/golang/xdb" + "schisandra-cloud-album/global" +) + +func InitIP2Region() { + var dbPath = "ip2region/ip2region.xdb" + cBuff, err := xdb.LoadContentFromFile(dbPath) + if err != nil { + global.LOG.Errorf("failed to load vector index from `%s`: %s\n", dbPath, err) + return + } + searcher, err := xdb.NewWithBuffer(cBuff) + if err != nil { + global.LOG.Errorf("failed to create searcher with vector index: %s\n", err) + return + } + global.IP2Location = searcher + return +} diff --git a/global/global.go b/global/global.go index 999a0e1..078b00f 100644 --- a/global/global.go +++ b/global/global.go @@ -3,6 +3,7 @@ package global import ( "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount" "github.com/casbin/casbin/v2" + "github.com/lionsoul2014/ip2region/binding/golang/xdb" "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" "github.com/wenlng/go-captcha/v2/click" @@ -26,4 +27,5 @@ var ( REDIS *redis.Client // redis连接 Wechat *officialAccount.OfficialAccount // 微信公众号 Casbin *casbin.CachedEnforcer // casbin权限管理器 + IP2Location *xdb.Searcher // IP地址定位 ) diff --git a/go.mod b/go.mod index b558046..ce2efc3 100644 --- a/go.mod +++ b/go.mod @@ -76,11 +76,14 @@ require ( github.com/klauspost/compress v1.17.5 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/microsoft/go-mssqldb v1.6.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mssola/useragent v1.0.0 // indirect github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect diff --git a/go.sum b/go.sum index 1dd845a..a8e012d 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 h1:YeIGErDiB/fhmNsJy0cfjoT8XnRNT9hb19xZ4MvWQDU= +github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs= github.com/lxzan/gws v1.8.5 h1:6x+wW3EHtoGFNeCtZP1OVZ1IHrpZZzDaEjQGg1lUJqU= github.com/lxzan/gws v1.8.5/go.mod h1:FcGeRMB7HwGuTvMLR24ku0Zx0p6RXqeKASeMc4VYgi4= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -177,6 +179,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -184,6 +188,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o= +github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= diff --git a/i18n/language/en.toml b/i18n/language/en.toml index 1e97279..dd9ec8d 100644 --- a/i18n/language/en.toml +++ b/i18n/language/en.toml @@ -53,3 +53,4 @@ QRCodeGetFailed = "qr code get failed!" QRCodeGetSuccess = "qr code get successfully!" QRCodeExpired = "qr code expired!" InternalError = "internal error!" +RequestError = "request error!" diff --git a/i18n/language/zh.toml b/i18n/language/zh.toml index bdc8b80..435adab 100644 --- a/i18n/language/zh.toml +++ b/i18n/language/zh.toml @@ -53,4 +53,5 @@ QRCodeGetFailed = "获取二维码失败!" QRCodeGetSuccess = "获取二维码成功!" QRCodeExpired = "二维码已过期!" InternalError = "内部错误!" +RequestError = "请求错误!" diff --git a/ip2region/ip2region.xdb b/ip2region/ip2region.xdb new file mode 100644 index 0000000..7052c05 Binary files /dev/null and b/ip2region/ip2region.xdb differ diff --git a/main.go b/main.go index 871ce14..2f7200c 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func main() { core.InitIDGenerator() // 初始化ID生成器 core.InitWechat() // 初始化微信 core.InitCasbin() // 初始化Casbin + core.InitIP2Region() // 初始化IP2Region // 命令行参数绑定 option := cmd.Parse() if cmd.IsStopWeb(&option) { @@ -26,7 +27,7 @@ func main() { } r := router.InitRouter() // 初始化路由 addr := global.CONFIG.System.Addr() - global.LOG.Info("Server run on ", addr) + err := r.Run(addr) if err != nil { global.LOG.Fatalf(err.Error()) diff --git a/model/sca_auth_user_device.go b/model/sca_auth_user_device.go index fc53779..8955ee6 100644 --- a/model/sca_auth_user_device.go +++ b/model/sca_auth_user_device.go @@ -9,10 +9,10 @@ const TableNameScaAuthUserDevice = "sca_auth_user_device" // ScaAuthUserDevice 用户设备信息 type ScaAuthUserDevice struct { ID int64 `gorm:"column:id;type:bigint(20);primaryKey;comment:主键ID" json:"id"` // 主键ID - UserID *int64 `gorm:"column:user_id;type:bigint(20);comment:用户ID" json:"user_id"` // 用户ID + UserID *string `gorm:"column:user_id;type:varchar(255);comment:用户ID" json:"user_id"` // 用户ID IP *string `gorm:"column:ip;type:varchar(255);comment:登录IP" json:"ip"` // 登录IP Location *string `gorm:"column:location;type:varchar(255);comment:地址" json:"location"` // 地址 - Agent *string `gorm:"column:agent;type:varchar(255);comment:设备信息" json:"agent"` // 设备信息 + Agent string `gorm:"column:agent;type:longtext;comment:设备信息" json:"agent"` // 设备信息 ExtJSON *string `gorm:"column:ext_json;type:varchar(255);comment:额外字段" json:"ext_json"` // 额外字段 CreatedBy *string `gorm:"column:created_by;type:varchar(32);comment:创建人" json:"created_by"` // 创建人 CreatedTime *time.Time `gorm:"column:created_time;type:datetime;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_time"` // 创建时间 @@ -22,6 +22,13 @@ type ScaAuthUserDevice struct { Browser *string `gorm:"column:browser;type:varchar(255);comment:浏览器" json:"browser"` // 浏览器 OperatingSystem *string `gorm:"column:operating_system;type:varchar(255);comment:操作系统" json:"operating_system"` // 操作系统 BrowserVersion *string `gorm:"column:browser_version;type:varchar(255);comment:浏览器版本" json:"browser_version"` // 浏览器版本 + Mobile *bool `gorm:"column:mobile;type:int(11);comment:是否为手机" json:"mobile"` // 是否为手机 + Bot *bool `gorm:"column:bot;type:int(11);comment:是否为机器人" json:"bot"` // 是否为机器人 + Mozilla *string `gorm:"column:mozilla;type:varchar(10);comment:火狐版本" json:"mozilla"` // 火狐版本 + Model *string `gorm:"column:model;type:varchar(20);comment:设备型号" json:"model"` // 设备型号 + Platform *string `gorm:"column:platform;type:varchar(20);comment:平台" json:"platform"` // 平台 + EngineName *string `gorm:"column:engine_name;type:varchar(20);comment:引擎名称" json:"engine_name"` // 引擎名称 + EngineVersion *string `gorm:"column:engine_version;type:varchar(20);comment:引擎版本" json:"engine_version"` // 引擎版本 } // TableName ScaAuthUserDevice's table name diff --git a/router/modules/oauth_router.go b/router/modules/oauth_router.go index 7ffc65f..ae7d0d0 100644 --- a/router/modules/oauth_router.go +++ b/router/modules/oauth_router.go @@ -32,6 +32,7 @@ func OauthRouter(router *gin.RouterGroup) { qqRouter.GET("/get_url", oauth.GetQQRedirectUrl) qqRouter.GET("/callback", oauth.QQCallback) } + group.GET("/get_device", oauth.GetUserLoginDevice) } } diff --git a/service/service.go b/service/service.go index ad3942a..a8c587a 100644 --- a/service/service.go +++ b/service/service.go @@ -3,6 +3,7 @@ package service import ( "schisandra-cloud-album/service/permission_service" "schisandra-cloud-album/service/role_service" + "schisandra-cloud-album/service/user_device_service" "schisandra-cloud-album/service/user_service" "schisandra-cloud-album/service/user_social_service" ) @@ -13,6 +14,7 @@ type Services struct { RoleService role_service.RoleService PermissionService permission_service.PermissionService UserSocialService user_social_service.UserSocialService + UserDeviceService user_device_service.UserDeviceService } // Service new函数实例化,实例化完成后会返回结构体地指针类型 diff --git a/service/user_device_service/user_device.go b/service/user_device_service/user_device.go new file mode 100644 index 0000000..f03077c --- /dev/null +++ b/service/user_device_service/user_device.go @@ -0,0 +1,3 @@ +package user_device_service + +type UserDeviceService struct{} diff --git a/service/user_device_service/user_device_service.go b/service/user_device_service/user_device_service.go new file mode 100644 index 0000000..0643a50 --- /dev/null +++ b/service/user_device_service/user_device_service.go @@ -0,0 +1,46 @@ +package user_device_service + +import ( + "schisandra-cloud-album/global" + "schisandra-cloud-album/model" +) + +// AddUserDevice 新增用户设备信息 +func (UserDeviceService) AddUserDevice(userDevice *model.ScaAuthUserDevice) error { + if err := global.DB.Create(&userDevice).Error; err != nil { + return err + } + return nil +} + +// GetUserDeviceByUIDIPAgent 根据uid / IP / agent 查询用户设备信息 +func (UserDeviceService) GetUserDeviceByUIDIPAgent(uid, ip, agent string) (*model.ScaAuthUserDevice, error) { + var userDevice model.ScaAuthUserDevice + if err := global.DB.Where("user_id =? AND ip =? AND agent =? AND deleted = 0 ", uid, ip, agent).First(&userDevice).Error; err != nil { + return nil, err + } + return &userDevice, nil +} + +// UpdateUserDevice 更新用户设备信息 +func (UserDeviceService) UpdateUserDevice(id int64, userDevice *model.ScaAuthUserDevice) error { + result := global.DB.Model(&userDevice).Where("id =? AND deleted = 0 ", id).Updates(model.ScaAuthUserDevice{ + IP: userDevice.IP, + Location: userDevice.Location, + Agent: userDevice.Agent, + Browser: userDevice.Browser, + BrowserVersion: userDevice.BrowserVersion, + OperatingSystem: userDevice.OperatingSystem, + Mobile: userDevice.Mobile, + Bot: userDevice.Bot, + Mozilla: userDevice.Mozilla, + Model: userDevice.Model, + Platform: userDevice.Platform, + EngineName: userDevice.EngineName, + EngineVersion: userDevice.EngineVersion, + }) + if result.Error != nil { + return result.Error + } + return nil +} diff --git a/utils/ip2location.go b/utils/ip2location.go new file mode 100644 index 0000000..80d77e5 --- /dev/null +++ b/utils/ip2location.go @@ -0,0 +1,21 @@ +package utils + +import ( + "regexp" + "strings" +) + +func RemoveZeroAndAdjust(s string) string { + // 正则表达式匹配 "|0|" 或 "|0" 或 "0|" 并替换为 "|" + re := regexp.MustCompile(`(\|0|0\||0)`) + result := re.ReplaceAllString(s, "|") + + // 移除可能出现的连续 "|" + re = regexp.MustCompile(`\|+`) + result = re.ReplaceAllString(result, "|") + + // 移除字符串开头和结尾可能出现的 "|" + result = strings.Trim(result, "|") + + return result +}