209 lines
8.1 KiB
Go
209 lines
8.1 KiB
Go
package user
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"unicode/utf8"
|
||
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
userdomain "hyapp/services/user-service/internal/domain/user"
|
||
)
|
||
|
||
const (
|
||
maxCountryNameLength = 128
|
||
maxCountryDisplayNameLength = 128
|
||
maxCountryFlagLength = 16
|
||
maxRegionNameLength = 128
|
||
)
|
||
|
||
// UpdateCountry 修改国家展示字段;国家码不允许原地修改。
|
||
func (s *Service) UpdateCountry(ctx context.Context, command userdomain.UpdateCountryCommand) (userdomain.Country, error) {
|
||
command = normalizeUpdateCountryCommand(command)
|
||
command.AppCode = appcode.FromContext(ctx)
|
||
if command.CountryID <= 0 {
|
||
return userdomain.Country{}, xerr.New(xerr.InvalidArgument, "country_id is required")
|
||
}
|
||
if err := validateCountryMetadata(command.CountryName, command.ISOAlpha3, command.ISONumeric, command.CountryDisplayName, command.PhoneCountryCode, command.Flag); err != nil {
|
||
return userdomain.Country{}, err
|
||
}
|
||
if strings.TrimSpace(command.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")
|
||
}
|
||
|
||
command.NowMs = s.now().UnixMilli()
|
||
return s.countryAdminRepository.UpdateCountry(ctx, command)
|
||
}
|
||
|
||
// ListCountries 返回国家主数据;enabled 为空时返回全部国家。
|
||
func (s *Service) ListCountries(ctx context.Context, enabled *bool) ([]userdomain.Country, error) {
|
||
if s.countryAdminRepository == nil {
|
||
return nil, xerr.New(xerr.Unavailable, "country repository is not configured")
|
||
}
|
||
|
||
return s.countryAdminRepository.ListCountries(ctx, userdomain.CountryFilter{AppCode: appcode.FromContext(ctx), Enabled: enabled})
|
||
}
|
||
|
||
// ListRegistrationCountries 返回 App 注册页可选国家列表。
|
||
func (s *Service) ListRegistrationCountries(ctx context.Context) ([]userdomain.Country, error) {
|
||
if s.countryRegionRepository == nil {
|
||
return nil, xerr.New(xerr.Unavailable, "country repository is not configured")
|
||
}
|
||
|
||
return s.countryRegionRepository.ListRegistrationCountries(ctx)
|
||
}
|
||
|
||
// 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{
|
||
AppCode: appcode.FromContext(ctx),
|
||
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{AppCode: appcode.FromContext(ctx), 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{
|
||
AppCode: appcode.FromContext(ctx),
|
||
RegionID: regionID,
|
||
Countries: normalizedCountries,
|
||
OperatorUserID: operatorUserID,
|
||
RequestID: requestID,
|
||
NowMs: s.now().UnixMilli(),
|
||
})
|
||
}
|
||
|
||
func normalizeUpdateCountryCommand(command userdomain.UpdateCountryCommand) userdomain.UpdateCountryCommand {
|
||
command.CountryName = strings.TrimSpace(command.CountryName)
|
||
command.ISOAlpha3 = userdomain.NormalizeISOAlpha3(command.ISOAlpha3)
|
||
command.ISONumeric = userdomain.NormalizeISONumeric(command.ISONumeric)
|
||
command.CountryDisplayName = strings.TrimSpace(command.CountryDisplayName)
|
||
command.PhoneCountryCode = userdomain.NormalizePhoneCountryCode(command.PhoneCountryCode)
|
||
command.Flag = strings.TrimSpace(command.Flag)
|
||
command.RequestID = strings.TrimSpace(command.RequestID)
|
||
return command
|
||
}
|
||
|
||
func validateCountryMetadata(countryName string, isoAlpha3 string, isoNumeric string, countryDisplayName string, phoneCountryCode string, flag string) error {
|
||
if countryName == "" || utf8.RuneCountInString(countryName) > maxCountryNameLength {
|
||
return xerr.New(xerr.InvalidArgument, "country_name length is invalid")
|
||
}
|
||
if !userdomain.ValidISOAlpha3(isoAlpha3) {
|
||
return xerr.New(xerr.InvalidArgument, "iso_alpha3 is invalid")
|
||
}
|
||
if !userdomain.ValidISONumeric(isoNumeric) {
|
||
return xerr.New(xerr.InvalidArgument, "iso_numeric is invalid")
|
||
}
|
||
if countryDisplayName == "" || utf8.RuneCountInString(countryDisplayName) > maxCountryDisplayNameLength {
|
||
return xerr.New(xerr.InvalidArgument, "country_display_name length is invalid")
|
||
}
|
||
if !userdomain.ValidPhoneCountryCode(phoneCountryCode) {
|
||
return xerr.New(xerr.InvalidArgument, "phone_country_code is invalid")
|
||
}
|
||
if utf8.RuneCountInString(flag) > maxCountryFlagLength {
|
||
return xerr.New(xerr.InvalidArgument, "flag is too long")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func validateRegionPayload(regionCode string, name string) error {
|
||
if !userdomain.ValidRegionCode(regionCode) {
|
||
return xerr.New(xerr.InvalidArgument, "region_code is invalid")
|
||
}
|
||
if userdomain.IsRetiredRegionCode(regionCode) {
|
||
return xerr.New(xerr.InvalidArgument, "region has been retired")
|
||
}
|
||
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
|
||
}
|