From ce7b69923d747cc5fad87ccace9145ffd2cec372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 15 Sep 2022 21:03:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(user):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=94=A8=E6=88=B7=E6=9C=8D=E5=8A=A1=E5=B1=82=E7=9A=84?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/user.go | 246 +++++++++++++++++++++++++++++------------------- 1 file changed, 148 insertions(+), 98 deletions(-) diff --git a/service/user.go b/service/user.go index 3e4dc93..8e963ce 100644 --- a/service/user.go +++ b/service/user.go @@ -1,6 +1,7 @@ package service import ( + "database/sql" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/exceptions" @@ -13,7 +14,7 @@ import ( "github.com/fufuok/utils" "github.com/google/uuid" - "xorm.io/builder" + "github.com/uptrace/bun" ) type _UserService struct{} @@ -106,17 +107,18 @@ func (u _UserService) InvalidUserPassword(uid string) (string, error) { if user == nil && err != nil { return "", exceptions.NewNotFoundError("指定的用户不存在。") } + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() verifyCode := tools.RandStr(10) user.Password = utils.Sha512Hex(verifyCode) user.ResetNeeded = true - affected, err := global.DBConn.ID(uid).Cols("password", "reset_needed").Update(user) + res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx) if err != nil { return "", err } - if affected > 0 { - // ! 同一个用户在缓存中有两个键。 - cache.AbolishRelation(fmt.Sprintf("user_%s", uid)) - cache.AbolishRelation("user") + if affected, _ := res.RowsAffected(); affected > 0 { + // ! 清除与此用户所有相关的记录。 + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return verifyCode, nil } else { return "", exceptions.NewUnsuccessfulOperationError() @@ -141,15 +143,16 @@ func (u _UserService) ResetUserPassword(username, password string) (bool, error) if user == nil || err != nil { return false, exceptions.NewNotFoundError("指定的用户不存在。") } + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() user.Password = utils.Sha512Hex(password) user.ResetNeeded = false - affected, err := global.DBConn.ID(user.Id).Cols("password", "reset_needed").Update(user) + res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx) if err != nil { return false, err } - if affected > 0 { - cache.AbolishRelation(fmt.Sprintf("user_%s", user.Id)) - cache.AbolishRelation("user") + if affected, _ := res.RowsAffected(); affected > 0 { + cache.AbolishRelation(fmt.Sprintf("user:%s", user.Id)) return true, nil } else { return false, nil @@ -160,9 +163,11 @@ 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{}) + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("id = ?", uid).Exists(ctx) if has { - cache.CacheExists([]string{fmt.Sprintf("user_%s", uid)}, "user", uid) + cache.CacheExists([]string{"user", fmt.Sprintf("user_%s", uid)}, "user", uid) } return has, err } @@ -171,7 +176,9 @@ 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{}) + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("username = ?", username).Exists(ctx) if has { cache.CacheExists([]string{"user"}, "user", username) } @@ -199,18 +206,19 @@ func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (st finalAbbr := tools.PinyinAbbr(*detail.Name) detail.Abbr = &finalAbbr } + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() - tx := global.DBConn.NewSession() - defer tx.Close() - if err := tx.Begin(); err != nil { + tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) + if err != nil { return "", err } - _, err = tx.Insert(user) + _, err = tx.NewInsert().Model(user).Exec(ctx) if err != nil { tx.Rollback() return "", fmt.Errorf("user create failed: %w", err) } - _, err = tx.Insert(detail) + _, err = tx.NewInsert().Model(detail).Exec(ctx) if err != nil { tx.Rollback() return "", fmt.Errorf("user Detail create failed: %w", err) @@ -220,6 +228,7 @@ func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (st tx.Rollback() return "", fmt.Errorf("transaction commit unsuccessful: %w", err) } + // ! 广谱关联关系的废除必须是在有新记录加入或者有记录被删除的情况下。 cache.AbolishRelation("user") return verifyCode, nil } @@ -234,10 +243,11 @@ func (u _UserService) SwitchUserState(uid string, enabled bool) error { } 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)) + ctx, cancel := global.TimeoutContext(30 * time.Second) + 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 } @@ -246,60 +256,86 @@ func (_UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedU 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) + + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + + var users = make([]model.User, 0) + keywordCond := "%" + keyword + "%" + err := global.DB.NewSelect().Model(&users).Relation("Detail"). + Where("user.type = ?", model.USER_TYPE_ENT). + WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Where("user.username like ?", keywordCond). + WhereOr("user_detail.name like ?", keywordCond). + WhereOr("user_detail like ?", keywordCond) + }). + Order("user.created_at asc"). + Limit(limit). + Offset(0). + Scan(ctx) if err != nil { return make([]model.JoinedUserDetail, 0), err } - cache.CacheSearch(users, []string{"user"}, "join_user_detail", keyword, strconv.Itoa(limit)) - return users, nil + 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(30 * time.Second) + defer cancel() 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) + 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 _postProcessSingle(user, has, err) + return user, err } func (_UserService) findUserByUsername(username string) (*model.User, error) { if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil { return cachedUser, nil } + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() 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) + 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 _postProcessSingle(user, has, err) + 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 } - 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) + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + user := &model.UserDetail{ + Id: uid, } - return _postProcessSingle(user, has, err) + 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) { @@ -307,82 +343,96 @@ func (_UserService) findUserByID(uid string) (*model.User, error) { 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) + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + user := &model.User{ + Id: uid, } - return _postProcessSingle(user, has, err) + 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 = builder.NewCond() + 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.And(builder.Neq{"d.id": "000"}) + cond = cond.Where("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) + keywordCond := "%" + keyword + "%" + cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Where("u.username like ?", keywordCond). + WhereOr("d.name like ?", keywordCond) + }) cacheConditions = append(cacheConditions, keyword) } if userType != -1 { - cond = cond.And(builder.Eq{"u.type": userType}) + cond = cond.Where("u.type = ?", userType) cacheConditions = append(cacheConditions, strconv.Itoa(userType)) } if userState != nil { - cond = cond.And(builder.Eq{"u.enabled": *userState}) + cond = cond.Where("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 + if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil { + return *cachedUsers, cacheCounts, nil } - 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 + + ctx, cancel := global.TimeoutContext(30 * time.Second) + 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)) } - 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 + + 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 } - 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 + + ctx, cancel := global.TimeoutContext(30 * time.Second) + defer cancel() + user := &model.User{} + err := global.DB.NewSelect().Model(&user).Relation("Detail"). + Where("user.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 }