enhance(user):完成可运行的程序基本结构以及用户基本查询功能。

This commit is contained in:
徐涛 2023-06-01 05:37:41 +08:00
parent 47cd27c968
commit 523e6215f4
4 changed files with 272 additions and 39 deletions

58
main.go
View File

@ -6,12 +6,12 @@ import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/router"
"electricity_bill_calc/service"
"fmt"
"time"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
@ -43,52 +43,46 @@ func init() {
}
func intializeSingularity() error {
singularityExists, err := service.UserService.IsUserExists("000")
l := logger.Named("Init", "Singularity")
singularityExists, err := repository.UserRepository.IsUserExists("000")
if err != nil {
return fmt.Errorf("singularity detect failed: %w", err)
l.Error("检测奇点账号失败。", zap.Error(err))
return fmt.Errorf("检测奇点账号失败: %w", err)
}
if singularityExists {
l.Info("奇点账号已经存在,跳过剩余初始化步骤。")
return nil
}
singularity := &model.User{
Id: "000",
Username: "singularity",
Type: 2,
Enabled: true,
}
singularityName := "Singularity"
singularityId := "000"
singularityExpires, err := model.ParseDate("2099-12-31")
if err != nil {
return fmt.Errorf("singularity expires time parse failed: %w", err)
l.Error("奇点用户账号过期时间解析失败。", zap.Error(err))
return fmt.Errorf("奇点用户账号过期时间解析失败: %w", err)
}
singularityDetail := &model.UserDetail{
Name: &singularityName,
UnitServiceFee: decimal.Zero,
ServiceExpiration: singularityExpires,
singularity := &model.ManagementAccountCreationForm{
Id: &singularityId,
Username: "singularity",
Name: "Singularity",
Type: 2,
Enabled: true,
Expires: singularityExpires,
}
verifyCode, err := service.UserService.CreateUser(singularity, singularityDetail)
verifyCode, err := service.UserService.CreateUserAccount(
singularity.IntoUser(),
singularity.IntoUserDetail())
if err != nil {
return fmt.Errorf("singularity account failed to create: %w", err)
l.Error("创建奇点账号失败。", zap.Error(err))
return fmt.Errorf("创建奇点账号失败: %w", err)
}
logger.Info(
fmt.Sprintf("Singularity account created, use %s as verify code to reset password.", verifyCode),
zap.String("account", "singularity"),
zap.String("verifyCode", verifyCode),
fmt.Sprintf("奇点账号已经完成创建, 首次登录需要使用验证码 [%s] 重置密码。", *verifyCode),
zap.String("账号名称", "singularity"),
zap.String("验证码", *verifyCode),
)
return nil
}
func DBConnectionKeepLive() {
for range time.Tick(30 * time.Second) {
ctx, cancel := global.TimeoutContext()
defer cancel()
err := global.DB.Ping(ctx)
if err != nil {
continue
}
}
}
// 清理Redis缓存中的孤儿键。
func RedisOrphanCleanup() {
cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup"))
for range time.Tick(2 * time.Minute) {
@ -102,8 +96,6 @@ func RedisOrphanCleanup() {
}
func main() {
// 本次停用检测的原因是使用Ping来保持数据库链接看起来没有什么用处。
// go DBConnectionKeepLive()
go RedisOrphanCleanup()
app := router.App()
app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort))

View File

@ -23,6 +23,38 @@ type ManagementAccountCreationForm struct {
Expires Date
}
func (m ManagementAccountCreationForm) IntoUser() *User {
return &User{
Id: *m.Id,
Username: m.Username,
Password: "",
ResetNeeded: false,
UserType: m.Type,
Enabled: m.Enabled,
CreatedAt: nil,
}
}
func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail {
return &UserDetail{
Id: *m.Id,
Name: &m.Name,
Abbr: nil,
Region: nil,
Address: nil,
Contact: m.Contact,
Phone: m.Phone,
UnitServiceFee: decimal.Zero,
ServiceExpiration: m.Expires,
CreatedAt: time.Now(),
CreatedBy: nil,
LastModifiedAt: time.Now(),
LastModifiedBy: nil,
DeletedAt: nil,
DeletedBy: nil,
}
}
type User struct {
Id string
Username string

View File

@ -5,6 +5,8 @@ import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/tools/time"
"fmt"
"github.com/doug-martin/goqu/v9"
@ -15,10 +17,12 @@ import (
type _UserRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var UserRepository = _UserRepository{
log: logger.Named("Repository", "User"),
ds: goqu.Dialect("postgres"),
}
// 使用用户名查询指定用户的基本信息
@ -32,7 +36,7 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro
defer cancel()
var user = new(model.User)
sql, params, _ := goqu.From("user").Where(goqu.C("username").Eq(username)).Prepared(true).ToSQL()
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": username}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err))
return nil, err
@ -52,7 +56,7 @@ func (ur _UserRepository) FindUserById(uid string) (*model.User, error) {
defer cancel()
var user = new(model.User)
sql, params, _ := goqu.From("user").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL()
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
@ -72,7 +76,7 @@ func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, err
defer cancel()
var user = new(model.UserDetail)
sql, params, _ := goqu.From("user_detail").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL()
sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
@ -92,7 +96,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail
defer cancel()
var user = new(model.UserWithDetail)
sql, params, _ := goqu.
sql, params, _ := ur.ds.
From("user").As("u").
Join(
goqu.T("user_detail").As("ud"),
@ -104,7 +108,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by").
Where(goqu.C("u.id").Eq(uid)).
Where(goqu.Ex{"u.id": uid}).
Prepared(true).ToSQL()
if err := pgxscan.Get(
ctx, global.DB, &user, sql, params...); err != nil {
@ -126,7 +130,7 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) {
defer cancel()
var userCount int
sql, params, _ := goqu.From("user").Select(goqu.COUNT("*")).Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL()
sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return false, err
@ -136,3 +140,60 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) {
}
return userCount > 0, nil
}
// 创建一个新用户
func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ur.log.Error("启动数据库事务失败。", zap.Error(err))
return false, err
}
createdTime := time.Now()
userSql, userParams, _ := ur.ds.
Insert("user").
Rows(
goqu.Record{
"id": user.Id, "username": user.Username, "password": user.Password,
"reset_needed": user.ResetNeeded, "type": user.UserType, "enabled": user.Enabled,
"created_at": createdTime,
},
).
Prepared(true).ToSQL()
userResult, err := tx.Exec(ctx, userSql, userParams...)
if err != nil {
ur.log.Error("向数据库插入新用户基本信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
userDetailSql, userDetailParams, _ := ur.ds.
Insert("user_detail").
Rows(
goqu.Record{
"id": user.Id, "name": detail.Name, "abbr": tools.PinyinAbbr(*detail.Name), "region": detail.Region,
"address": detail.Address, "contact": detail.Contact, "phone": detail.Phone,
"unit_service_fee": detail.UnitServiceFee, "service_expiration": detail.ServiceExpiration,
"created_at": createdTime, "created_by": operator,
"last_modified_at": createdTime, "last_modified_by": operator,
},
).
Prepared(true).ToSQL()
detailResult, err := tx.Exec(ctx, userDetailSql, userDetailParams...)
if err != nil {
ur.log.Error("向数据库插入新用户详细信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
err = tx.Commit(ctx)
if err != nil {
ur.log.Error("提交数据库事务失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
} else {
cache.AbolishRelation("user")
}
return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil
}

148
service/user.go Normal file
View File

@ -0,0 +1,148 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/tools"
"electricity_bill_calc/tools/serial"
"electricity_bill_calc/tools/time"
"github.com/fufuok/utils"
"github.com/google/uuid"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _UserService struct {
log *zap.Logger
}
var UserService = _UserService{
log: logger.Named("Service", "User"),
}
func matchUserPassword(controlCode, testCode string) bool {
hashedCode := utils.Sha512Hex(testCode)
return controlCode == hashedCode
}
// 处理用户登录的通用过程。
func (us _UserService) processUserLogin(username, password string, userType []int16) (*model.User, *model.UserDetail, error) {
us.log.Info("处理用户登录。", zap.String("username", username))
user, err := repository.UserRepository.FindUserByUsername(username)
if err != nil {
us.log.Error("处理用户登录失败。", zap.String("username", username), zap.Error(err))
return nil, nil, err
}
if user == nil {
us.log.Warn("处理用户登录失败,用户不存在。", zap.String("username", username))
return nil, nil, exceptions.NewAuthenticationError(404, "用户不存在。")
}
if !lo.Contains(userType, user.UserType) {
us.log.Warn("处理用户登录失败,用户类型错误。", zap.String("username", username), zap.Int16s("user type", userType))
return nil, nil, exceptions.NewAuthenticationError(400, "用户类型不正确。")
}
if !user.Enabled {
us.log.Warn("处理用户登录失败,用户已被禁用。", zap.String("username", username))
return nil, nil, exceptions.NewAuthenticationError(403, "用户已被禁用。")
}
if user.ResetNeeded {
us.log.Warn("处理用户登录失败,用户需要重置密码。", zap.String("username", username))
authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。")
authErr.NeedReset = true
return nil, nil, authErr
}
if !matchUserPassword(user.Password, password) {
us.log.Warn("处理用户登录失败,密码错误。", zap.String("username", username))
return nil, nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。")
}
userDetail, err := repository.UserRepository.FindUserDetailById(user.Id)
if err != nil {
us.log.Error("处理企业用户登录失败,查询用户详细信息失败。", zap.String("username", username), zap.Error(err))
return nil, nil, err
}
if userDetail.ServiceExpiration.Before(time.Now()) {
us.log.Warn("处理企业用户登录失败,用户服务已过期。", zap.String("username", username))
return nil, nil, exceptions.NewAuthenticationError(406, "用户服务期限已过。")
}
return user, userDetail, nil
}
// 处理企业用户登录
func (us _UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) {
user, userDetail, err := us.processUserLogin(username, password, []int16{model.USER_TYPE_ENT})
if err != nil {
us.log.Error("处理企业用户登录失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
token, _ := uuid.NewRandom()
userSession := &model.Session{
Uid: user.Id,
Name: user.Username,
Type: user.UserType,
Token: token.String(),
ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife),
}
if userDetail != nil && userDetail.Name != nil {
userSession.Name = *userDetail.Name
}
err = cache.CacheSession(userSession)
if err != nil {
us.log.Error("处理企业用户登录失败,缓存用户会话失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
return userSession, nil
}
// 处理运维、监管用户登录
func (us _UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) {
user, userDetail, err := us.processUserLogin(username, password, []int16{model.USER_TYPE_OPS, model.USER_TYPE_SUP})
if err != nil {
us.log.Error("处理运维、监管用户登录失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
token, _ := uuid.NewRandom()
userSession := &model.Session{
Uid: user.Id,
Name: user.Username,
Type: user.UserType,
Token: token.String(),
ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife),
}
if userDetail != nil {
userSession.Name = *userDetail.Name
}
err = cache.CacheSession(userSession)
if err != nil {
us.log.Error("处理运维、监管用户登录失败,缓存用户会话失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
return userSession, nil
}
// 创建用户账号的通用方法。
func (us _UserService) CreateUserAccount(user *model.User, detail *model.UserDetail) (*string, error) {
if lo.IsEmpty(user.Id) {
var prefix string
if user.UserType == model.USER_TYPE_ENT {
prefix = "E"
} else {
prefix = "S"
}
user.Id = serial.GeneratePrefixedUniqueSerialString(prefix)
detail.Id = user.Id
}
verifyCode := tools.RandStr(10)
user.Password = utils.Sha512Hex(verifyCode)
user.ResetNeeded = true
res, err := repository.UserRepository.CreateUser(*user, *detail, nil)
if err != nil || !res {
us.log.Error("创建用户账号失败。", zap.String("username", user.Username), zap.Error(err))
return nil, err
}
return &verifyCode, nil
}