Files
platforms/backend/api/internal/logic/gas/user.go
2026-08-18 20:52:15 +08:00

390 lines
13 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 gas
import (
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"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"
)
// gasUserListItem 在用户主档上附加当前气站服务关系中的配送点归属。
type gasUserListItem struct {
models.UserAccount
DeliveryBasicID uint64 `gorm:"column:delivery_basic_id" json:"delivery_basic_id"`
}
// scopedUserList 将用户列表与当前气站唯一有效服务关系绑定,避免返回跨站归属。
func scopedUserList(databaseService *gorm.DB, gasBasicID uint64) *gorm.DB {
return common.ActiveRecords(databaseService.Model(&models.UserAccount{})).
Select("user_account.*, user_service_relation.delivery_basic_id AS delivery_basic_id").
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.gas_basic_id = ?", gasBasicID)
}
func ListUser(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
page, size := common.PageSize(ctx)
var list []gasUserListItem
var total int64
query := common.ApplyKeywordFilter(ctx,
scopedUserList(impl.DBService, station.ID),
&models.UserAccount{})
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := query.Order("user_account.created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondList(ctx, list, total)
}
func GetUser(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
user, relation, ok := requireUser(ctx, ctx.Param("identity"), station.ID)
if !ok {
return
}
var addresses []models.UserAddress
if err := common.ActiveRecords(impl.DBService).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, err := common.PublicResourceResponse(gin.H{"user": user, "relation": relation, "addresses": addresses})
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, response)
}
// GetUserAvatar 返回与当前气站存在服务关系的用户受保护头像。
func GetUserAvatar(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
user, _, ok := requireUser(ctx, ctx.Param("identity"), station.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"`
DeliveryBasicIdentity string `json:"delivery_basic_identity" binding:"required"`
}
func CreateUser(ctx *gin.Context) {
station, ok := currentGas(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
}
delivery, ok := requireDelivery(ctx, request.DeliveryBasicIdentity, station.ID)
if !ok {
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: station.ID, DeliveryBasicID: delivery.ID}
if err := impl.DBService.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) {
station, ok := currentGas(ctx)
if !ok {
return
}
user, relation, ok := requireUser(ctx, ctx.Param("identity"), station.ID)
if !ok {
return
}
var request userRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
delivery, ok := requireDelivery(ctx, request.DeliveryBasicIdentity, station.ID)
if !ok {
return
}
if err := impl.DBService.Transaction(func(tx *gorm.DB) error {
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 := tx.Model(&user).Updates(values).Error; err != nil {
return err
}
return tx.Model(&relation).Update("delivery_basic_id", delivery.ID).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
func UpdateUserStatus(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, _, ok := requireUser(ctx, ctx.Param("identity"), station.ID); !ok {
return
}
common.UpdateRecordStatus(ctx, &models.UserAccount{})
}
func ArchiveUser(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
user, relation, ok := requireUser(ctx, ctx.Param("identity"), station.ID)
if !ok {
return
}
var blocking int64
if err := impl.DBService.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 := impl.DBService.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 := impl.DBService.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 := impl.DBService.Model(&models.WalletApplyCash{}).
Where("wallet_basic_id = ? AND status <> ? AND apply_status = ?", wallet.ID, common.StatusArchived, common.StatusPending).
Count(&blocking).Error; cashErr != nil || blocking > 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
}
if err != nil && err != gorm.ErrRecordNotFound {
infra.Response.Error(ctx, err)
return
}
if err := impl.DBService.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 = ? AND status <> ?", user.ID, common.StatusArchived).
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 ListUserAddress(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
page, size := common.PageSize(ctx)
var list []models.UserAddress
var total int64
query := common.ActiveRecords(impl.DBService.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.gas_basic_id = ?", station.ID)
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := query.Order("user_address.created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondList(ctx, list, total)
}
func GetUserAddress(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
var address models.UserAddress
query := common.ActiveRecords(impl.DBService.Model(&models.UserAddress{})).
Select("user_address.*").
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_address.identity = ? AND user_service_relation.gas_basic_id = ?", ctx.Param("identity"), station.ID)
respondScopedRecord(ctx, query, &address)
}
type addressRequest struct {
UserAccountIdentity 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 CreateUserAddress(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
user, _, ok := requireUser(ctx, request.UserAccountIdentity, station.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 := impl.DBService.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 UpdateUserAddress(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
var existing models.UserAddress
if err := common.ActiveRecords(impl.DBService.Model(&models.UserAddress{})).
Select("user_address.*").
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_address.identity = ? AND user_service_relation.gas_basic_id = ?", ctx.Param("identity"), station.ID).
First(&existing).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
user, _, ok := requireUser(ctx, request.UserAccountIdentity, station.ID)
if !ok || user.ID != existing.UserAccountID {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err := impl.DBService.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 UpdateUserAddressStatus(ctx *gin.Context) {
if !requireScopedAddress(ctx) {
return
}
common.UpdateRecordStatus(ctx, &models.UserAddress{})
}
func ArchiveUserAddress(ctx *gin.Context) {
if !requireScopedAddress(ctx) {
return
}
common.ArchiveRecord(ctx, &models.UserAddress{})
}
func requireScopedAddress(ctx *gin.Context) bool {
station, ok := currentGas(ctx)
if !ok {
return false
}
var count int64
err := impl.DBService.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_address.identity = ? AND user_service_relation.gas_basic_id = ?", ctx.Param("identity"), station.ID).
Count(&count).Error
if err != nil || count != 1 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return false
}
return true
}