289 lines
11 KiB
Go
289 lines
11 KiB
Go
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
|
||
}
|