From 1c5bcf033b69cd53d9e06c44167aa4646d4046fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 12 Aug 2022 06:32:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(login):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=EF=BC=8C=E5=BE=85?= =?UTF-8?q?=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/user.go | 52 ++++++++++++++++++++++ exceptions/auth.go | 21 +++++++++ go.mod | 1 + repository/abstract.go | 23 ++++++++++ repository/user.go | 22 +++++++++ response/user_response.go | 29 ++++++++++++ router/security.go | 23 ++++++++++ service/user.go | 94 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 265 insertions(+) create mode 100644 controller/user.go create mode 100644 exceptions/auth.go create mode 100644 repository/abstract.go create mode 100644 repository/user.go create mode 100644 response/user_response.go create mode 100644 router/security.go create mode 100644 service/user.go diff --git a/controller/user.go b/controller/user.go new file mode 100644 index 0000000..a06e198 --- /dev/null +++ b/controller/user.go @@ -0,0 +1,52 @@ +package controller + +import ( + "electricity_bill_calc/exceptions" + "electricity_bill_calc/model" + "electricity_bill_calc/response" + "electricity_bill_calc/service" + "errors" + "net/http" + + "github.com/gin-gonic/gin" +) + +type _UserController struct{} + +var UserController _UserController + +type LoginFormData struct { + Username string `form:"uname"` + Password string `form:"upass"` + Type int8 `form:"type"` +} + +func (_UserController) Login(c *gin.Context) { + result := response.NewResult(c) + loginData := new(LoginFormData) + c.BindJSON(loginData) + var ( + session *model.Session + err error + ) + if loginData.Type == 0 { + session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) + } else { + session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) + } + if err != nil { + if errors.Is(err, &exceptions.AuthenticationError{}) { + authError := err.(exceptions.AuthenticationError) + if authError.NeedReset { + result.LoginNeedReset() + return + } + result.Error(int(authError.Code), authError.Message) + return + } else { + result.Error(http.StatusInternalServerError, err.Error()) + return + } + } + result.LoginSuccess(session, false) +} diff --git a/exceptions/auth.go b/exceptions/auth.go new file mode 100644 index 0000000..d3728b8 --- /dev/null +++ b/exceptions/auth.go @@ -0,0 +1,21 @@ +package exceptions + +import "fmt" + +type AuthenticationError struct { + Code int16 + Message string + NeedReset bool +} + +func NewAuthenticationError(code int16, msg string) *AuthenticationError { + return &AuthenticationError{ + Code: code, + Message: msg, + NeedReset: false, + } +} + +func (e AuthenticationError) Error() string { + return fmt.Sprintf("[%d]%s", e.Code, e.Message) +} diff --git a/go.mod b/go.mod index 96d9aab..e5d4617 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/gin-gonic/gin v1.8.1 github.com/go-redis/redis/v8 v8.11.5 + github.com/google/uuid v1.3.0 github.com/jackc/pgx/v5 v5.0.0-beta.1 github.com/shopspring/decimal v1.3.1 github.com/spf13/viper v1.12.0 diff --git a/repository/abstract.go b/repository/abstract.go new file mode 100644 index 0000000..50d137c --- /dev/null +++ b/repository/abstract.go @@ -0,0 +1,23 @@ +package repository + +func _postProcessSingle[T interface{}](instance *T, has bool, err error) (*T, error) { + if err != nil { + return nil, err + } + if has { + return instance, nil + } else { + return nil, nil + } +} + +func _postProcessList[T interface{}](instance []*T, has bool, err error) ([]*T, error) { + if err != nil { + return nil, err + } + if has { + return instance, nil + } else { + return nil, nil + } +} diff --git a/repository/user.go b/repository/user.go new file mode 100644 index 0000000..43ae712 --- /dev/null +++ b/repository/user.go @@ -0,0 +1,22 @@ +package repository + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/model" +) + +type _UserRepository struct{} + +var UserRepo _UserRepository + +func (_UserRepository) FindUserByUsername(username string) (*model.User, error) { + user := new(model.User) + has, err := global.DBConn.Where("username=?", username).Get(user) + return _postProcessSingle(user, has, err) +} + +func (_UserRepository) RetreiveUserDetail(uid string) (*model.UserDetail, error) { + user := new(model.UserDetail) + has, err := global.DBConn.Where("id=?", uid).Get(user) + return _postProcessSingle(user, has, err) +} diff --git a/response/user_response.go b/response/user_response.go new file mode 100644 index 0000000..bafc299 --- /dev/null +++ b/response/user_response.go @@ -0,0 +1,29 @@ +package response + +import ( + "electricity_bill_calc/model" + "net/http" +) + +type LoginResponse struct { + BaseResponse + NeedReset bool `json:"needReset"` + Session *model.Session `json:"session,omitempty"` +} + +func (r *Result) LoginSuccess(session *model.Session, needReset bool) { + res := &LoginResponse{} + res.Code = http.StatusOK + res.Message = "用户已成功登录。" + res.NeedReset = needReset + res.Session = session + r.Ctx.JSON(http.StatusOK, res) +} + +func (r *Result) LoginNeedReset() { + res := &LoginResponse{} + res.Code = http.StatusUnauthorized + res.Message = "用户凭据已失效。" + res.NeedReset = true + r.Ctx.JSON(http.StatusOK, res) +} diff --git a/router/security.go b/router/security.go new file mode 100644 index 0000000..fe13b92 --- /dev/null +++ b/router/security.go @@ -0,0 +1,23 @@ +package router + +import ( + "electricity_bill_calc/cache" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func AuthenticatedSession(c *gin.Context) { + auth := c.Request.Header.Get("Authorization") + if len(auth) > 0 { + token := strings.Fields(auth)[1] + session, err := cache.RetreiveSession(token) + + if err != nil { + c.AbortWithStatus(http.StatusForbidden) + } + c.Set("session", session) + } + c.Next() +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..ac8ed2e --- /dev/null +++ b/service/user.go @@ -0,0 +1,94 @@ +package service + +import ( + "crypto/sha512" + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/exceptions" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "fmt" + "time" + + "github.com/google/uuid" +) + +type _UserService struct{} + +var UserService _UserService + +func (_UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) { + user, err := repository.UserRepo.FindUserByUsername(username) + + if err != nil { + return nil, err + } + if user == nil { + return nil, exceptions.NewAuthenticationError(404, "用户不存在。") + } + if user.Type != 0 { + return nil, exceptions.NewAuthenticationError(401, "用户类型不正确。") + } + hash := sha512.New512_256() + hash.Write([]byte(password)) + hashedPassword := fmt.Sprintf("%x", hash.Sum(nil)) + if hashedPassword != user.Password { + return nil, exceptions.NewAuthenticationError(401, "用户凭据不正确。") + } + if user.ResetNeeded { + authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") + authErr.NeedReset = true + return nil, authErr + } + session := &model.Session{ + Token: uuid.New().String(), + Uid: user.Id, + Type: user.Type, + Name: user.Username, + ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), + } + userDetial, _ := repository.UserRepo.RetreiveUserDetail(user.Id) + if userDetial != nil { + session.Name = *userDetial.Name + } + cache.CacheSession(session) + return session, nil +} + +func (_UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) { + user, err := repository.UserRepo.FindUserByUsername(username) + + if err != nil { + return nil, err + } + if user == nil { + return nil, exceptions.NewAuthenticationError(404, "用户不存在。") + } + if user.Type != 1 && user.Type != 2 { + return nil, exceptions.NewAuthenticationError(401, "用户类型不正确。") + } + hash := sha512.New512_256() + hash.Write([]byte(password)) + hashedPassword := fmt.Sprintf("%x", hash.Sum(nil)) + if hashedPassword != user.Password { + return nil, exceptions.NewAuthenticationError(401, "用户凭据不正确。") + } + if user.ResetNeeded { + authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") + authErr.NeedReset = true + return nil, authErr + } + session := &model.Session{ + Token: uuid.New().String(), + Uid: user.Id, + Type: user.Type, + Name: user.Username, + ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), + } + userDetial, _ := repository.UserRepo.RetreiveUserDetail(user.Id) + if userDetial != nil { + session.Name = *userDetial.Name + } + cache.CacheSession(session) + return session, nil +}