electricity_bill_calc_service/service/user.go

448 lines
14 KiB
Go

package service
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"fmt"
"strconv"
"time"
"github.com/fufuok/utils"
"github.com/google/uuid"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _UserService struct {
l *zap.Logger
}
var UserService = _UserService{
l: logger.Named("Service", "User"),
}
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.Time.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("指定的用户不存在。")
}
ctx, cancel := global.TimeoutContext()
defer cancel()
verifyCode := tools.RandStr(10)
user.Password = utils.Sha512Hex(verifyCode)
user.ResetNeeded = true
res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx)
if err != nil {
return "", err
}
if affected, _ := res.RowsAffected(); affected > 0 {
// ! 清除与此用户所有相关的记录。
cache.AbolishRelation(fmt.Sprintf("user:%s", uid))
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("指定的用户不存在。")
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user.Password = utils.Sha512Hex(password)
user.ResetNeeded = false
res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx)
if err != nil {
return false, err
}
if affected, _ := res.RowsAffected(); affected > 0 {
cache.AbolishRelation(fmt.Sprintf("user:%s", user.Id))
return true, nil
} else {
return false, nil
}
}
func (_UserService) IsUserExists(uid string) (bool, error) {
if has, _ := cache.CheckExists("user", uid); has {
return has, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("id = ?", uid).Exists(ctx)
if has {
cache.CacheExists([]string{"user", 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
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("username = ?", username).Exists(ctx)
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
}
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return "", err
}
_, err = tx.NewInsert().Model(user).Exec(ctx)
if err != nil {
tx.Rollback()
return "", fmt.Errorf("user create failed: %w", err)
}
_, err = tx.NewInsert().Model(detail).Exec(ctx)
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.Id = uid
newStateUser.Enabled = enabled
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(newStateUser).WherePK().Column("enabled").Exec(ctx)
if affected, _ := res.RowsAffected(); err == nil && affected > 0 {
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
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var users = make([]model.User, 0)
keywordCond := "%" + keyword + "%"
err := global.DB.NewSelect().Model(&users).Relation("Detail").
Where("u.type = ?", model.USER_TYPE_ENT).
WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("u.username like ?", keywordCond).
WhereOr("detail.name like ?", keywordCond).
WhereOr("detail.abbr like ?", keywordCond).
WhereOr("detail.contact like ?", keywordCond).
WhereOr("detail.address like ?", keywordCond)
}).
Order("u.created_at asc").
Limit(limit).
Offset(0).
Scan(ctx)
if err != nil {
return make([]model.JoinedUserDetail, 0), err
}
var detailedUsers = make([]model.JoinedUserDetail, 0)
var relations = make([]string, 0)
// ! 这里的转换是为了兼容之前使用Xorm时构建的关联关系而存在的
for _, u := range users {
detailedUsers = append(detailedUsers, model.JoinedUserDetail{
UserDetail: *u.Detail,
Id: u.Id,
Username: u.Username,
Type: u.Type,
Enabled: u.Enabled,
})
relations = append(relations, fmt.Sprintf("user:%s", u.Id))
}
relations = append(relations, "user")
cache.CacheSearch(users, relations, "join_user_detail", keyword, strconv.Itoa(limit))
return detailedUsers, nil
}
func (_UserService) findUserWithCredentialsByUsername(username string) (*model.UserWithCredentials, error) {
if cachedUser, _ := cache.RetreiveSearch[model.UserWithCredentials]("user_with_credentials", username); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := new(model.UserWithCredentials)
err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx)
if err == nil {
cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user_with_credentials", username)
}
return user, err
}
func (u _UserService) findUserByUsername(username string) (*model.User, error) {
if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := new(model.User)
err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx)
if err == nil {
cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user", username)
}
return user, err
}
func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) {
if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.UserDetail{
Id: uid,
}
err := global.DB.NewSelect().Model(user).WherePK().Scan(ctx)
if err == nil {
cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user_detail", uid)
}
return user, err
}
func (_UserService) findUserByID(uid string) (*model.User, error) {
cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid)
if cachedUser != nil {
return cachedUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.User{
Id: uid,
}
err := global.DB.NewSelect().Model(&user).WherePK().Scan(ctx)
if err == nil {
cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user", uid)
}
return user, err
}
func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) {
var (
cond = global.DB.NewSelect()
cacheConditions = make([]string, 0)
users = make([]model.User, 0)
)
cond = cond.Model(&users).Relation("Detail")
cacheConditions = append(cacheConditions, strconv.Itoa(page))
cond = cond.Where("detail.id <> ?", "000")
if len(keyword) != 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("u.username like ?", keywordCond).
WhereOr("detail.name like ?", keywordCond)
})
cacheConditions = append(cacheConditions, keyword)
}
if userType != -1 {
cond = cond.Where("u.type = ?", userType)
cacheConditions = append(cacheConditions, strconv.Itoa(userType))
}
if userState != nil {
cond = cond.Where("u.enabled = ?", *userState)
cacheConditions = append(cacheConditions, strconv.FormatBool(*userState))
}
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
// * 这里利用已经构建完成的条件集合从缓存中获取数据,如果所有数据都可以从缓存中获取,那么就直接返回了。
if cacheCounts, err := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 && err == nil {
if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil {
return *cachedUsers, cacheCounts, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
total, err := cond.
Limit(config.ServiceSettings.ItemsPageSize).Offset(startItem).
ScanAndCount(ctx)
var (
joinedUsers = make([]model.JoinedUserDetail, 0)
relations = []string{"user"}
)
for _, u := range users {
joinedUsers = append(joinedUsers, model.JoinedUserDetail{
UserDetail: *u.Detail,
Id: u.Id,
Username: u.Username,
Type: u.Type,
Enabled: u.Enabled,
})
relations = append(relations, fmt.Sprintf("user:%s", u.Id))
}
cache.CacheCount(relations, "join_user_detail", int64(total), cacheConditions...)
cache.CacheSearch(joinedUsers, relations, "join_user_detail", cacheConditions...)
return joinedUsers, int64(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
}
ctx, cancel := global.TimeoutContext()
defer cancel()
user := &model.User{}
err := global.DB.NewSelect().Model(user).Relation("Detail").
Where("u.id = ?", uid).
Scan(ctx)
if err == nil {
fullJoinedUser := &model.FullJoinedUserDetail{
User: *user,
UserDetail: *user.Detail,
}
cache.CacheEntity(*fullJoinedUser, []string{fmt.Sprintf("user:%s", uid)}, "full_join_user_detail", uid)
return fullJoinedUser, nil
}
return nil, err
}