feat: 仓库地址排序备注与用户注册错误提示
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
仓库地址增加排序/备注字段与迁移;用户创建时对邮箱冲突等给出明确中文提示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c3d21dfdbb
commit
6852135774
2
db.sql
2
db.sql
@ -1329,6 +1329,8 @@ CREATE TABLE `t_warehouse_address` (
|
|||||||
`Recipient` VARCHAR(64) NULL COMMENT '收件人',
|
`Recipient` VARCHAR(64) NULL COMMENT '收件人',
|
||||||
`ContactPhone` VARCHAR(32) NULL COMMENT '联系电话',
|
`ContactPhone` VARCHAR(32) NULL COMMENT '联系电话',
|
||||||
`ShippingAddress` TEXT COMMENT '收货地址',
|
`ShippingAddress` TEXT COMMENT '收货地址',
|
||||||
|
`Sort` INT NULL DEFAULT NULL COMMENT '展示排序,越小越靠前',
|
||||||
|
`Remark` VARCHAR(512) NULL DEFAULT NULL COMMENT '备注',
|
||||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
|
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
|
||||||
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
|
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
|
||||||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||||
|
|||||||
@ -624,3 +624,44 @@ ALTER TABLE `t_customer_export_qualification`
|
|||||||
MODIFY COLUMN `CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
MODIFY COLUMN `CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
MODIFY COLUMN `UpdateUserId` INT NULL COMMENT '更新人',
|
MODIFY COLUMN `UpdateUserId` INT NULL COMMENT '更新人',
|
||||||
MODIFY COLUMN `UpdateTime` DATETIME NULL COMMENT '更新时间';
|
MODIFY COLUMN `UpdateTime` DATETIME NULL COMMENT '更新时间';
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- migrate_warehouse_address_sort_remark.sql
|
||||||
|
-- 仓库地å<C2B0>€ï¼šå±•示排åº<C3A5>(选填ï¼? 备注(选填ï¼?
|
||||||
|
-- =============================================================================
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Sort'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Sort` INT NULL DEFAULT NULL COMMENT ''展示排åº<EFBFBD>,越å°<EFBFBD>è¶Šé<EFBFBD> å‰<EFBFBD>'' AFTER `ShippingAddress`',
|
||||||
|
'SELECT ''Sort already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Remark'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Remark` VARCHAR(512) NULL DEFAULT NULL COMMENT ''备注'' AFTER `Sort`',
|
||||||
|
'SELECT ''Remark already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
|||||||
39
sql/migrate_warehouse_address_sort_remark.sql
Normal file
39
sql/migrate_warehouse_address_sort_remark.sql
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- migrate_warehouse_address_sort_remark.sql
|
||||||
|
-- 仓库地址:展示排序(选填)+ 备注(选填)
|
||||||
|
-- =============================================================================
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Sort'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Sort` INT NULL DEFAULT NULL COMMENT ''展示排序,越小越靠前'' AFTER `ShippingAddress`',
|
||||||
|
'SELECT ''Sort already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Remark'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Remark` VARCHAR(512) NULL DEFAULT NULL COMMENT ''备注'' AFTER `Sort`',
|
||||||
|
'SELECT ''Remark already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
@ -37,4 +37,15 @@ public class CreateWarehouseInput
|
|||||||
/// 发货地址。
|
/// 发货地址。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ShippingAddress { get; set; }
|
public string? ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展示排序(越小越靠前;可不填)。
|
||||||
|
/// </summary>
|
||||||
|
public int? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注(可不填)。
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(512)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,4 +43,15 @@ public class UpdateWarehouseInput
|
|||||||
/// 发货地址。
|
/// 发货地址。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ShippingAddress { get; set; }
|
public string? ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展示排序(越小越靠前;可不填)。
|
||||||
|
/// </summary>
|
||||||
|
public int? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注(可不填)。
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(512)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,16 @@ public class WarehouseOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ShippingAddress { get; set; }
|
public string? ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展示排序(越小越靠前;为空则排在有排序值的记录之后)。
|
||||||
|
/// </summary>
|
||||||
|
public int? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注。
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间。
|
/// 创建时间。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -40,6 +40,16 @@ public class WarehousePaginatedOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ShippingAddress { get; set; }
|
public string? ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展示排序(越小越靠前;为空则排在有排序值的记录之后)。
|
||||||
|
/// </summary>
|
||||||
|
public int? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注。
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间。
|
/// 创建时间。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -5,11 +5,13 @@ using Fake.Rbac.Application.Dtos.Role;
|
|||||||
using Fake.Rbac.Application.Dtos.User;
|
using Fake.Rbac.Application.Dtos.User;
|
||||||
using Fake.Rbac.Application.Services;
|
using Fake.Rbac.Application.Services;
|
||||||
using Fake.Rbac.Domain.Managers;
|
using Fake.Rbac.Domain.Managers;
|
||||||
|
using Fake.Rbac.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Fake.ObjectStorage;
|
using Fake.ObjectStorage;
|
||||||
using Oms.Application.Dtos.Rbac;
|
using Oms.Application.Dtos.Rbac;
|
||||||
using Oms.Application.Rbac;
|
using Oms.Application.Rbac;
|
||||||
|
using Oms.Application.Shared;
|
||||||
using Oms.Application.Storage;
|
using Oms.Application.Storage;
|
||||||
using Oms.Domain.SystemAggregate.Entities;
|
using Oms.Domain.SystemAggregate.Entities;
|
||||||
using Oms.Domain.SystemAggregate.Enums;
|
using Oms.Domain.SystemAggregate.Enums;
|
||||||
@ -27,6 +29,7 @@ public class OmsUserService(
|
|||||||
AccountManager accountManager,
|
AccountManager accountManager,
|
||||||
UserProfileRepository profileRepository,
|
UserProfileRepository profileRepository,
|
||||||
IEmployeeRepository employeeRepository,
|
IEmployeeRepository employeeRepository,
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
IObjectStorage fileStorage) : ApplicationService
|
IObjectStorage fileStorage) : ApplicationService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -94,34 +97,78 @@ public class OmsUserService(
|
|||||||
[FromBody] OmsUserCreateInput input,
|
[FromBody] OmsUserCreateInput input,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var employeeNo = await ResolveEmployeeNoForCreateAsync(input.EmployeeNo, cancellationToken);
|
var account = input.Account?.Trim() ?? string.Empty;
|
||||||
var user = await userService.CreateAsync(new UserCreateDto
|
OmsRbacRegistrationGuard.ValidateAdminUserFields(
|
||||||
{
|
input.Name,
|
||||||
Name = input.Name,
|
account,
|
||||||
Account = input.Account,
|
input.Password,
|
||||||
Password = input.Password,
|
input.Email,
|
||||||
Email = input.Email,
|
input.Phone,
|
||||||
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
|
input.WechatId,
|
||||||
OrganizationId = input.OrganizationId,
|
requirePassword: true);
|
||||||
RoleIds = input.RoleIds,
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
await profileRepository.SaveAsync(
|
var employeeNo = await ResolveEmployeeNoForCreateAsync(input.EmployeeNo, cancellationToken);
|
||||||
user.Id,
|
await OmsRbacRegistrationGuard.EnsureLoginIdentityAvailableAsync(
|
||||||
new UserProfileData
|
rbacDbContext,
|
||||||
{
|
account,
|
||||||
Phone = input.Phone,
|
input.Email,
|
||||||
EmployeeNo = employeeNo,
|
forAdminUserCreate: true,
|
||||||
WechatId = input.WechatId,
|
excludeUserId: null,
|
||||||
WechatQrCode = input.WechatQrCode,
|
cancellationToken);
|
||||||
WecomKfUrl = input.WecomKfUrl,
|
await OmsRbacRegistrationGuard.EnsurePhoneAvailableAsync(
|
||||||
},
|
rbacDbContext,
|
||||||
|
input.Phone,
|
||||||
|
excludeUserId: null,
|
||||||
|
cancellationToken);
|
||||||
|
await OmsRbacRegistrationGuard.EnsureWechatIdAvailableAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
input.WechatId,
|
||||||
|
excludeUserId: null,
|
||||||
|
cancellationToken);
|
||||||
|
await OmsRbacRegistrationGuard.EnsureOrganizationExistsAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
input.OrganizationId,
|
||||||
|
cancellationToken);
|
||||||
|
await OmsRbacRegistrationGuard.EnsureRolesExistAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
input.RoleIds,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
await EnsureEmployeeProfileAsync(user.Id, input, cancellationToken);
|
try
|
||||||
|
{
|
||||||
|
var user = await userService.CreateAsync(new UserCreateDto
|
||||||
|
{
|
||||||
|
Name = input.Name!.Trim(),
|
||||||
|
Account = account,
|
||||||
|
Password = input.Password,
|
||||||
|
// 禁止 Email='':uk_user_email 会把空串当成占用
|
||||||
|
Email = string.IsNullOrWhiteSpace(input.Email) ? null : input.Email.Trim(),
|
||||||
|
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
|
||||||
|
OrganizationId = input.OrganizationId,
|
||||||
|
RoleIds = input.RoleIds,
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
var profile = await profileRepository.GetAsync(user.Id, cancellationToken);
|
await profileRepository.SaveAsync(
|
||||||
return MapUserDetail(user, profile);
|
user.Id,
|
||||||
|
new UserProfileData
|
||||||
|
{
|
||||||
|
Phone = input.Phone,
|
||||||
|
EmployeeNo = employeeNo,
|
||||||
|
WechatId = input.WechatId,
|
||||||
|
WechatQrCode = input.WechatQrCode,
|
||||||
|
WecomKfUrl = input.WecomKfUrl,
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
await EnsureEmployeeProfileAsync(user.Id, input, cancellationToken);
|
||||||
|
|
||||||
|
var profile = await profileRepository.GetAsync(user.Id, cancellationToken);
|
||||||
|
return MapUserDetail(user, profile);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not BusinessException)
|
||||||
|
{
|
||||||
|
throw OmsRbacRegistrationGuard.ToSaveBusinessException(ex, forAdminUserCreate: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -133,30 +180,75 @@ public class OmsUserService(
|
|||||||
[FromBody] OmsUserUpdateInput input,
|
[FromBody] OmsUserUpdateInput input,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var user = await userService.UpdateAsync(
|
if (id == Guid.Empty)
|
||||||
id,
|
{
|
||||||
new UserUpdateDto
|
throw new BusinessException("用户 Id 无效");
|
||||||
{
|
}
|
||||||
Name = input.Name,
|
|
||||||
Email = input.Email,
|
// 更新不改登录账号/密码
|
||||||
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
|
OmsRbacRegistrationGuard.ValidateAdminUserFields(
|
||||||
OrganizationId = input.OrganizationId,
|
input.Name,
|
||||||
},
|
account: "placeholder",
|
||||||
|
password: null,
|
||||||
|
input.Email,
|
||||||
|
input.Phone,
|
||||||
|
input.WechatId,
|
||||||
|
requirePassword: false);
|
||||||
|
|
||||||
|
var email = string.IsNullOrWhiteSpace(input.Email) ? null : input.Email.Trim();
|
||||||
|
await OmsRbacRegistrationGuard.EnsureEmailAvailableAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
email,
|
||||||
|
excludeUserId: id,
|
||||||
|
forAdminUserCreate: true,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
await profileRepository.UpdateContactAsync(
|
await OmsRbacRegistrationGuard.EnsurePhoneAvailableAsync(
|
||||||
id,
|
rbacDbContext,
|
||||||
new UserProfileData
|
input.Phone,
|
||||||
{
|
excludeUserId: id,
|
||||||
Phone = input.Phone,
|
cancellationToken);
|
||||||
WechatId = input.WechatId,
|
await OmsRbacRegistrationGuard.EnsureWechatIdAvailableAsync(
|
||||||
WechatQrCode = input.WechatQrCode,
|
rbacDbContext,
|
||||||
WecomKfUrl = input.WecomKfUrl,
|
input.WechatId,
|
||||||
},
|
excludeUserId: id,
|
||||||
|
cancellationToken);
|
||||||
|
await OmsRbacRegistrationGuard.EnsureOrganizationExistsAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
input.OrganizationId,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
var profile = await profileRepository.GetAsync(id, cancellationToken);
|
try
|
||||||
return MapUserDetail(user, profile);
|
{
|
||||||
|
var user = await userService.UpdateAsync(
|
||||||
|
id,
|
||||||
|
new UserUpdateDto
|
||||||
|
{
|
||||||
|
Name = input.Name!.Trim(),
|
||||||
|
Email = email,
|
||||||
|
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
|
||||||
|
OrganizationId = input.OrganizationId,
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
await profileRepository.UpdateContactAsync(
|
||||||
|
id,
|
||||||
|
new UserProfileData
|
||||||
|
{
|
||||||
|
Phone = input.Phone,
|
||||||
|
WechatId = input.WechatId,
|
||||||
|
WechatQrCode = input.WechatQrCode,
|
||||||
|
WecomKfUrl = input.WecomKfUrl,
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var profile = await profileRepository.GetAsync(id, cancellationToken);
|
||||||
|
return MapUserDetail(user, profile);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not BusinessException)
|
||||||
|
{
|
||||||
|
throw OmsRbacRegistrationGuard.ToSaveBusinessException(ex, forAdminUserCreate: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -259,12 +351,12 @@ public class OmsUserService(
|
|||||||
|
|
||||||
if (!UserProfileRepository.IsValidEmployeeNo(employeeNo))
|
if (!UserProfileRepository.IsValidEmployeeNo(employeeNo))
|
||||||
{
|
{
|
||||||
throw new BusinessException("工号格式无效,应为 IDE + 至少 3 位数字,如 IDE001");
|
throw new BusinessException($"工号「{employeeNo}」格式无效,应为 IDE + 至少 3 位数字,如 IDE001");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await profileRepository.IsEmployeeNoTakenAsync(employeeNo, null, cancellationToken))
|
if (await profileRepository.IsEmployeeNoTakenAsync(employeeNo, null, cancellationToken))
|
||||||
{
|
{
|
||||||
throw new BusinessException($"工号已存在:{employeeNo}");
|
throw new BusinessException($"工号「{employeeNo}」已存在,请更换工号后再创建");
|
||||||
}
|
}
|
||||||
|
|
||||||
return employeeNo;
|
return employeeNo;
|
||||||
|
|||||||
@ -33,13 +33,16 @@ public class WarehouseService : ApplicationService
|
|||||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
|
|
||||||
var entities = await WarehouseAddressRepository.GetPagedListAsync(
|
// 有 Sort 的按升序靠前;无 Sort 的靠后;同序再按 Id
|
||||||
|
var (entities, totalCount) = await RepositoryPageHelper.GetPageAsync(
|
||||||
|
WarehouseAddressRepository,
|
||||||
predicate,
|
predicate,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
new Dictionary<string, bool> { [nameof(WarehouseAddress.UpdateTime)] = false, [nameof(WarehouseAddress.Id)] = false });
|
q => q.OrderBy(x => x.Sort == null)
|
||||||
|
.ThenBy(x => x.Sort)
|
||||||
|
.ThenBy(x => x.Id));
|
||||||
|
|
||||||
var totalCount = await WarehouseAddressRepository.CountAsync(predicate);
|
|
||||||
var items = entities.Select(entity =>
|
var items = entities.Select(entity =>
|
||||||
{
|
{
|
||||||
var output = ObjectMapper.Map<WarehouseAddress, WarehousePaginatedOutput>(entity);
|
var output = ObjectMapper.Map<WarehouseAddress, WarehousePaginatedOutput>(entity);
|
||||||
@ -97,6 +100,8 @@ public class WarehouseService : ApplicationService
|
|||||||
deleted.Recipient = input.Recipient;
|
deleted.Recipient = input.Recipient;
|
||||||
deleted.ContactPhone = input.ContactPhone;
|
deleted.ContactPhone = input.ContactPhone;
|
||||||
deleted.ShippingAddress = input.ShippingAddress;
|
deleted.ShippingAddress = input.ShippingAddress;
|
||||||
|
deleted.Sort = input.Sort;
|
||||||
|
deleted.Remark = NormalizeRemark(input.Remark);
|
||||||
await WarehouseAddressRepository.UpdateAsync(deleted);
|
await WarehouseAddressRepository.UpdateAsync(deleted);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -108,6 +113,8 @@ public class WarehouseService : ApplicationService
|
|||||||
Recipient = input.Recipient,
|
Recipient = input.Recipient,
|
||||||
ContactPhone = input.ContactPhone,
|
ContactPhone = input.ContactPhone,
|
||||||
ShippingAddress = input.ShippingAddress,
|
ShippingAddress = input.ShippingAddress,
|
||||||
|
Sort = input.Sort,
|
||||||
|
Remark = NormalizeRemark(input.Remark),
|
||||||
};
|
};
|
||||||
|
|
||||||
await WarehouseAddressRepository.InsertAsync(entity);
|
await WarehouseAddressRepository.InsertAsync(entity);
|
||||||
@ -141,6 +148,8 @@ public class WarehouseService : ApplicationService
|
|||||||
entity.Recipient = input.Recipient;
|
entity.Recipient = input.Recipient;
|
||||||
entity.ContactPhone = input.ContactPhone;
|
entity.ContactPhone = input.ContactPhone;
|
||||||
entity.ShippingAddress = input.ShippingAddress;
|
entity.ShippingAddress = input.ShippingAddress;
|
||||||
|
entity.Sort = input.Sort;
|
||||||
|
entity.Remark = NormalizeRemark(input.Remark);
|
||||||
|
|
||||||
await WarehouseAddressRepository.UpdateAsync(entity);
|
await WarehouseAddressRepository.UpdateAsync(entity);
|
||||||
}
|
}
|
||||||
@ -180,4 +189,10 @@ public class WarehouseService : ApplicationService
|
|||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? NormalizeRemark(string? remark)
|
||||||
|
{
|
||||||
|
var value = remark?.Trim();
|
||||||
|
return string.IsNullOrEmpty(value) ? null : value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using System.Net.Mail;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Rbac.Infrastructure;
|
using Fake.Rbac.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -5,32 +7,276 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
namespace Oms.Application.Shared;
|
namespace Oms.Application.Shared;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信/客户注册前校验 RBAC 登录账号,避免 user 表已存在但 t_customer 未创建时 EF 抛出英文 DbUpdateException。
|
/// 微信/客户注册、后台建用户前校验 RBAC 登录身份,避免 EF 抛出英文 DbUpdateException。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class OmsRbacRegistrationGuard
|
public static class OmsRbacRegistrationGuard
|
||||||
{
|
{
|
||||||
|
private static readonly Regex PhonePattern = new(@"^1\d{10}$", RegexOptions.Compiled);
|
||||||
|
|
||||||
public static async Task EnsureLoginIdentityAvailableAsync(
|
public static async Task EnsureLoginIdentityAvailableAsync(
|
||||||
FakeRbacDbContext rbacDbContext,
|
FakeRbacDbContext rbacDbContext,
|
||||||
string account,
|
string account,
|
||||||
string? email,
|
string? email,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await EnsureLoginIdentityAvailableAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
account,
|
||||||
|
email,
|
||||||
|
forAdminUserCreate: false,
|
||||||
|
excludeUserId: null,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验登录账号/邮箱是否可用。
|
||||||
|
/// <paramref name="forAdminUserCreate"/> 为 true 时提示面向后台建员工用户(会说明可能已被客户账号占用)。
|
||||||
|
/// </summary>
|
||||||
|
public static async Task EnsureLoginIdentityAvailableAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string account,
|
||||||
|
string? email,
|
||||||
|
bool forAdminUserCreate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await EnsureLoginIdentityAvailableAsync(
|
||||||
|
rbacDbContext,
|
||||||
|
account,
|
||||||
|
email,
|
||||||
|
forAdminUserCreate,
|
||||||
|
excludeUserId: null,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureLoginIdentityAvailableAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string account,
|
||||||
|
string? email,
|
||||||
|
bool forAdminUserCreate,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var normalizedAccount = account.Trim();
|
var normalizedAccount = account.Trim();
|
||||||
var normalizedEmail = string.IsNullOrWhiteSpace(email) ? null : email.Trim();
|
var normalizedEmail = string.IsNullOrWhiteSpace(email) ? null : email.Trim();
|
||||||
|
|
||||||
if (await ExistsActiveUserByAccountAsync(rbacDbContext, normalizedAccount, cancellationToken))
|
if (await ExistsUserByAccountAsync(rbacDbContext, normalizedAccount, excludeUserId, cancellationToken))
|
||||||
{
|
{
|
||||||
throw new BusinessException("登录账号已存在,请直接绑定或使用其他账号");
|
throw new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? $"登录账号「{normalizedAccount}」已存在。客户门户注册也会占用同一登录账号,请更换账号后再创建员工用户"
|
||||||
|
: "登录账号已存在,请直接绑定或使用其他账号");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedEmail != null
|
if (normalizedEmail != null
|
||||||
&& await ExistsActiveUserByEmailAsync(rbacDbContext, normalizedEmail, cancellationToken))
|
&& await ExistsUserByEmailAsync(rbacDbContext, normalizedEmail, excludeUserId, cancellationToken))
|
||||||
{
|
{
|
||||||
throw new BusinessException("邮箱已被占用,请更换邮箱或直接绑定已有账号");
|
throw new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? $"邮箱「{normalizedEmail}」已被占用。客户门户注册也会占用同一邮箱,请更换邮箱后再创建员工用户"
|
||||||
|
: "邮箱已被占用,请更换邮箱或直接绑定已有账号");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BusinessException ToSaveBusinessException(Exception ex)
|
/// <summary>
|
||||||
|
/// 后台创建/更新员工用户时的字段格式与必填校验(不含唯一性)。
|
||||||
|
/// </summary>
|
||||||
|
public static void ValidateAdminUserFields(
|
||||||
|
string? name,
|
||||||
|
string? account,
|
||||||
|
string? password,
|
||||||
|
string? email,
|
||||||
|
string? phone,
|
||||||
|
string? wechatId,
|
||||||
|
bool requirePassword)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
throw new BusinessException("姓名不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Trim().Length > 64)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"姓名过长(当前 {name.Trim().Length} 字,最多 64 字)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(account))
|
||||||
|
{
|
||||||
|
throw new BusinessException("登录账号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.Trim().Length > 64)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"登录账号过长(当前 {account.Trim().Length} 字,最多 64 字)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requirePassword)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
{
|
||||||
|
throw new BusinessException("密码不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.Length < 6)
|
||||||
|
{
|
||||||
|
throw new BusinessException("密码长度不能少于6位");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.Length > 128)
|
||||||
|
{
|
||||||
|
throw new BusinessException("密码过长,请控制在 128 位以内");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(email))
|
||||||
|
{
|
||||||
|
var trimmedEmail = email.Trim();
|
||||||
|
if (trimmedEmail.Length > 128)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"邮箱过长(当前 {trimmedEmail.Length} 字,最多 128 字)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValidEmail(trimmedEmail))
|
||||||
|
{
|
||||||
|
throw new BusinessException($"邮箱格式不正确:{trimmedEmail}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(phone))
|
||||||
|
{
|
||||||
|
var trimmedPhone = phone.Trim();
|
||||||
|
if (!PhonePattern.IsMatch(trimmedPhone))
|
||||||
|
{
|
||||||
|
throw new BusinessException($"手机号格式不正确:{trimmedPhone}(应为 11 位中国大陆手机号)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(wechatId) && wechatId.Trim().Length > 64)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"微信号过长(当前 {wechatId.Trim().Length} 字,最多 64 字)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureOrganizationExistsAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
Guid? organizationId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (organizationId is null || organizationId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"""
|
||||||
|
SELECT 1 AS Value
|
||||||
|
FROM `organization`
|
||||||
|
WHERE `Id` = {organizationId.Value.ToString()} AND `IsDeleted` = 0
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!hit.HasValue)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"所属组织不存在或已删除({organizationId}),请重新选择组织");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureRolesExistAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
IEnumerable<Guid>? roleIds,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (roleIds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids = roleIds.Where(x => x != Guid.Empty).Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var roleId in ids)
|
||||||
|
{
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"""
|
||||||
|
SELECT 1 AS Value
|
||||||
|
FROM `role`
|
||||||
|
WHERE `Id` = {roleId.ToString()} AND `IsDeleted` = 0
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!hit.HasValue)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"角色不存在或已删除({roleId}),请重新选择角色");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureEmailAvailableAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string? email,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
bool forAdminUserCreate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedEmail = email.Trim();
|
||||||
|
if (await ExistsUserByEmailAsync(rbacDbContext, normalizedEmail, excludeUserId, cancellationToken))
|
||||||
|
{
|
||||||
|
throw new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? $"邮箱「{normalizedEmail}」已被占用。客户门户注册也会占用同一邮箱,请更换邮箱后再保存"
|
||||||
|
: "邮箱已被占用,请更换邮箱或直接绑定已有账号");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsurePhoneAvailableAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string? phone,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(phone))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = phone.Trim();
|
||||||
|
if (await ExistsUserByPhoneAsync(rbacDbContext, normalized, excludeUserId, cancellationToken))
|
||||||
|
{
|
||||||
|
throw new BusinessException(
|
||||||
|
$"手机号「{normalized}」已被占用。客户门户注册也会占用同一手机号,请更换手机号后再保存");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureWechatIdAvailableAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string? wechatId,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(wechatId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = wechatId.Trim();
|
||||||
|
if (await ExistsUserByWechatIdAsync(rbacDbContext, normalized, excludeUserId, cancellationToken))
|
||||||
|
{
|
||||||
|
throw new BusinessException($"微信号「{normalized}」已被占用,请更换后再保存");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BusinessException ToSaveBusinessException(Exception ex, bool forAdminUserCreate = false)
|
||||||
{
|
{
|
||||||
var message = ExtractDeepMessage(ex);
|
var message = ExtractDeepMessage(ex);
|
||||||
if (ContainsDuplicateKey(message))
|
if (ContainsDuplicateKey(message))
|
||||||
@ -39,19 +285,41 @@ public static class OmsRbacRegistrationGuard
|
|||||||
|| message.Contains("uk_user_account", StringComparison.OrdinalIgnoreCase)
|
|| message.Contains("uk_user_account", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("IX_user_Account", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("IX_user_Account", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new BusinessException("登录账号已存在,请直接绑定或使用其他账号");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? "登录账号已存在。客户门户注册也会占用同一登录账号,请更换账号后再创建员工用户"
|
||||||
|
: "登录账号已存在,请直接绑定或使用其他账号");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("Email", StringComparison.OrdinalIgnoreCase)
|
if (message.Contains("Email", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("uk_user_email", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("uk_user_email", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new BusinessException("邮箱已被占用,请更换邮箱或直接绑定已有账号");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? "邮箱已被占用。客户门户注册也会占用同一邮箱,请更换邮箱后再创建员工用户"
|
||||||
|
: "邮箱已被占用,请更换邮箱或直接绑定已有账号");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("Phone", StringComparison.OrdinalIgnoreCase)
|
if (message.Contains("Phone", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("uk_user_phone", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("uk_cus_phone", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("uk_cus_phone", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new BusinessException("手机号已存在");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? "手机号已被占用。客户门户注册也会占用同一手机号,请更换手机号后再保存"
|
||||||
|
: "手机号已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("EmployeeNo", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("uk_user_employee", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new BusinessException("工号已被占用,请更换工号后再保存");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("WechatId", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("uk_user_wechat", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new BusinessException("微信号已被占用,请更换后再保存");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("uk_cus_email", StringComparison.OrdinalIgnoreCase))
|
if (message.Contains("uk_cus_email", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -66,41 +334,125 @@ public static class OmsRbacRegistrationGuard
|
|||||||
return new BusinessException("当前微信已绑定账号");
|
return new BusinessException("当前微信已绑定账号");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BusinessException("账号信息已存在,请直接绑定或更换手机号/邮箱");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? "账号信息与已有用户冲突(登录账号/邮箱/手机号等唯一字段),请检查后更换"
|
||||||
|
: "账号信息已存在,请直接绑定或更换手机号/邮箱");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("Data too long", StringComparison.OrdinalIgnoreCase)
|
if (message.Contains("Data too long", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("1406", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("1406", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (message.Contains("Avatar", StringComparison.OrdinalIgnoreCase))
|
var field = ResolveTooLongField(message, forAdminUserCreate);
|
||||||
|
if (field != null)
|
||||||
{
|
{
|
||||||
return new BusinessException("微信头像地址过长,请稍后重试或联系客服");
|
return new BusinessException(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("Nickname", StringComparison.OrdinalIgnoreCase)
|
return new BusinessException(
|
||||||
|| message.Contains("CustomerName", StringComparison.OrdinalIgnoreCase)
|
forAdminUserCreate
|
||||||
|| message.Contains("Name", StringComparison.OrdinalIgnoreCase))
|
? "保存失败:某个字段内容过长,请缩短后重试"
|
||||||
{
|
: "注册信息字段过长,请缩短客户名称后重试");
|
||||||
return new BusinessException("客户名称或微信昵称过长,请修改后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BusinessException("注册信息字段过长,请缩短客户名称后重试");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("foreign key constraint fails", StringComparison.OrdinalIgnoreCase)
|
if (message.Contains("foreign key constraint fails", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("1452", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("1452", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new BusinessException("客户角色未正确初始化,请联系管理员");
|
if (message.Contains("organization", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("Organization", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new BusinessException("所属组织不存在或已删除,请重新选择组织");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("role", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("Role", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new BusinessException("角色不存在或已删除,请重新选择角色");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BusinessException("关联数据无效(组织/角色),请检查后重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.Contains("Cannot insert duplicate key", StringComparison.OrdinalIgnoreCase)
|
if (message.Contains("Cannot insert duplicate key", StringComparison.OrdinalIgnoreCase)
|
||||||
|| message.Contains("2627", StringComparison.OrdinalIgnoreCase))
|
|| message.Contains("2627", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new BusinessException("账号信息已存在,请直接绑定或更换手机号/邮箱");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate
|
||||||
|
? "账号信息与已有用户冲突(登录账号/邮箱/手机号等唯一字段),请检查后更换"
|
||||||
|
: "账号信息已存在,请直接绑定或更换手机号/邮箱");
|
||||||
}
|
}
|
||||||
|
|
||||||
var reason = ExtractActionableReason(ex);
|
var reason = ExtractActionableReason(ex);
|
||||||
return new BusinessException($"注册失败:{reason}");
|
return new BusinessException(
|
||||||
|
forAdminUserCreate ? $"保存用户失败:{reason}" : $"注册失败:{reason}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResolveTooLongField(string message, bool forAdminUserCreate)
|
||||||
|
{
|
||||||
|
if (message.Contains("Avatar", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return forAdminUserCreate ? "头像地址过长,请更换后重试" : "微信头像地址过长,请稍后重试或联系客服";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("Account", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "登录账号过长,请缩短后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("Email", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "邮箱过长,请缩短后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("Phone", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "手机号过长,请检查后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("EmployeeNo", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "工号过长,请检查后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("WechatId", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "微信号过长,请缩短后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("WechatQrCode", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("WecomKfUrl", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "微信二维码或客服链接过长,请缩短后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("Password", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("PasswordHash", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "密码相关字段过长,请缩短密码后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Contains("Nickname", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("CustomerName", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains("`Name`", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| message.Contains(" column 'Name'", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return forAdminUserCreate ? "姓名过长,请缩短后重试" : "客户名称或微信昵称过长,请修改后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidEmail(string email)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addr = new MailAddress(email);
|
||||||
|
return string.Equals(addr.Address, email, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ExtractActionableReason(Exception ex)
|
private static string ExtractActionableReason(Exception ex)
|
||||||
@ -130,29 +482,93 @@ public static class OmsRbacRegistrationGuard
|
|||||||
return reason.Length <= 160 ? reason : reason[..160];
|
return reason.Length <= 160 ? reason : reason[..160];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<bool> ExistsActiveUserByAccountAsync(
|
private static async Task<bool> ExistsUserByAccountAsync(
|
||||||
FakeRbacDbContext rbacDbContext,
|
FakeRbacDbContext rbacDbContext,
|
||||||
string account,
|
string account,
|
||||||
|
Guid? excludeUserId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 唯一索引不按 IsDeleted 区分,软删除账号仍会触发 Duplicate entry。
|
// 唯一索引不按 IsDeleted 区分,软删除账号仍会触发 Duplicate entry。
|
||||||
var hit = await rbacDbContext.Database
|
if (excludeUserId is Guid id && id != Guid.Empty)
|
||||||
|
{
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `Account` = {account} AND `Id` <> {id.ToString()} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return hit.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var any = await rbacDbContext.Database
|
||||||
.SqlQuery<int?>(
|
.SqlQuery<int?>(
|
||||||
$"SELECT 1 AS Value FROM `user` WHERE `Account` = {account} LIMIT 1")
|
$"SELECT 1 AS Value FROM `user` WHERE `Account` = {account} LIMIT 1")
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
return hit.HasValue;
|
return any.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<bool> ExistsActiveUserByEmailAsync(
|
private static async Task<bool> ExistsUserByEmailAsync(
|
||||||
FakeRbacDbContext rbacDbContext,
|
FakeRbacDbContext rbacDbContext,
|
||||||
string email,
|
string email,
|
||||||
|
Guid? excludeUserId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var hit = await rbacDbContext.Database
|
if (excludeUserId is Guid id && id != Guid.Empty)
|
||||||
|
{
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `Email` = {email} AND `Id` <> {id.ToString()} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return hit.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var any = await rbacDbContext.Database
|
||||||
.SqlQuery<int?>(
|
.SqlQuery<int?>(
|
||||||
$"SELECT 1 AS Value FROM `user` WHERE `Email` = {email} LIMIT 1")
|
$"SELECT 1 AS Value FROM `user` WHERE `Email` = {email} LIMIT 1")
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
return hit.HasValue;
|
return any.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ExistsUserByPhoneAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string phone,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (excludeUserId is Guid id && id != Guid.Empty)
|
||||||
|
{
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `Phone` = {phone} AND `Id` <> {id.ToString()} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return hit.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var any = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `Phone` = {phone} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return any.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ExistsUserByWechatIdAsync(
|
||||||
|
FakeRbacDbContext rbacDbContext,
|
||||||
|
string wechatId,
|
||||||
|
Guid? excludeUserId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (excludeUserId is Guid id && id != Guid.Empty)
|
||||||
|
{
|
||||||
|
var hit = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `WechatId` = {wechatId} AND `Id` <> {id.ToString()} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return hit.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var any = await rbacDbContext.Database
|
||||||
|
.SqlQuery<int?>(
|
||||||
|
$"SELECT 1 AS Value FROM `user` WHERE `WechatId` = {wechatId} LIMIT 1")
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return any.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ExtractDeepMessage(Exception ex)
|
private static string ExtractDeepMessage(Exception ex)
|
||||||
|
|||||||
@ -15,4 +15,16 @@ public class WarehouseAddress : OmsAuditedAggregateRoot
|
|||||||
public string? ContactPhone { get; set; }
|
public string? ContactPhone { get; set; }
|
||||||
|
|
||||||
public string? ShippingAddress { get; set; }
|
public string? ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展示排序(越小越靠前;为空则排在有排序值的记录之后)。
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsNullable = true)]
|
||||||
|
public int? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注(选填,展示用)。
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 512, IsNullable = true)]
|
||||||
|
public string? Remark { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user