Files
platforms/backend/api/internal/logic/delivery/user.go
2026-08-22 21:10:22 +08:00

350 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package delivery
import (
"errors"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func userQuery(pointID uint64) *gorm.DB {
return common.ActiveRecords(db().Model(&models.UserAccount{})).
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?",
common.StatusArchived).
Where("user_service_relation.delivery_basic_id = ?", pointID)
}
func scopedUser(ctx *gin.Context, identity string, pointID uint64) (models.UserAccount, models.UserServiceRelation, bool) {
var user models.UserAccount
if err := userQuery(pointID).Where("user_account.identity = ?", identity).First(&user).Error; err != nil {
common.RespondRecordError(ctx, err)
return user, models.UserServiceRelation{}, false
}
var relation models.UserServiceRelation
if err := common.ActiveRecords(db()).Where("user_account_id = ? AND delivery_basic_id = ?", user.ID, pointID).First(&relation).Error; err != nil {
common.RespondRecordError(ctx, err)
return user, relation, false
}
return user, relation, true
}
func ListUser(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
listScoped(ctx, &models.UserAccount{}, userQuery(point.ID), "user_account.created_at desc")
}
}
func GetUser(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
user, relation, ok := scopedUser(ctx, ctx.Param("identity"), point.ID)
if !ok {
return
}
var addresses []models.UserAddress
if err := common.ActiveRecords(db()).Where("user_account_id = ?", user.ID).Order("is_default desc, created_at desc").Find(&addresses).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
response, _ := common.PublicResourceResponse(gin.H{"user": user, "relation": relation, "addresses": addresses})
infra.Response.Success(ctx, response)
}
// GetUserAvatar 返回当前配送点存在有效服务关系的用户受保护头像。
func GetUserAvatar(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
user, _, ok := scopedUser(ctx, ctx.Param("identity"), point.ID)
if !ok {
return
}
upload.ServeAvatar(ctx, user.Avatar)
}
type userRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name" binding:"required,max=64"`
Phone string `json:"phone" binding:"max=32"`
Avatar *string `json:"avatar" binding:"omitempty,max=512"`
RealName string `json:"real_name" binding:"max=64"`
}
func CreateUser(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var request userRequest
if err := ctx.ShouldBindJSON(&request); err != nil || request.Username == "" || !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := common.PasswordHash(request.Password)
if err != nil {
infra.Response.Error(ctx, err)
return
}
avatar := ""
if request.Avatar != nil {
avatar = *request.Avatar
}
user := models.UserAccount{
Entity: common.NewEntity(common.StatusEnable), Username: request.Username, PasswordHash: hash,
Name: request.Name, Phone: request.Phone, Avatar: avatar, RealName: request.RealName,
}
relation := models.UserServiceRelation{
Entity: common.NewEntity(common.StatusEnable), GasBasicID: point.GasBasicID, DeliveryBasicID: point.ID,
}
if err := db().Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
relation.UserAccountID = user.ID
return tx.Create(&relation).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
common.RespondCreatedResource(ctx, user)
}
func UpdateUser(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
user, _, ok := scopedUser(ctx, ctx.Param("identity"), point.ID)
if !ok {
return
}
var request userRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
values := map[string]any{"name": request.Name, "phone": request.Phone, "real_name": request.RealName}
// 未选择新头像时不提交 avatar避免编辑基础资料误清空现有头像。
if request.Avatar != nil {
values["avatar"] = *request.Avatar
}
if err := db().Model(&user).Updates(values).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
func ResetUserPassword(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
user, _, ok := scopedUser(ctx, ctx.Param("identity"), point.ID)
if !ok {
return
}
var request struct {
Password string `json:"password" binding:"required"`
}
if err := ctx.ShouldBindJSON(&request); err != nil || !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, _ := common.PasswordHash(request.Password)
if err := db().Model(&user).Update("password_hash", hash).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"changed": true})
}
func UpdateUserStatus(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if _, _, ok := scopedUser(ctx, ctx.Param("identity"), point.ID); ok {
common.UpdateRecordStatus(ctx, &models.UserAccount{})
}
}
func ArchiveUser(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
user, relation, ok := scopedUser(ctx, ctx.Param("identity"), point.ID)
if !ok {
return
}
var blocking int64
if err := db().Model(&models.GasorderBasic{}).Where("user_account_id = ? AND order_status NOT IN ?",
user.ID, []int{common.StatusCompleted, common.StatusCancelled}).Count(&blocking).Error; err != nil || blocking > 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err := db().Model(&models.CsTicket{}).Where("user_account_id = ? AND status <> ? AND ticket_status = ?",
user.ID, common.StatusArchived, common.StatusOpen).Count(&blocking).Error; err != nil || blocking > 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var wallet models.WalletBasic
err := db().Where("owner_type = ? AND owner_id = ? AND status <> ?", "user", user.ID, common.StatusArchived).First(&wallet).Error
if err == nil && (wallet.Balance > 0 || wallet.WithdrawalBalance > 0) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err == nil {
if cashErr := db().Model(&models.WalletApplyCash{}).Where("wallet_basic_id = ? AND apply_status = ? AND status <> ?",
wallet.ID, common.StatusPending, common.StatusArchived).Count(&blocking).Error; cashErr != nil || blocking > 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
infra.Response.Error(ctx, err)
return
}
if err := db().Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&relation).Update("status", common.StatusArchived).Error; err != nil {
return err
}
if err := tx.Model(&models.UserAddress{}).Where("user_account_id = ?", user.ID).Update("status", common.StatusArchived).Error; err != nil {
return err
}
return tx.Model(&user).Update("status", common.StatusArchived).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"archived": true})
}
func addressQuery(pointID uint64) *gorm.DB {
return common.ActiveRecords(db().Model(&models.UserAddress{})).
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_address.user_account_id AND user_service_relation.status <> ?",
common.StatusArchived).Where("user_service_relation.delivery_basic_id = ?", pointID)
}
func ListAddress(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
listScoped(ctx, &models.UserAddress{}, addressQuery(point.ID), "user_address.created_at desc")
}
}
func GetAddress(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var address models.UserAddress
respondRecord(ctx, addressQuery(point.ID).Where("user_address.identity = ?", ctx.Param("identity")), &address)
}
type addressRequest struct {
UserIdentity string `json:"user_account_identity" binding:"required"`
Address string `json:"address" binding:"required,max=255"`
Longitude string `json:"longitude" binding:"max=32"`
Latitude string `json:"latitude" binding:"max=32"`
IsDefault bool `json:"is_default"`
}
func CreateAddress(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
user, _, ok := scopedUser(ctx, request.UserIdentity, point.ID)
if !ok {
return
}
address := models.UserAddress{
Entity: common.NewEntity(common.StatusEnable), UserAccountID: user.ID, Address: request.Address,
Longitude: request.Longitude, Latitude: request.Latitude, IsDefault: request.IsDefault,
}
if err := db().Transaction(func(tx *gorm.DB) error {
if request.IsDefault {
if err := tx.Model(&models.UserAddress{}).Where("user_account_id = ?", user.ID).Update("is_default", false).Error; err != nil {
return err
}
}
return tx.Create(&address).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
common.RespondCreatedResource(ctx, address)
}
func UpdateAddress(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var existing models.UserAddress
if err := addressQuery(point.ID).Where("user_address.identity = ?", ctx.Param("identity")).First(&existing).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
user, _, ok := scopedUser(ctx, request.UserIdentity, point.ID)
if !ok || user.ID != existing.UserAccountID {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err := db().Transaction(func(tx *gorm.DB) error {
if request.IsDefault {
if err := tx.Model(&models.UserAddress{}).Where("user_account_id = ? AND id <> ?", user.ID, existing.ID).
Update("is_default", false).Error; err != nil {
return err
}
}
return tx.Model(&existing).Updates(map[string]any{
"address": request.Address, "longitude": request.Longitude, "latitude": request.Latitude, "is_default": request.IsDefault,
}).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
func UpdateAddressStatus(ctx *gin.Context) { mutateAddress(ctx, false) }
func ArchiveAddress(ctx *gin.Context) { mutateAddress(ctx, true) }
func mutateAddress(ctx *gin.Context, archive bool) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var count int64
if err := addressQuery(point.ID).Where("user_address.identity = ?", ctx.Param("identity")).Count(&count).Error; err != nil || count != 1 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if archive {
common.ArchiveRecord(ctx, &models.UserAddress{})
} else {
common.UpdateRecordStatus(ctx, &models.UserAddress{})
}
}