diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..76e0d98 --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "tmp\\main.exe" +cmd = "go build -o ./tmp/main.exe ." +delay = 1000 +exclude_dir = ["assets", "tmp", "vendor", "testdata", ".idea", ".git"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +full_bin = "" +include_dir = [] +include_ext = ["go", "tpl", "tmpl", "html"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +post_cmd = [] +pre_cmd = [] +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = false + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +time = false + +[misc] +clean_on_exit = false + +[proxy] +app_port = 0 +enabled = false +proxy_port = 0 + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore index 929c7fa..60fabea 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.so *.dylib .air.toml +test # Test binary, built with `go test -c` *.test diff --git a/api/api.go b/api/api.go index 4dec60c..2437f40 100644 --- a/api/api.go +++ b/api/api.go @@ -1,14 +1,14 @@ package api import ( - "schisandra-cloud-album/api/auth_api" "schisandra-cloud-album/api/captcha_api" "schisandra-cloud-album/api/sms_api" + "schisandra-cloud-album/api/user_api" ) // Apis 统一导出的api type Apis struct { - AuthApi auth_api.AuthAPI + UserApi user_api.UserAPI CaptchaApi captcha_api.CaptchaAPI SmsApi sms_api.SmsAPI } diff --git a/api/auth_api/auth.go b/api/auth_api/auth.go deleted file mode 100644 index acc0d12..0000000 --- a/api/auth_api/auth.go +++ /dev/null @@ -1,3 +0,0 @@ -package auth_api - -type AuthAPI struct{} diff --git a/api/auth_api/auth_api.go b/api/auth_api/auth_api.go deleted file mode 100644 index 2d5c688..0000000 --- a/api/auth_api/auth_api.go +++ /dev/null @@ -1,159 +0,0 @@ -package auth_api - -import ( - ginI18n "github.com/gin-contrib/i18n" - "github.com/gin-gonic/gin" - "reflect" - "schisandra-cloud-album/common/result" - "schisandra-cloud-album/model" - "schisandra-cloud-album/service" - "schisandra-cloud-album/utils" -) - -var authService = service.Service.AuthService - -// GetUserList -// @Summary 获取所有用户列表 -// @Tags 鉴权模块 -// @Success 200 {string} json -// @Router /api/auth/user/List [get] -func (AuthAPI) GetUserList(c *gin.Context) { - userList := authService.GetUserList() - result.OkWithData(userList, c) -} - -// QueryUserByUsername -// @Summary 根据用户名查询用户 -// @Tags 鉴权模块 -// @Param username query string true "用户名" -// @Success 200 {string} json -// @Router /api/auth/user/query_by_username [get] -func (AuthAPI) QueryUserByUsername(c *gin.Context) { - username := c.Query("username") - user := authService.QueryUserByUsername(username) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) - return - } - result.OkWithData(user, c) -} - -// QueryUserByUuid -// @Summary 根据uuid查询用户 -// @Tags 鉴权模块 -// @Param uuid query string true "用户uuid" -// @Success 200 {string} json -// @Router /api/auth/user/query_by_uuid [get] -func (AuthAPI) QueryUserByUuid(c *gin.Context) { - uuid := c.Query("uuid") - user := authService.QueryUserByUuid(uuid) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) - return - } - result.OkWithData(user, c) -} - -// DeleteUser 删除用户 -// @Summary 删除用户 -// @Tags 鉴权模块 -// @Param uuid query string true "用户uuid" -// @Success 200 {string} json -// @Router /api/auth/user/delete [delete] -func (AuthAPI) DeleteUser(c *gin.Context) { - uuid := c.Query("uuid") - err := authService.DeleteUser(uuid) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "DeletedFailed"), c) - return - } - result.OkWithMessage(ginI18n.MustGetMessage(c, "DeletedSuccess"), c) -} - -// QueryUserByPhone 根据手机号查询用户 -// @Summary 根据手机号查询用户 -// @Tags 鉴权模块 -// @Param phone query string true "手机号" -// @Success 200 {string} json -// @Router /api/auth/user/query_by_phone [get] -func (AuthAPI) QueryUserByPhone(c *gin.Context) { - phone := c.Query("phone") - user := authService.QueryUserByPhone(phone) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) - return - } - result.OkWithData(user, c) -} - -// AccountLogin 账号登录 -// @Summary 账号登录 -// @Tags 鉴权模块 -// @Param account query string true "账号" -// @Param password query string true "密码" -// @Success 200 {string} json -// @Router /api/auth/user/login [post] -func (AuthAPI) AccountLogin(c *gin.Context) { - account := c.PostForm("account") - password := c.PostForm("password") - isPhone := utils.IsPhone(account) - if isPhone { - user := authService.QueryUserByPhone(account) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c) - } else { - verify := utils.Verify(password, *user.Password) - if verify { - result.OkWithData(user, c) - } else { - result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) - } - } - } - isEmail := utils.IsEmail(account) - if isEmail { - user := authService.QueryUserByEmail(account) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "EmailNotRegister"), c) - } else { - verify := utils.Verify(password, *user.Password) - if verify { - result.OkWithData(user, c) - } else { - result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) - } - } - } - isUsername := utils.IsUsername(account) - if isUsername { - user := authService.QueryUserByUsername(account) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "UsernameNotRegister"), c) - } else { - verify := utils.Verify(password, *user.Password) - if verify { - result.OkWithData(user, c) - } else { - result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) - } - } - - } -} - -// Register 用户注册 -// @Summary 用户注册 -// @Tags 鉴权模块 -// @Param user body model.ScaAuthUser true "用户信息" -// @Success 200 {string} json -// @Router /api/auth/user/register [post] -func (AuthAPI) Register(c *gin.Context) { - var user model.ScaAuthUser - _ = c.ShouldBindJSON(&user) - err := authService.AddUser(user) - if err != nil { - result.FailWithMessage("用户注册失败!", c) - return - } - result.OkWithMessage("用户注册成功!", c) -} diff --git a/api/captcha_api/captcha_api.go b/api/captcha_api/captcha_api.go index c8a3add..2a2a0ee 100644 --- a/api/captcha_api/captcha_api.go +++ b/api/captcha_api/captcha_api.go @@ -10,7 +10,7 @@ import ( "github.com/wenlng/go-captcha/v2/rotate" "github.com/wenlng/go-captcha/v2/slide" "log" - "schisandra-cloud-album/api/captcha_api/model" + "schisandra-cloud-album/api/captcha_api/dto" "schisandra-cloud-album/common/redis" "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" @@ -44,7 +44,7 @@ func (CaptchaAPI) GenerateRotateCaptcha(c *gin.Context) { return } key := helper.StringToMD5(string(dotsByte)) - err = redis.Set(key, dotsByte, time.Minute).Err() + err = redis.Set("user:login:client:"+key, dotsByte, time.Minute).Err() if err != nil { result.FailWithNull(c) return @@ -66,7 +66,7 @@ func (CaptchaAPI) GenerateRotateCaptcha(c *gin.Context) { // @Success 200 {string} json // @Router /api/captcha/rotate/check [post] func (CaptchaAPI) CheckRotateData(c *gin.Context) { - rotateRequest := model.RotateCaptchaRequest{} + rotateRequest := dto.RotateCaptchaRequest{} err := c.ShouldBindJSON(&rotateRequest) angle := rotateRequest.Angle key := rotateRequest.Key @@ -74,7 +74,7 @@ func (CaptchaAPI) CheckRotateData(c *gin.Context) { result.FailWithNull(c) return } - cacheDataByte, err := redis.Get(key).Bytes() + cacheDataByte, err := redis.Get("user:login:client:" + key).Bytes() if len(cacheDataByte) == 0 || err != nil { result.FailWithCodeAndMessage(1011, ginI18n.MustGetMessage(c, "CaptchaExpired"), c) return @@ -126,7 +126,7 @@ func (CaptchaAPI) GenerateBasicTextCaptcha(c *gin.Context) { return } key := helper.StringToMD5(string(dotsByte)) - err = redis.Set(key, dotsByte, time.Minute).Err() + err = redis.Set("user:login:client:"+key, dotsByte, time.Minute).Err() if err != nil { result.FailWithNull(c) return @@ -154,7 +154,7 @@ func (CaptchaAPI) CheckClickData(c *gin.Context) { result.FailWithNull(c) return } - cacheDataByte, err := redis.Get(key).Bytes() + cacheDataByte, err := redis.Get("user:login:client:" + key).Bytes() if len(cacheDataByte) == 0 || err != nil { result.FailWithNull(c) return diff --git a/api/captcha_api/model/request_model.go b/api/captcha_api/dto/request_dto.go similarity index 87% rename from api/captcha_api/model/request_model.go rename to api/captcha_api/dto/request_dto.go index 423351f..887c71e 100644 --- a/api/captcha_api/model/request_model.go +++ b/api/captcha_api/dto/request_dto.go @@ -1,4 +1,4 @@ -package model +package dto type RotateCaptchaRequest struct { Angle int `json:"angle"` diff --git a/api/role_api/role.go b/api/role_api/role.go new file mode 100644 index 0000000..96ec8c6 --- /dev/null +++ b/api/role_api/role.go @@ -0,0 +1,3 @@ +package role_api + +type RoleAPI struct{} diff --git a/api/role_api/role_api.go b/api/role_api/role_api.go new file mode 100644 index 0000000..e3c92dd --- /dev/null +++ b/api/role_api/role_api.go @@ -0,0 +1 @@ +package role_api diff --git a/api/sms_api/sms_api.go b/api/sms_api/sms_api.go index a4c2c8b..300333a 100644 --- a/api/sms_api/sms_api.go +++ b/api/sms_api/sms_api.go @@ -7,9 +7,11 @@ import ( "github.com/pkg6/go-sms/gateways" "github.com/pkg6/go-sms/gateways/aliyun" "github.com/pkg6/go-sms/gateways/smsbao" + "schisandra-cloud-album/common/redis" "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" "schisandra-cloud-album/utils" + "time" ) // SendMessageByAli 发送短信验证码 @@ -46,6 +48,8 @@ func (SmsAPI) SendMessageByAli(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c) return } + result.OkWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendSuccess"), c) + } // SendMessageBySmsBao 短信宝发送短信验证码 @@ -78,3 +82,32 @@ func (SmsAPI) SendMessageBySmsBao(c *gin.Context) { } result.OkWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendSuccess"), c) } + +// SendMessageTest 发送测试短信验证码 +// @Summary 发送测试短信验证码 +// @Description 发送测试短信验证码 +// @Tags 短信验证码 +// @Produce json +// @Param phone query string true "手机号" +// @Router /api/sms/test/send [get] +func (SmsAPI) SendMessageTest(c *gin.Context) { + phone := c.Query("phone") + if phone == "" { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c) + return + } + isPhone := utils.IsPhone(phone) + if !isPhone { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneError"), c) + return + } + code := utils.GenValidateCode(6) + err := redis.Set("user:login:sms:"+phone, code, time.Minute).Err() + if err != nil { + global.LOG.Error(err) + result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c) + return + } + result.OkWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendSuccess"), c) + +} diff --git a/api/user_api/dto/request_dto.go b/api/user_api/dto/request_dto.go new file mode 100644 index 0000000..eda15cf --- /dev/null +++ b/api/user_api/dto/request_dto.go @@ -0,0 +1,30 @@ +package dto + +import "encoding/json" + +// RefreshTokenRequest 刷新token请求 +type RefreshTokenRequest struct { + RefreshToken string `json:"refresh_token"` +} + +// PhoneLoginRequest 手机号登录请求 +type PhoneLoginRequest struct { + Phone string `json:"phone"` + Captcha string `json:"captcha"` +} + +// ResponseData 返回数据 +type ResponseData struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresAt int64 `json:"expires_at"` + UID *string `json:"uid"` +} + +func (res ResponseData) MarshalBinary() ([]byte, error) { + return json.Marshal(res) +} + +func (res ResponseData) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, &res) +} diff --git a/api/user_api/user.go b/api/user_api/user.go new file mode 100644 index 0000000..5b911c8 --- /dev/null +++ b/api/user_api/user.go @@ -0,0 +1,3 @@ +package user_api + +type UserAPI struct{} diff --git a/api/user_api/user_api.go b/api/user_api/user_api.go new file mode 100644 index 0000000..8355a37 --- /dev/null +++ b/api/user_api/user_api.go @@ -0,0 +1,307 @@ +package user_api + +import ( + ginI18n "github.com/gin-contrib/i18n" + "github.com/gin-gonic/gin" + "github.com/yitter/idgenerator-go/idgen" + "reflect" + "schisandra-cloud-album/api/user_api/dto" + "schisandra-cloud-album/common/enum" + "schisandra-cloud-album/common/redis" + "schisandra-cloud-album/common/result" + "schisandra-cloud-album/global" + "schisandra-cloud-album/model" + "schisandra-cloud-album/service" + "schisandra-cloud-album/utils" + "strconv" + "time" +) + +var userService = service.Service.UserService +var userRoleService = service.Service.UserRoleService + +// GetUserList +// @Summary 获取所有用户列表 +// @Tags 鉴权模块 +// @Success 200 {string} json +// @Router /api/auth/user/List [get] +func (UserAPI) GetUserList(c *gin.Context) { + userList := userService.GetUserList() + result.OkWithData(userList, c) +} + +// QueryUserByUsername +// @Summary 根据用户名查询用户 +// @Tags 鉴权模块 +// @Param username query string true "用户名" +// @Success 200 {string} json +// @Router /api/auth/user/query_by_username [get] +func (UserAPI) QueryUserByUsername(c *gin.Context) { + username := c.Query("username") + user := userService.QueryUserByUsername(username) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) + return + } + result.OkWithData(user, c) +} + +// QueryUserByUuid +// @Summary 根据uuid查询用户 +// @Tags 鉴权模块 +// @Param uuid query string true "用户uuid" +// @Success 200 {string} json +// @Router /api/auth/user/query_by_uuid [get] +func (UserAPI) QueryUserByUuid(c *gin.Context) { + uuid := c.Query("uuid") + user := userService.QueryUserByUuid(uuid) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) + return + } + result.OkWithData(user, c) +} + +// DeleteUser 删除用户 +// @Summary 删除用户 +// @Tags 鉴权模块 +// @Param uuid query string true "用户uuid" +// @Success 200 {string} json +// @Router /api/auth/user/delete [delete] +func (UserAPI) DeleteUser(c *gin.Context) { + uuid := c.Query("uuid") + err := userService.DeleteUser(uuid) + if err != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "DeletedFailed"), c) + return + } + result.OkWithMessage(ginI18n.MustGetMessage(c, "DeletedSuccess"), c) +} + +// QueryUserByPhone 根据手机号查询用户 +// @Summary 根据手机号查询用户 +// @Tags 鉴权模块 +// @Param phone query string true "手机号" +// @Success 200 {string} json +// @Router /api/auth/user/query_by_phone [get] +func (UserAPI) QueryUserByPhone(c *gin.Context) { + phone := c.Query("phone") + user := userService.QueryUserByPhone(phone) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "NotFoundUser"), c) + return + } + result.OkWithData(user, c) +} + +// AccountLogin 账号登录 +// @Summary 账号登录 +// @Tags 鉴权模块 +// @Param account query string true "账号" +// @Param password query string true "密码" +// @Success 200 {string} json +// @Router /api/user/login [post] +func (UserAPI) AccountLogin(c *gin.Context) { + account := c.PostForm("account") + password := c.PostForm("password") + isPhone := utils.IsPhone(account) + if isPhone { + user := userService.QueryUserByPhone(account) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c) + return + } else { + verify := utils.Verify(password, *user.Password) + if verify { + result.OkWithData(user, c) + return + } else { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) + return + } + } + } + isEmail := utils.IsEmail(account) + if isEmail { + user := userService.QueryUserByEmail(account) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "EmailNotRegister"), c) + return + } else { + verify := utils.Verify(password, *user.Password) + if verify { + result.OkWithData(user, c) + return + } else { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) + return + } + } + } + isUsername := utils.IsUsername(account) + if isUsername { + user := userService.QueryUserByUsername(account) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + result.FailWithMessage(ginI18n.MustGetMessage(c, "UsernameNotRegister"), c) + return + } else { + verify := utils.Verify(password, *user.Password) + if verify { + result.OkWithData(user, c) + return + } else { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) + return + } + } + + } +} + +// PhoneLogin 手机号登录/注册 +// @Summary 手机号登录/注册 +// @Tags 鉴权模块 +// @Param phone query string true "手机号" +// @Param captcha query string true "验证码" +// @Success 200 {string} json +// @Router /api/user/phone_login [post] +func (UserAPI) PhoneLogin(c *gin.Context) { + request := dto.PhoneLoginRequest{} + err := c.ShouldBindJSON(&request) + if err != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) + return + } + phone := request.Phone + captcha := request.Captcha + if phone == "" || captcha == "" { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneAndCaptchaNotEmpty"), c) + return + } + isPhone := utils.IsPhone(phone) + if !isPhone { + result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneErrorFormat"), c) + return + } + + user := userService.QueryUserByPhone(phone) + if reflect.DeepEqual(user, model.ScaAuthUser{}) { + // 未注册 + code := redis.Get("user:login:sms:" + phone) + 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 + } + userRole := model.ScaAuthUserRole{ + UserID: addUser.ID, + RoleID: enum.User, + } + e := userRoleService.AddUserRole(userRole) + if e != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + ids, err := userRoleService.GetUserRoleIdsByUserId(addUser.ID) + if err != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: addUser.UID, RoleID: ids}) + + data := dto.ResponseData{ + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresAt: expiresAt, + UID: addUser.UID, + } + fail := redis.Set("user:login:token:"+*addUser.UID, data, time.Hour*24*30).Err() + if fail != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + result.OkWithData(data, c) + return + } + } else { + code := redis.Get("user:login:sms:" + phone) + 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 { + ids, err := userRoleService.GetUserRoleIdsByUserId(user.ID) + if err != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + accessToken, refreshToken, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: user.UID, RoleID: ids}) + + data := dto.ResponseData{ + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresAt: expiresAt, + UID: user.UID, + } + fail := redis.Set("user:login:token:"+*user.UID, data, time.Hour*24*30).Err() + if fail != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + result.OkWithData(data, c) + return + } + } + + } + +} + +// RefreshHandler 刷新token +// @Summary 刷新token +// @Tags 鉴权模块 +// @Param refresh_token query string true "刷新token" +// @Success 200 {string} json +// @Router /api/auth/token/refresh [post] +func (UserAPI) RefreshHandler(c *gin.Context) { + refreshToken := c.Query("refresh_token") + if refreshToken == "" { + result.FailWithMessage("refresh_token不能为空!", c) + return + } + parseRefreshToken, isUpd, err := utils.ParseToken(refreshToken) + if err != nil { + global.LOG.Errorln(err) + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) + return + } + if isUpd { + accessTokenString, refreshTokenString, expiresAt := utils.GenerateAccessTokenAndRefreshToken(utils.JWTPayload{UserID: parseRefreshToken.UserID, RoleID: parseRefreshToken.RoleID}) + data := dto.ResponseData{ + AccessToken: accessTokenString, + RefreshToken: refreshTokenString, + ExpiresAt: expiresAt, + UID: parseRefreshToken.UserID, + } + fail := redis.Set("user:login:token:"+*parseRefreshToken.UserID, data, time.Hour*24*30).Err() + if fail != nil { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) + return + } + result.OkWithData(data, c) + return + } +} diff --git a/common/enum/role.go b/common/enum/role.go new file mode 100644 index 0000000..ee0ef24 --- /dev/null +++ b/common/enum/role.go @@ -0,0 +1,7 @@ +package enum + +var ( + SuperAdmin int64 = 1 + Admin int64 = 2 + User int64 = 3 +) diff --git a/config/conf_jwt.go b/config/conf_jwt.go index d381e3e..a984c1b 100644 --- a/config/conf_jwt.go +++ b/config/conf_jwt.go @@ -1,10 +1,8 @@ package config type JWT struct { - Secret string `yaml:"secret"` - Expiration string `yaml:"expiration"` - RefreshExpiration string `yaml:"refresh-expiration"` - RefreshTokenKey string `yaml:"refresh-token-key"` - HeaderKey string `yaml:"header-key"` - HeaderPrefix string `yaml:"header-prefix"` + Secret string `yaml:"secret"` + HeaderKey string `yaml:"header-key"` + HeaderPrefix string `yaml:"header-prefix"` + Issuer string `yaml:"issuer"` } diff --git a/core/idgenerator.go b/core/idgenerator.go new file mode 100644 index 0000000..f8d73fd --- /dev/null +++ b/core/idgenerator.go @@ -0,0 +1,10 @@ +package core + +import "github.com/yitter/idgenerator-go/idgen" + +func InitIDGenerator() { + var options = idgen.NewIdGeneratorOptions(1) + options.WorkerIdBitLength = 6 // 默认值6,限定 WorkerId 最大值为2^6-1,即默认最多支持64个节点。 + options.SeqBitLength = 6 // 默认值6,限制每毫秒生成的ID个数。若生成速度超过5万个/秒,建议加大 SeqBitLength 到 10。 + idgen.SetIdGenerator(options) +} diff --git a/docs/docs.go b/docs/docs.go index ca36bb1..7c1e0e5 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -176,7 +176,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.ScaAuthUser" + "$ref": "#/definitions/dto.ScaAuthUser" } } ], @@ -429,7 +429,7 @@ const docTemplate = `{ } }, "definitions": { - "model.ScaAuthUser": { + "dto.ScaAuthUser": { "type": "object", "properties": { "avatar": { diff --git a/go.mod b/go.mod index 5e77ef1..0183e63 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.12.0 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -41,6 +41,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -60,6 +61,7 @@ require ( github.com/pkg6/go-sms v0.1.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/yitter/idgenerator-go v1.3.3 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/image v0.18.0 // indirect golang.org/x/mod v0.19.0 // indirect diff --git a/go.sum b/go.sum index fafdaa2..2dcd71b 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -53,6 +55,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -151,6 +155,8 @@ github.com/wenlng/go-captcha-assets v1.0.1 h1:AdjRFMKmadPRWRTv0XEYfjDvcaayZ2yExI github.com/wenlng/go-captcha-assets v1.0.1/go.mod h1:yQqc7rRbxgLCg+tWtVp+7Y317D1wIZDan/yIwt8wSac= github.com/wenlng/go-captcha/v2 v2.0.0 h1:7Z4Zy09SIHgvj9e8ZxP4VhYOwg7IHt8kGlVrE5jP5Z8= github.com/wenlng/go-captcha/v2 v2.0.0/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34= +github.com/yitter/idgenerator-go v1.3.3 h1:i6rzmpbCL0vlmr/tuW5+lSQzNuDG9vYBjIYRvnRcHE8= +github.com/yitter/idgenerator-go v1.3.3/go.mod h1:VVjbqFjGUsIkaXVkXEdmx1LiXUL3K1NvyxWPJBPbBpE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= diff --git a/i18n/language/en.toml b/i18n/language/en.toml index 2122a5c..e4e6623 100644 --- a/i18n/language/en.toml +++ b/i18n/language/en.toml @@ -33,3 +33,11 @@ CaptchaSendSuccess = "captcha send successfully!" CaptchaTooOften = "captcha too often!" PhoneNotEmpty = "phone number can not be empty!" EmailNotEmpty = "email can not be empty!" +AuthVerifyFailed = "auth verify failed!, please try again!" +AuthVerifySuccess = "auth verify successfully!" +AuthVerifyExpired = "auth verify expired!" +ParamsError = "params error!" +PhoneAndCaptchaNotEmpty = "phone number and captcha can not be empty!" +PhoneErrorFormat = "phone number error format!" +RegisterUserError = "register user error!" +LoginExpired = "login expired!, please try again!" diff --git a/i18n/language/zh.toml b/i18n/language/zh.toml index b7631b3..e47187a 100644 --- a/i18n/language/zh.toml +++ b/i18n/language/zh.toml @@ -33,3 +33,11 @@ CaptchaSendSuccess = "验证码发送成功!" CaptchaTooOften = "验证码发送过于频繁!" PhoneNotEmpty = "手机号不能为空!" EmailNotEmpty = "邮箱不能为空!" +AuthVerifyFailed = "认证失败!,请重新登录!" +AuthVerifySuccess = "认证成功!" +AuthVerifyExpired = "认证已过期!,请重新登录!" +ParamsError = "参数错误!" +PhoneAndCaptchaNotEmpty = "手机号和验证码不能为空!" +PhoneErrorFormat = "手机号格式错误!" +RegisterUserError = "注册用户错误!" +LoginExpired = "登录已过期!,请重新登录!" diff --git a/main.go b/main.go index 1b302d4..3e3b33d 100644 --- a/main.go +++ b/main.go @@ -9,11 +9,12 @@ import ( func main() { // 初始化配置 - core.InitConfig() // 读取配置文件 - core.InitLogger() // 初始化日志 - core.InitGorm() // 初始化数据库 - core.InitRedis() // 初始化redis - core.InitCaptcha() // 初始化验证码 + core.InitConfig() // 读取配置文件 + core.InitLogger() // 初始化日志 + core.InitGorm() // 初始化数据库 + core.InitRedis() // 初始化redis + core.InitCaptcha() // 初始化验证码 + core.InitIDGenerator() // 初始化ID生成器 // 命令行参数绑定 option := cmd.Parse() if cmd.IsStopWeb(&option) { diff --git a/middleware/jwt.go b/middleware/jwt.go new file mode 100644 index 0000000..f006e44 --- /dev/null +++ b/middleware/jwt.go @@ -0,0 +1,38 @@ +package middleware + +import ( + ginI18n "github.com/gin-contrib/i18n" + "github.com/gin-gonic/gin" + "schisandra-cloud-album/common/result" + "schisandra-cloud-album/global" + "schisandra-cloud-album/utils" + "strings" +) + +func JWTAuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 默认双Token放在请求头Authorization的Bearer中,并以空格隔开 + authHeader := c.GetHeader(global.CONFIG.JWT.HeaderKey) + if authHeader == "" { + c.Abort() + result.FailWithMessage(ginI18n.MustGetMessage(c, "AuthVerifyFailed"), c) + return + } + headerPrefix := global.CONFIG.JWT.HeaderPrefix + accessToken := strings.TrimPrefix(authHeader, headerPrefix+" ") + + if accessToken == "undefined" || accessToken == "" { + c.Abort() + result.FailWithMessage(ginI18n.MustGetMessage(c, "AuthVerifyFailed"), c) + return + } + parseToken, isUpd, err := utils.ParseToken(accessToken) + if err != nil || !isUpd { + c.Abort() + result.FailWithCodeAndMessage(401, ginI18n.MustGetMessage(c, "AuthVerifyExpired"), c) + return + } + c.Set("userId", parseToken.UserID) + c.Next() + } +} diff --git a/model/sca_auth_user.go b/model/sca_auth_user.go index 99edd33..100ad5e 100644 --- a/model/sca_auth_user.go +++ b/model/sca_auth_user.go @@ -1,6 +1,7 @@ package model import ( + "encoding/json" "time" ) @@ -9,7 +10,7 @@ const TableNameScaAuthUser = "sca_auth_user" // ScaAuthUser 用户表 type ScaAuthUser struct { ID int64 `gorm:"column:id;type:bigint(255);primaryKey;autoIncrement:true;comment:自增ID" json:"-"` // 自增ID - UUID *string `gorm:"column:uuid;type:varchar(255);comment:唯一ID" json:"uuid"` // 唯一ID + UID *string `gorm:"column:uid;type:varchar(255);comment:唯一ID" json:"uid"` // 唯一ID Username *string `gorm:"column:username;type:varchar(32);comment:用户名" json:"username"` // 用户名 Nickname *string `gorm:"column:nickname;type:varchar(32);comment:昵称" json:"nickname"` // 昵称 Email *string `gorm:"column:email;type:varchar(32);comment:邮箱" json:"email"` // 邮箱 @@ -17,14 +18,14 @@ type ScaAuthUser struct { Password *string `gorm:"column:password;type:varchar(64);comment:密码" json:"-"` // 密码 Gender *string `gorm:"column:gender;type:varchar(32);comment:性别" json:"gender"` // 性别 Avatar *string `gorm:"column:avatar;type:varchar(255);comment:头像" json:"avatar"` // 头像 - Status *int64 `gorm:"column:status;type:tinyint(4);comment:状态 0 正常 1 封禁" json:"status"` // 状态 0 正常 1 封禁 + Status *int64 `gorm:"column:status;type:tinyint(4);default:0;comment:状态 0 正常 1 封禁" json:"status"` // 状态 0 正常 1 封禁 Introduce *string `gorm:"column:introduce;type:varchar(255);comment:介绍" json:"introduce"` // 介绍 ExtJSON *string `gorm:"column:ext_json;type:varchar(255);comment:额外字段" 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"` // 创建时间 UpdateBy *string `gorm:"column:update_by;type:varchar(32);comment:更新人" json:"update_by"` // 更新人 UpdateTime *time.Time `gorm:"column:update_time;type:datetime;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_time"` // 更新时间 - Deleted *int64 `gorm:"column:deleted;type:int(11);comment:是否删除 0 未删除 1 已删除" json:"-"` // 是否删除 0 未删除 1 已删除 + Deleted *int64 `gorm:"column:deleted;type:int(11);default:0;comment:是否删除 0 未删除 1 已删除" json:"-"` // 是否删除 0 未删除 1 已删除 Blog *string `gorm:"column:blog;type:varchar(255);comment:博客" json:"blog"` // 博客 Location *string `gorm:"column:location;type:varchar(255);comment:地址" json:"location"` // 地址 Company *string `gorm:"column:company;type:varchar(255);comment:公司" json:"company"` // 公司 @@ -34,3 +35,11 @@ type ScaAuthUser struct { func (*ScaAuthUser) TableName() string { return TableNameScaAuthUser } + +func (user *ScaAuthUser) MarshalBinary() ([]byte, error) { + return json.Marshal(user) +} + +func (user *ScaAuthUser) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, user) +} diff --git a/router/modules/auth_user_router.go b/router/modules/auth_user_router.go deleted file mode 100644 index 0098963..0000000 --- a/router/modules/auth_user_router.go +++ /dev/null @@ -1,19 +0,0 @@ -package modules - -import ( - "github.com/gin-gonic/gin" - "schisandra-cloud-album/api" -) - -var authApi = api.Api.AuthApi - -func AuthRouter(router *gin.RouterGroup) { - group := router.Group("auth") - group.GET("/user/List", authApi.GetUserList) - group.GET("/user/query_by_username", authApi.QueryUserByUsername) - group.GET("/user/query_by_uuid", authApi.QueryUserByUuid) - group.DELETE("/user/delete", authApi.DeleteUser) - group.GET("/user/query_by_phone", authApi.QueryUserByPhone) - group.POST("/user/login", authApi.AccountLogin) - group.POST("/user/register", authApi.Register) -} diff --git a/router/modules/sms_router.go b/router/modules/sms_router.go index 87cb5cf..0e2f60c 100644 --- a/router/modules/sms_router.go +++ b/router/modules/sms_router.go @@ -11,4 +11,5 @@ func SmsRouter(router *gin.RouterGroup) { group := router.Group("/sms") group.GET("/ali/send", smsApi.SendMessageByAli) group.GET("/smsbao/send", smsApi.SendMessageBySmsBao) + group.GET("/test/send", smsApi.SendMessageTest) } diff --git a/router/modules/user_router.go b/router/modules/user_router.go new file mode 100644 index 0000000..fded1c8 --- /dev/null +++ b/router/modules/user_router.go @@ -0,0 +1,25 @@ +package modules + +import ( + "github.com/gin-gonic/gin" + "schisandra-cloud-album/api" + "schisandra-cloud-album/middleware" +) + +var userApi = api.Api.UserApi + +// UserRouter 用户相关路由 有auth接口组需要token验证,没有auth接口组不需要token验证 +func UserRouter(router *gin.RouterGroup) { + userGroup := router.Group("user") + { + userGroup.POST("/login", userApi.AccountLogin) + userGroup.POST("/phone_login", userApi.PhoneLogin) + } + authGroup := router.Group("auth").Use(middleware.JWTAuthMiddleware()) + { + authGroup.GET("/user/List", userApi.GetUserList) + authGroup.GET("/user/query_by_uuid", userApi.QueryUserByUuid) + authGroup.POST("/token/refresh", userApi.RefreshHandler) + } + +} diff --git a/router/router.go b/router/router.go index 1d72831..23a833e 100644 --- a/router/router.go +++ b/router/router.go @@ -23,7 +23,7 @@ func InitRouter() *gin.Engine { publicGroup.Use(middleware.I18n()) modules.SwaggerRouter(router) // 注册swagger路由 - modules.AuthRouter(publicGroup) // 注册鉴权路由 + modules.UserRouter(publicGroup) // 注册鉴权路由 modules.CaptchaRouter(publicGroup) // 注册验证码路由 modules.SmsRouter(publicGroup) // 注册短信验证码路由 return router diff --git a/service/auth_service/auth.go b/service/auth_service/auth.go deleted file mode 100644 index 11b5d34..0000000 --- a/service/auth_service/auth.go +++ /dev/null @@ -1,3 +0,0 @@ -package auth_service - -type AuthService struct{} diff --git a/service/role_service/role.go b/service/role_service/role.go new file mode 100644 index 0000000..eda2b8a --- /dev/null +++ b/service/role_service/role.go @@ -0,0 +1,3 @@ +package role_service + +type RoleService struct{} diff --git a/service/role_service/role_service.go b/service/role_service/role_service.go new file mode 100644 index 0000000..2b77984 --- /dev/null +++ b/service/role_service/role_service.go @@ -0,0 +1,15 @@ +package role_service + +import ( + "schisandra-cloud-album/global" + "schisandra-cloud-album/model" +) + +// GetRoleById : 通过Id获取角色信息 +func (RoleService) GetRoleById(id int64) (model.ScaAuthRole, error) { + var role model.ScaAuthRole + if err := global.DB.Where("id = ? and deleted = 0", id).First(&role).Error; err != nil { + return model.ScaAuthRole{}, err + } + return role, nil +} diff --git a/service/service.go b/service/service.go index 6c9e083..1aef04e 100644 --- a/service/service.go +++ b/service/service.go @@ -1,12 +1,16 @@ package service import ( - "schisandra-cloud-album/service/auth_service" + "schisandra-cloud-album/service/role_service" + "schisandra-cloud-album/service/user_role_service" + "schisandra-cloud-album/service/user_service" ) // Services 统一导出的service type Services struct { - AuthService auth_service.AuthService + UserService user_service.UserService + RoleService role_service.RoleService + UserRoleService user_role_service.UserRoleService } // Service new函数实例化,实例化完成后会返回结构体地指针类型 diff --git a/service/user_role_service/user_role.go b/service/user_role_service/user_role.go new file mode 100644 index 0000000..74c9ce8 --- /dev/null +++ b/service/user_role_service/user_role.go @@ -0,0 +1,3 @@ +package user_role_service + +type UserRoleService struct{} diff --git a/service/user_role_service/user_role_service.go b/service/user_role_service/user_role_service.go new file mode 100644 index 0000000..a482d19 --- /dev/null +++ b/service/user_role_service/user_role_service.go @@ -0,0 +1,23 @@ +package user_role_service + +import ( + "schisandra-cloud-album/global" + "schisandra-cloud-album/model" +) + +// GetUserRoleIdsByUserId 通过用户ID获取用户角色ID列表 +func (UserRoleService) GetUserRoleIdsByUserId(userId int64) ([]*int64, error) { + var roleIds []*int64 + if err := global.DB.Table("sca_auth_user_role").Where("user_id = ?", userId).Pluck("role_id", &roleIds).Error; err != nil { + return nil, err + } + return roleIds, nil +} + +// AddUserRole 新增用户角色 +func (UserRoleService) AddUserRole(userRole model.ScaAuthUserRole) error { + if err := global.DB.Create(&userRole).Error; err != nil { + return err + } + return nil +} diff --git a/service/user_service/user.go b/service/user_service/user.go new file mode 100644 index 0000000..1ae6bca --- /dev/null +++ b/service/user_service/user.go @@ -0,0 +1,3 @@ +package user_service + +type UserService struct{} diff --git a/service/auth_service/auth_service.go b/service/user_service/user_service.go similarity index 58% rename from service/auth_service/auth_service.go rename to service/user_service/user_service.go index 0d56df9..78a2410 100644 --- a/service/auth_service/auth_service.go +++ b/service/user_service/user_service.go @@ -1,4 +1,4 @@ -package auth_service +package user_service import ( "gorm.io/gorm" @@ -8,52 +8,60 @@ import ( ) // GetUserList 获取所有用户列表 -func (AuthService) GetUserList() []*model.ScaAuthUser { +func (UserService) GetUserList() []*model.ScaAuthUser { data := make([]*model.ScaAuthUser, 0) global.DB.Where("deleted = 0 ").Find(&data) return data } // QueryUserByUsername 根据用户名查询用户 -func (AuthService) QueryUserByUsername(username string) model.ScaAuthUser { +func (UserService) QueryUserByUsername(username string) model.ScaAuthUser { authUser := model.ScaAuthUser{} global.DB.Where("username = ? and deleted = 0", username).First(&authUser) return authUser } // QueryUserByUuid 根据用户uuid查询用户 -func (AuthService) QueryUserByUuid(uuid string) model.ScaAuthUser { +func (UserService) QueryUserByUuid(uuid string) model.ScaAuthUser { authUser := model.ScaAuthUser{} global.DB.Where("uuid = ? and deleted = 0", uuid).First(&authUser) return authUser } // AddUser 添加用户 -func (AuthService) AddUser(user model.ScaAuthUser) error { - return global.DB.Create(&user).Error +func (UserService) AddUser(user model.ScaAuthUser) (model.ScaAuthUser, error) { + if err := global.DB.Create(&user).Error; err != nil { + return model.ScaAuthUser{}, err + } + // 查询创建后的用户信息 + var createdUser model.ScaAuthUser + if err := global.DB.First(&createdUser, user.ID).Error; err != nil { + return model.ScaAuthUser{}, err + } + return createdUser, nil } // UpdateUser 更新用户 -func (AuthService) UpdateUser(user model.ScaAuthUser) *gorm.DB { +func (UserService) UpdateUser(user model.ScaAuthUser) *gorm.DB { authUser := model.ScaAuthUser{} - return global.DB.Model(&authUser).Where("uuid = ?", user.UUID).Updates(user) + return global.DB.Model(&authUser).Where("uuid = ?", user.UID).Updates(user) } // DeleteUser 删除用户 -func (AuthService) DeleteUser(uuid string) error { +func (UserService) DeleteUser(uuid string) error { authUser := model.ScaAuthUser{} return global.DB.Model(&authUser).Where("uuid = ?", uuid).Updates(&model.ScaAuthUser{Deleted: &enum.DELETED}).Error } // QueryUserByPhone 根据手机号查询用户 -func (AuthService) QueryUserByPhone(phone string) model.ScaAuthUser { +func (UserService) QueryUserByPhone(phone string) model.ScaAuthUser { authUser := model.ScaAuthUser{} global.DB.Where("phone = ? and deleted = 0", phone).First(&authUser) return authUser } // QueryUserByEmail 根据邮箱查询用户 -func (AuthService) QueryUserByEmail(email string) model.ScaAuthUser { +func (UserService) QueryUserByEmail(email string) model.ScaAuthUser { authUser := model.ScaAuthUser{} global.DB.Where("email = ? and deleted = 0", email).First(&authUser) return authUser diff --git a/utils/genValidateCode.go b/utils/gen_validate_code.go similarity index 100% rename from utils/genValidateCode.go rename to utils/gen_validate_code.go diff --git a/utils/jwt.go b/utils/jwt.go index 376256a..44b3be2 100644 --- a/utils/jwt.go +++ b/utils/jwt.go @@ -7,9 +7,8 @@ import ( ) type JWTPayload struct { - UserID int `json:"user_id"` - Role string `json:"role"` - Username string `json:"username"` + UserID *string `json:"user_id"` + RoleID []*int64 `json:"role_id"` } type JWTClaims struct { @@ -17,10 +16,11 @@ type JWTClaims struct { jwt.RegisteredClaims } -var MySecret = []byte(global.CONFIG.JWT.Secret) +var MySecret []byte // GenerateToken generates a JWT token with the given payload func GenerateToken(payload JWTPayload) (string, error) { + MySecret = []byte(global.CONFIG.JWT.Secret) claims := JWTClaims{ JWTPayload: payload, RegisteredClaims: jwt.RegisteredClaims{ @@ -33,17 +33,55 @@ func GenerateToken(payload JWTPayload) (string, error) { return token.SignedString(MySecret) } +// GenerateAccessTokenAndRefreshToken generates a JWT token with the given payload, and returns the accessToken and refreshToken +func GenerateAccessTokenAndRefreshToken(payload JWTPayload) (string, string, int64) { + MySecret = []byte(global.CONFIG.JWT.Secret) + // accessToken 的数据 + accessClaims := JWTClaims{ + JWTPayload: payload, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 2)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: global.CONFIG.JWT.Issuer, + }, + } + refreshClaims := JWTClaims{ + JWTPayload: payload, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)), // 7天 + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: global.CONFIG.JWT.Issuer, + }, + } + accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims) + refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims) + accessTokenString, err := accessToken.SignedString(MySecret) + if err != nil { + global.LOG.Error(err) + return "", "", 0 + } + refreshTokenString, err := refreshToken.SignedString(MySecret) + if err != nil { + global.LOG.Error(err) + return "", "", 0 + } + return accessTokenString, refreshTokenString, refreshClaims.ExpiresAt.Time.Unix() +} + // ParseToken parses a JWT token and returns the payload -func ParseToken(tokenString string) (*JWTPayload, error) { +func ParseToken(tokenString string) (*JWTPayload, bool, error) { + MySecret = []byte(global.CONFIG.JWT.Secret) token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { global.LOG.Error(err) - return nil, err + return nil, false, err } if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid { - return &claims.JWTPayload, nil + return &claims.JWTPayload, true, nil } - return nil, err + return nil, false, err }