2026-04-29 12:43:05 +08:00

289 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 user
import (
"context"
"strings"
"unicode/utf8"
"hyapp/pkg/xerr"
userdomain "hyapp/services/user-service/internal/domain/user"
)
const (
maxCountryNameLength = 128
maxCountryDisplayNameLength = 128
maxRegionNameLength = 128
)
// CreateCountry 创建国家主数据country_code 创建后不可变。
func (s *Service) CreateCountry(ctx context.Context, countryName string, countryCode string, countryDisplayName string, sortOrder int32, operatorUserID int64, requestID string) (userdomain.Country, error) {
countryName = strings.TrimSpace(countryName)
countryCode = userdomain.NormalizeCountryCode(countryCode)
countryDisplayName = strings.TrimSpace(countryDisplayName)
requestID = strings.TrimSpace(requestID)
if err := validateCountryPayload(countryName, countryCode, countryDisplayName); err != nil {
return userdomain.Country{}, err
}
if requestID == "" {
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.countryAdminRepository == nil {
return userdomain.Country{}, xerr.New(xerr.Unavailable, "country repository is not configured")
}
nowMs := s.now().UnixMilli()
return s.countryAdminRepository.CreateCountry(ctx, userdomain.CreateCountryCommand{
CountryName: countryName,
CountryCode: countryCode,
CountryDisplayName: countryDisplayName,
SortOrder: sortOrder,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: nowMs,
})
}
// UpdateCountry 修改国家展示字段;国家码不允许原地修改。
func (s *Service) UpdateCountry(ctx context.Context, countryID int64, countryName string, countryDisplayName string, sortOrder int32, operatorUserID int64, requestID string) (userdomain.Country, error) {
countryName = strings.TrimSpace(countryName)
countryDisplayName = strings.TrimSpace(countryDisplayName)
requestID = strings.TrimSpace(requestID)
if countryID <= 0 {
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "country_id is required")
}
if err := validateCountryNames(countryName, countryDisplayName); err != nil {
return userdomain.Country{}, err
}
if requestID == "" {
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.countryAdminRepository == nil {
return userdomain.Country{}, xerr.New(xerr.Unavailable, "country repository is not configured")
}
return s.countryAdminRepository.UpdateCountry(ctx, userdomain.UpdateCountryCommand{
CountryID: countryID,
CountryName: countryName,
CountryDisplayName: countryDisplayName,
SortOrder: sortOrder,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
// ListCountries 返回国家主数据status 为空时返回全部。
func (s *Service) ListCountries(ctx context.Context, status string) ([]userdomain.Country, error) {
status = strings.TrimSpace(status)
if status != "" && status != userdomain.CountryStatusActive && status != userdomain.CountryStatusDisabled {
return nil, xerr.New(xerr.InvalidArgument, "country status is invalid")
}
if s.countryAdminRepository == nil {
return nil, xerr.New(xerr.Unavailable, "country repository is not configured")
}
return s.countryAdminRepository.ListCountries(ctx, userdomain.CountryFilter{Status: status})
}
// DisableCountry 停用国家;后续注册、改国家和区域配置都不能再选择它。
func (s *Service) DisableCountry(ctx context.Context, countryID int64, operatorUserID int64, requestID string) (userdomain.Country, error) {
requestID = strings.TrimSpace(requestID)
if countryID <= 0 {
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "country_id is required")
}
if requestID == "" {
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.countryAdminRepository == nil {
return userdomain.Country{}, xerr.New(xerr.Unavailable, "country repository is not configured")
}
return s.countryAdminRepository.DisableCountry(ctx, userdomain.DisableCountryCommand{
CountryID: countryID,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
// CreateRegion 创建区域并建立国家归属;一个国家只能存在一个 active 区域归属。
func (s *Service) CreateRegion(ctx context.Context, regionCode string, name string, countries []string, sortOrder int32, operatorUserID int64, requestID string) (userdomain.Region, error) {
regionCode = userdomain.NormalizeRegionCode(regionCode)
name = strings.TrimSpace(name)
requestID = strings.TrimSpace(requestID)
countries, err := normalizeCountryList(countries)
if err != nil {
return userdomain.Region{}, err
}
if err := validateRegionPayload(regionCode, name); err != nil {
return userdomain.Region{}, err
}
if requestID == "" {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.regionAdminRepository == nil {
return userdomain.Region{}, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.CreateRegion(ctx, userdomain.CreateRegionCommand{
RegionCode: regionCode,
Name: name,
Countries: countries,
SortOrder: sortOrder,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
// UpdateRegion 修改区域展示字段,不改变国家归属。
func (s *Service) UpdateRegion(ctx context.Context, regionID int64, regionCode string, name string, sortOrder int32, operatorUserID int64, requestID string) (userdomain.Region, error) {
regionCode = userdomain.NormalizeRegionCode(regionCode)
name = strings.TrimSpace(name)
requestID = strings.TrimSpace(requestID)
if regionID <= 0 {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "region_id is required")
}
if err := validateRegionPayload(regionCode, name); err != nil {
return userdomain.Region{}, err
}
if requestID == "" {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.regionAdminRepository == nil {
return userdomain.Region{}, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.UpdateRegion(ctx, userdomain.UpdateRegionCommand{
RegionID: regionID,
RegionCode: regionCode,
Name: name,
SortOrder: sortOrder,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
// ListRegions 返回区域主数据status 为空时返回全部。
func (s *Service) ListRegions(ctx context.Context, status string) ([]userdomain.Region, error) {
status = strings.TrimSpace(status)
if status != "" && status != userdomain.RegionStatusActive && status != userdomain.RegionStatusDisabled {
return nil, xerr.New(xerr.InvalidArgument, "region status is invalid")
}
if s.regionAdminRepository == nil {
return nil, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.ListRegions(ctx, userdomain.RegionFilter{Status: status})
}
// GetRegion 返回单个区域及其 active 国家列表。
func (s *Service) GetRegion(ctx context.Context, regionID int64) (userdomain.Region, error) {
if regionID <= 0 {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "region_id is required")
}
if s.regionAdminRepository == nil {
return userdomain.Region{}, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.GetRegion(ctx, regionID)
}
// ReplaceRegionCountries 原子替换区域国家归属,并为受影响国家创建用户区域重算任务。
func (s *Service) ReplaceRegionCountries(ctx context.Context, regionID int64, countries []string, operatorUserID int64, requestID string) (userdomain.Region, error) {
requestID = strings.TrimSpace(requestID)
if regionID <= 0 {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "region_id is required")
}
normalizedCountries, err := normalizeCountryList(countries)
if err != nil {
return userdomain.Region{}, err
}
if requestID == "" {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.regionAdminRepository == nil {
return userdomain.Region{}, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.ReplaceRegionCountries(ctx, userdomain.ReplaceRegionCountriesCommand{
RegionID: regionID,
Countries: normalizedCountries,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
// DisableRegion 停用区域并释放 active 国家归属。
func (s *Service) DisableRegion(ctx context.Context, regionID int64, operatorUserID int64, requestID string) (userdomain.Region, error) {
requestID = strings.TrimSpace(requestID)
if regionID <= 0 {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "region_id is required")
}
if requestID == "" {
return userdomain.Region{}, xerr.New(xerr.InvalidArgument, "request_id is required")
}
if s.regionAdminRepository == nil {
return userdomain.Region{}, xerr.New(xerr.Unavailable, "region repository is not configured")
}
return s.regionAdminRepository.DisableRegion(ctx, userdomain.DisableRegionCommand{
RegionID: regionID,
OperatorUserID: operatorUserID,
RequestID: requestID,
NowMs: s.now().UnixMilli(),
})
}
func validateCountryPayload(countryName string, countryCode string, countryDisplayName string) error {
if !userdomain.ValidCountryCode(countryCode) {
return xerr.New(xerr.InvalidArgument, "country_code must use 2 to 3 uppercase letters")
}
return validateCountryNames(countryName, countryDisplayName)
}
func validateCountryNames(countryName string, countryDisplayName string) error {
if countryName == "" || utf8.RuneCountInString(countryName) > maxCountryNameLength {
return xerr.New(xerr.InvalidArgument, "country_name length is invalid")
}
if countryDisplayName == "" || utf8.RuneCountInString(countryDisplayName) > maxCountryDisplayNameLength {
return xerr.New(xerr.InvalidArgument, "country_display_name length is invalid")
}
return nil
}
func validateRegionPayload(regionCode string, name string) error {
if !userdomain.ValidRegionCode(regionCode) {
return xerr.New(xerr.InvalidArgument, "region_code is invalid")
}
if name == "" || utf8.RuneCountInString(name) > maxRegionNameLength {
return xerr.New(xerr.InvalidArgument, "region name length is invalid")
}
return nil
}
func normalizeCountryList(countries []string) ([]string, error) {
if len(countries) == 0 {
return nil, xerr.New(xerr.InvalidArgument, "countries is required")
}
seen := make(map[string]struct{}, len(countries))
result := make([]string, 0, len(countries))
for _, country := range countries {
country = userdomain.NormalizeCountryCode(country)
if !userdomain.ValidCountryCode(country) {
return nil, xerr.New(xerr.InvalidArgument, "country_code must use 2 to 3 uppercase letters")
}
if _, exists := seen[country]; exists {
return nil, xerr.New(xerr.InvalidArgument, "countries must not contain duplicates")
}
seen[country] = struct{}{}
result = append(result, country)
}
return result, nil
}