修改User模块

This commit is contained in:
chfei committed 2026-10-23 19:01:28 +08:00
1 parent d6471f97d3
commit bb7162aaf6
21 files changed
+278 -336

No files matched your search

@@ -1,15 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace Store.Users.Contracts.Dtos;
/// <summary>资金请求;DTO 只声明非空规则,金额、长度和操作规则由服务判断。</summary>
public sealed class UserFundsRequest
/// <summary>资金请求;DTO 检查输入,服务区分积分/余额操作并校验权限。</summary>
public sealed class UserFundsRequest : IValidatableObject
{
[Required(ErrorMessage = "目标账户不能为空")] public string UserName { get; set; } = "";
[Required(ErrorMessage = "请求号不能为空")] public string RequestId { get; set; } = "";
[Required(ErrorMessage = "目标账户不能为空"), StringLength(20)] public string UserName { get; set; } = "";
[Required(ErrorMessage = "请求号不能为空"), StringLength(64)] public string RequestId { get; set; } = "";
[Range(typeof(decimal), "0.01", "1000000")]
public decimal Amount { get; set; }
/// <summary>积分支持充值、转移;余额支持充值、消费。</summary>
[Required(ErrorMessage = "操作类型不能为空")] public string Operation { get; set; } = "";
[Required(ErrorMessage = "操作类型不能为空"), StringLength(20)] public string Operation { get; set; } = "";
[StringLength(255)]
public string? Remark { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (decimal.Round(Amount, 2) != Amount)
yield return new ValidationResult("金额最多保留两位小数", [nameof(Amount)]);
}
}
public sealed record UserFundsResponse(string UserName, decimal Value, string RequestId);
@@ -1,22 +1,22 @@
using System.ComponentModel.DataAnnotations;
namespace Store.Users.Contracts.Dtos;
/// <summary>按用户名查询账户;仅声明用户名非空。</summary>
/// <summary>按用户名查询账户;声明用户名非空及长度。</summary>
public sealed class UserNameRequest
{
[Required(ErrorMessage = "用户名不能为空")]
[Required(ErrorMessage = "用户名不能为空"), StringLength(20)]
public string UserName { get; set; } = "";
}
/// <summary>登录输入;长度、账号是否存在及密码匹配由认证服务处理。</summary>
/// <summary>登录输入;输入在 DTO 中检查,账号存在及密码匹配由认证服务处理。</summary>
public sealed class UserLoginRequest
{
[Required(ErrorMessage = "用户名不能为空")] public string UserName { get; set; } = "";
[Required(ErrorMessage = "密码不能为空")] public string Password { get; set; } = "";
[Required(ErrorMessage = "用户名不能为空"), StringLength(20)] public string UserName { get; set; } = "";
[Required(ErrorMessage = "密码不能为空"), StringLength(128)] public string Password { get; set; } = "";
}
/// <summary>按代理编码查询充值二维码。</summary>
public sealed class UserTopUpQrcodeQuery
{
[Required(ErrorMessage = "代理编码不能为空")] public string AgentCode { get; set; } = "";
[Required(ErrorMessage = "代理编码不能为空"), StringLength(50)] public string AgentCode { get; set; } = "";
}
@@ -1,3 +1,4 @@
using Store.Users.Contracts.Enums;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
@@ -33,9 +34,9 @@ namespace Store.Users.Contracts.Dtos
public string CardId { get; set; }
/// <summary>
/// 用户分组 3主号 4客服 5加盟 8登录 9采购 10加盟 11上货
/// 用户分组,参见 <see cref="UserGroup"/>。
/// </summary>
public byte GroupId { get; set; }
public UserGroup GroupId { get; set; }
/// <summary>
/// 父账户用户名
@@ -73,9 +74,9 @@ namespace Store.Users.Contracts.Dtos
public string Notes { get; set; }
/// <summary>
/// 超管等级 0 普通用户 1 超级管理员
/// 超管等级,参见 <see cref="UserAdminLevel"/>。
/// </summary>
public short? SaLevel { get; set; }
public UserAdminLevel? SaLevel { get; set; }
/// <summary>
/// 账户权限
@@ -138,7 +139,7 @@ namespace Store.Users.Contracts.Dtos
/// </summary>
public bool IsMainUser
{
get => string.IsNullOrWhiteSpace(ParentUserName);
get => GroupId == UserGroup.MainAccount && string.IsNullOrWhiteSpace(ParentUserName);
}
/// <summary>
/// 是否OEM租户
@@ -1,30 +1,48 @@
using Store.Users.Contracts.Enums;
using System.ComponentModel.DataAnnotations;
namespace Store.Users.Contracts.Dtos;
/// <summary>账户资料;null 保留原值,空字符串清空文本。长度与范围由服务检查。</summary>
/// <summary>账户资料;null 保留原值,空字符串清空文本。输入约束由 DTO 声明,权限与账户关系由服务检查。</summary>
public class UserEditRequest
{
[StringLength(50)]
public string? NickName { get; set; }
[StringLength(30)]
public string? Mobile { get; set; }
[StringLength(100)]
public string? CompanyName { get; set; }
[StringLength(255)]
public string? Notes { get; set; }
[StringLength(50)]
public string? CardId { get; set; }
public byte? GroupId { get; set; }
[EnumDataType(typeof(UserGroup), ErrorMessage = "请选择有效的用户分组")]
public UserGroup? GroupId { get; set; }
/// <summary>仅管理员可设置;普通账号不能通过资料修改提升管理员级别。</summary>
public short? SaLevel { get; set; }
[EnumDataType(typeof(UserAdminLevel), ErrorMessage = "管理员级别只能为普通用户或超级管理员")]
public UserAdminLevel? SaLevel { get; set; }
[StringLength(1000)]
public string? Authority { get; set; }
[StringLength(1000)]
public string? ForbiddenPage { get; set; }
[StringLength(50)]
public string? OemId { get; set; }
[Range(30, int.MaxValue, ErrorMessage = "每月续费积分不能低于30")]
public int? ShopPointsPerMonth { get; set; }
[Range(typeof(decimal), "2.2", "999999")]
public decimal? PostagePerOrder { get; set; }
[Range(typeof(decimal), "2.5", "999999")]
public decimal? RefundPostagePerOrder { get; set; }
[Range(typeof(decimal), "0", "999999")]
public decimal? GiftOemAmountPerOrder { get; set; }
[Range(typeof(decimal), "0", "999999")]
public decimal? SyncLogisticsPerOrder { get; set; }
}
/// <summary>修改账户资料;账户 ID 与资料字段统一通过 JSON 请求体提交。</summary>
public sealed class UserUpdateRequest : UserEditRequest
{
[Range(1, int.MaxValue, ErrorMessage = "ID必须大于0")]
public int Id { get; set; }
}
@@ -32,17 +50,20 @@ public sealed class UserUpdateRequest : UserEditRequest
public sealed class UserCreateRequest : UserEditRequest
{
/// <summary>创建子账号时的父账号;管理员可指定任意主账号,普通主账号只能指定自己。</summary>
[StringLength(20)]
public string? ParentUserName { get; set; }
[Required(ErrorMessage = "用户名不能为空")] public string UserName { get; set; } = "";
[Required(ErrorMessage = "密码不能为空")] public string Password { get; set; } = "";
[Required(ErrorMessage = "用户名不能为空"), StringLength(20)] public string UserName { get; set; } = "";
[Required(ErrorMessage = "密码不能为空"), StringLength(128, MinimumLength = 6)] public string Password { get; set; } = "";
}
/// <summary>本人改密需原密码;管理员及主账号可为管理范围内的其他账户重置密码。</summary>
/// <summary>非管理员本人改密需原密码;管理员可重置任意账户,主账号可重置直属子账号密码。</summary>
public sealed class ChangePasswordRequest
{
[StringLength(20)]
public string? UserName { get; set; }
[StringLength(128)]
public string? OldPassword { get; set; }
[Required(ErrorMessage = "新密码不能为空")] public string NewPassword { get; set; } = "";
[Required(ErrorMessage = "新密码不能为空"), StringLength(128, MinimumLength = 6)] public string NewPassword { get; set; } = "";
}
/// <summary>本人修改密码时验证原密码非空。</summary>
@@ -51,28 +72,51 @@ public sealed class UserPasswordProofRequest
[Required(ErrorMessage = "原密码不能为空")] public string OldPassword { get; set; } = "";
}
/// <summary>分页参数;页码、分页大小和过滤值由服务检查。</summary>
/// <summary>分页参数;在查询数据库前完成范围校验。</summary>
public class UserSearchRequest
{
[Range(1, 1000000)]
public int Page { get; set; } = 1;
[Range(1, 200)]
public int PageSize { get; set; } = 50;
[StringLength(100)]
public string? Keyword { get; set; }
public byte? GroupId { get; set; }
[EnumDataType(typeof(UserGroup), ErrorMessage = "请选择有效的用户分组")]
public UserGroup? GroupId { get; set; }
/// <summary>查询子账号时可按父账号筛选;管理员可指定任意账号,普通主账号只能指定自己。</summary>
[StringLength(20)]
public string? ParentUserName { get; set; }
}
public sealed record UserPage<T>(long Total, IReadOnlyList<T> Items);
/// <summary>流水查询支持时间范围、操作类型、店铺及订单过滤。</summary>
public sealed class UserRecordSearchRequest : UserSearchRequest
public sealed class UserRecordSearchRequest : UserSearchRequest, IValidatableObject
{
/// <summary>目标账户;省略时查询本人。</summary>
[StringLength(20)]
public string? UserName { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
[StringLength(20)]
public string? Operation { get; set; }
[StringLength(50)]
public string? ShopId { get; set; }
public int? PaymentType { get; set; }
[StringLength(100)]
public string? OrderId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (StartTime > EndTime)
yield return new ValidationResult("开始时间不能晚于结束时间", [nameof(StartTime), nameof(EndTime)]);
}
}
/// <summary>单个账户 ID 参数的校验,供服务内部调用复用。</summary>
public sealed class UserIdRequest
{
[Range(1, int.MaxValue, ErrorMessage = "ID必须大于0")]
public int Id { get; set; }
}
@@ -5,19 +5,40 @@ namespace Store.Users.Contracts.Dtos;
public sealed class UserTopUpMessageRequest
{
/// <summary>目标账户;省略时为本人,指定时检查管理权限。</summary>
[StringLength(20)]
public string? UserName { get; set; }
[Required(ErrorMessage = "充值备注不能为空")] public string Message { get; set; } = "";
[Required(ErrorMessage = "充值备注不能为空"), StringLength(1000)] public string Message { get; set; } = "";
}
public sealed class UserTopUpResultRequest
{
[Range(1, long.MaxValue, ErrorMessage = "ID必须大于0")]
public long Id { get; set; }
[Required(ErrorMessage = "处理结果不能为空")] public string Result { get; set; } = "";
[Required(ErrorMessage = "处理结果不能为空"), StringLength(1000)] public string Result { get; set; } = "";
}
/// <summary>充值二维码;长度与 Base64 格式在服务中检查。</summary>
public sealed class UserTopUpQrcodeRequest
/// <summary>充值二维码;非空、长度与 Base64 格式由 DTO 检查。</summary>
public sealed class UserTopUpQrcodeRequest : IValidatableObject
{
[Required(ErrorMessage = "代理编码不能为空")] public string AgentCode { get; set; } = "";
[Required(ErrorMessage = "二维码不能为空")] public string QrcodeBase64 { get; set; } = "";
[Required(ErrorMessage = "代理编码不能为空"), StringLength(50)] public string AgentCode { get; set; } = "";
[Required(ErrorMessage = "二维码不能为空"), StringLength(2097152)] public string QrcodeBase64 { get; set; } = "";
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var content = QrcodeBase64;
if (content.StartsWith("data:image/", StringComparison.OrdinalIgnoreCase))
{
int comma = content.IndexOf(',');
if (comma < 0 || !content[..comma].EndsWith(";base64", StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult("二维码格式错误", [nameof(QrcodeBase64)]);
yield break;
}
content = content[(comma + 1)..];
}
bool valid;
try { valid = Convert.FromBase64String(content).Length > 0; }
catch (FormatException) { valid = false; }
if (!valid) yield return new ValidationResult("二维码必须为有效的Base64数据", [nameof(QrcodeBase64)]);
}
}
@@ -0,0 +1,12 @@
using System.ComponentModel;
namespace Store.Users.Contracts.Enums;
/// <summary>超管等级,数值与旧系统 sa_level 保持一致。</summary>
public enum UserAdminLevel : short
{
/// <summary>普通用户。</summary>
[Description("普通用户")] Normal = 0,
/// <summary>超级管理员。</summary>
[Description("超级管理员")] SuperAdmin = 1
}
@@ -0,0 +1,22 @@
using System.ComponentModel;
namespace Store.Users.Contracts.Enums;
/// <summary>用户分组,数值与旧系统 group_id 保持一致。</summary>
public enum UserGroup : byte
{
/// <summary>主号。</summary>
[Description("主号")] MainAccount = 3,
/// <summary>客服。</summary>
[Description("客服")] CustomerService = 4,
/// <summary>加盟(旧系统分组 5)。</summary>
[Description("加盟(5)")] Franchise5 = 5,
/// <summary>登录。</summary>
[Description("登录")] Login = 8,
/// <summary>采购。</summary>
[Description("采购")] Purchasing = 9,
/// <summary>加盟(旧系统分组 10);保留与分组 5 的区别。</summary>
[Description("加盟(10)")] Franchise10 = 10,
/// <summary>上货。</summary>
[Description("上货")] Listing = 11
}