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 }