package service import ( "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/exceptions" "electricity_bill_calc/global" "electricity_bill_calc/model" "electricity_bill_calc/tools" "fmt" "strconv" "time" "github.com/fufuok/utils" "github.com/google/uuid" "xorm.io/builder" ) type _UserService struct{} var UserService _UserService func (u _UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) { user, err := u.findUserWithCredentialsByUsername(username) if err != nil { return nil, err } if user == nil { return nil, exceptions.NewAuthenticationError(404, "用户不存在。") } if user.Type != 0 { return nil, exceptions.NewAuthenticationError(400, "用户类型不正确。") } if !user.Enabled { return nil, exceptions.NewAuthenticationError(403, "用户已被禁用。") } hashedPassword := utils.Sha512Hex(password) if hashedPassword != user.Password { return nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。") } if user.ResetNeeded { authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") authErr.NeedReset = true return nil, authErr } userDetial, _ := u.retreiveUserDetail(user.Id) if userDetial.ServiceExpiration.Before(time.Now()) { return nil, exceptions.NewAuthenticationError(406, "用户服务期限已过。") } session := &model.Session{ Token: uuid.New().String(), Uid: user.Id, Type: user.Type, Name: user.Username, ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), } if userDetial != nil { session.Name = *userDetial.Name } cache.CacheSession(session) return session, nil } func (u _UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) { user, err := u.findUserWithCredentialsByUsername(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, "用户类型不正确。") } if !user.Enabled { return nil, exceptions.NewAuthenticationError(401, "用户已被禁用。") } hashedPassword := utils.Sha512Hex(password) 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, _ := u.retreiveUserDetail(user.Id) if userDetial != nil { session.Name = *userDetial.Name } cache.CacheSession(session) return session, nil } func (u _UserService) InvalidUserPassword(uid string) (string, error) { user, err := u.findUserByID(uid) if user == nil && err != nil { return "", exceptions.NewNotFoundError("指定的用户不存在。") } verifyCode := tools.RandStr(10) user.Password = utils.Sha512Hex(verifyCode) user.ResetNeeded = true affected, err := global.DBConn.ID(uid).Cols("password", "reset_needed").Update(user) if err != nil { return "", err } if affected > 0 { // ! 同一个用户在缓存中有两个键。 cache.AbolishRelation(fmt.Sprintf("user_%s", uid)) cache.AbolishRelation("user") return verifyCode, nil } else { return "", exceptions.NewUnsuccessfulOperationError() } } func (u _UserService) VerifyUserPassword(username, verifyCode string) (bool, error) { user, err := u.findUserByUsername(username) if user == nil || err != nil { return false, exceptions.NewNotFoundError("指定的用户不存在。") } hashedVerifyCode := utils.Sha512Hex(verifyCode) if hashedVerifyCode != user.Password { return false, nil } else { return true, nil } } func (u _UserService) ResetUserPassword(username, password string) (bool, error) { user, err := u.findUserByUsername(username) if user == nil || err != nil { return false, exceptions.NewNotFoundError("指定的用户不存在。") } user.Password = utils.Sha512Hex(password) user.ResetNeeded = false affected, err := global.DBConn.ID(user.Id).Cols("password", "reset_needed").Update(user) if err != nil { return false, err } if affected > 0 { cache.AbolishRelation(fmt.Sprintf("user_%s", user.Id)) cache.AbolishRelation("user") return true, nil } else { return false, nil } } func (_UserService) IsUserExists(uid string) (bool, error) { if has, _ := cache.CheckExists("user", uid); has { return has, nil } has, err := global.DBConn.ID(uid).Exist(&model.User{}) if has { cache.CacheExists([]string{fmt.Sprintf("user_%s", uid)}, "user", uid) } return has, err } func (_UserService) IsUsernameExists(username string) (bool, error) { if has, _ := cache.CheckExists("user", username); has { return has, nil } has, err := global.DBConn.Where(builder.Eq{"username": username}).Exist(&model.User{}) if has { cache.CacheExists([]string{"user"}, "user", username) } return has, err } func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (string, error) { if len(user.Id) == 0 { user.Id = uuid.New().String() } exists, err := u.IsUserExists(user.Id) if exists { return "", exceptions.NewNotFoundError("user already exists") } if err != nil { return "", nil } detail.Id = user.Id verifyCode := tools.RandStr(10) user.Password = utils.Sha512Hex(verifyCode) user.ResetNeeded = true if detail.Name != nil { finalAbbr := tools.PinyinAbbr(*detail.Name) detail.Abbr = &finalAbbr } tx := global.DBConn.NewSession() defer tx.Close() if err := tx.Begin(); err != nil { return "", err } _, err = tx.Insert(user) if err != nil { tx.Rollback() return "", fmt.Errorf("user create failed: %w", err) } _, err = tx.Insert(detail) if err != nil { tx.Rollback() return "", fmt.Errorf("user Detail create failed: %w", err) } err = tx.Commit() if err != nil { tx.Rollback() return "", fmt.Errorf("transaction commit unsuccessful: %w", err) } cache.AbolishRelation("user") return verifyCode, nil } func (u _UserService) SwitchUserState(uid string, enabled bool) error { exists, err := u.IsUserExists(uid) if !exists { return exceptions.NewNotFoundError("user not exists") } if err != nil { return err } newStateUser := new(model.User) newStateUser.Enabled = enabled _, err = global.DBConn.ID(uid).Cols("enabled").Update(newStateUser) if err != nil { cache.AbolishRelation("user") cache.AbolishRelation(fmt.Sprintf("user_%s", uid)) } return err } func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedUserDetail, error) { if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", keyword, strconv.Itoa(limit)); cachedUsers != nil { return *cachedUsers, nil } var users = make([]model.JoinedUserDetail, 0) err := global.DBConn. Table("user_detail").Alias("d"). Join("INNER", []string{"user", "u"}, "d.id=u.id"). Where( builder.NewCond(). Or(builder.Like{"u.username", keyword}). Or(builder.Like{"d.name", keyword}). Or(builder.Like{"d.abbr", keyword})). And(builder.Eq{"u.type": 0}). Asc("u.created_at"). Limit(limit, 0). Find(&users) if err != nil { return make([]model.JoinedUserDetail, 0), err } cache.CacheSearch(users, []string{"user"}, "join_user_detail", keyword, strconv.Itoa(limit)) return users, nil } func (_UserService) findUserWithCredentialsByUsername(username string) (*model.UserWithCredentials, error) { if cachedUser, _ := cache.RetreiveSearch[model.UserWithCredentials]("user_with_credentials", username); cachedUser != nil { return cachedUser, nil } user := new(model.UserWithCredentials) has, err := global.DBConn.Where(builder.Eq{"username": username}).NoAutoCondition().Get(user) if has { cache.CacheSearch(*user, []string{"user"}, "user_with_credentials", username) } return _postProcessSingle(user, has, err) } func (_UserService) findUserByUsername(username string) (*model.User, error) { if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil { return cachedUser, nil } user := new(model.User) has, err := global.DBConn.Where(builder.Eq{"username": username}).NoAutoCondition().Get(user) if has { cache.CacheSearch(*user, []string{"user"}, "user", username) } return _postProcessSingle(user, has, err) } func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) { if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { return cachedUser, nil } user := new(model.UserDetail) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) if has { cache.CacheEntity(*user, []string{fmt.Sprintf("user_%s", uid)}, "user_detail", uid) } return _postProcessSingle(user, has, err) } func (_UserService) findUserByID(uid string) (*model.User, error) { cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid) if cachedUser != nil { return cachedUser, nil } user := new(model.User) has, err := global.DBConn.ID(uid).NoAutoCondition().Get(user) if has { cache.CacheEntity(*user, []string{fmt.Sprintf("user_%s", uid)}, "user", uid) } return _postProcessSingle(user, has, err) } func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) { var ( cond = builder.NewCond() cacheConditions = make([]string, 0) ) cacheConditions = append(cacheConditions, strconv.Itoa(page)) cond = cond.And(builder.Neq{"d.id": "000"}) if len(keyword) != 0 { keywordCond := builder.NewCond(). Or(builder.Like{"u.username", keyword}). Or(builder.Like{"d.name", keyword}) cond = cond.And(keywordCond) cacheConditions = append(cacheConditions, keyword) } if userType != -1 { cond = cond.And(builder.Eq{"u.type": userType}) cacheConditions = append(cacheConditions, strconv.Itoa(userType)) } if userState != nil { cond = cond.And(builder.Eq{"u.enabled": *userState}) cacheConditions = append(cacheConditions, strconv.FormatBool(*userState)) } startItem := (page - 1) * config.ServiceSettings.ItemsPageSize var ( total int64 err error ) if cacheCounts, err := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 && err == nil { total = cacheCounts } else { total, err = global.DBConn. Table("user_detail").Alias("d"). Join("INNER", []string{"user", "u"}, "d.id=u.id"). Where(cond). Count(&model.User{}) if err != nil { return nil, -1, err } cache.CacheCount([]string{"user"}, "join_user_detail", total, cacheConditions...) } users := make([]model.JoinedUserDetail, 0) if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil { return *cachedUsers, total, nil } err = global.DBConn. Table("user_detail").Alias("d"). Join("INNER", []string{"user", "u"}, "d.id=u.id"). Where(cond). Limit(config.ServiceSettings.ItemsPageSize, startItem). Find(&users) cache.CacheSearch(users, []string{"user"}, "join_user_detail", cacheConditions...) return users, total, err } func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, error) { if cachedUser, _ := cache.RetreiveEntity[model.FullJoinedUserDetail]("full_join_user_detail", uid); cachedUser != nil { return cachedUser, nil } user := &model.FullJoinedUserDetail{} has, err := global.DBConn. Table("user_detail").Alias("d"). Join("INNER", []string{"user", "u"}, "d.id=u.id"). Where(builder.Eq{"d.id": uid}). NoAutoCondition(). Get(user) if has { cache.CacheEntity(*user, []string{fmt.Sprintf("user_%s", uid)}, "full_join_user_detail", uid) return user, nil } return nil, err }