enhance(user):完成可运行的程序基本结构以及用户基本查询功能。
This commit is contained in:
parent
47cd27c968
commit
523e6215f4
58
main.go
58
main.go
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
148
service/user.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user