增加Shop API
This commit is contained in:
1 parent
3769193696
commit
5d017740c6
83 files changed
+4451
-696
No files matched your search
@@ -75,8 +75,19 @@ internal sealed class ShopRenewCommandHandler(UnitOfWorkExecutionContext executi
|
||||
if (currentTime > expireBase)
|
||||
expireBase = currentTime;
|
||||
}
|
||||
int newExpireTime = CalculateNewExpireTime(expireBase, request.RenewDuration);
|
||||
|
||||
DateTime newExpireDate;
|
||||
try
|
||||
{
|
||||
newExpireDate = expireBase.AddMonths(request.RenewDuration);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
throw new AppException("续费时长超出允许范围");
|
||||
}
|
||||
long timestamp = DateHelper.GetUnixTimestampInSeconds(newExpireDate);
|
||||
if (timestamp > int.MaxValue || timestamp < 0)
|
||||
throw new AppException("续费时间超出系统支持范围");
|
||||
int newExpireTime = (int)timestamp;
|
||||
|
||||
int affected = await orm.Update<UserEntity>().Set(a => a.Points, newPoints)
|
||||
.Where(a => a.Id == user.Id && a.Points == user.Points)
|
||||
@@ -94,7 +105,7 @@ internal sealed class ShopRenewCommandHandler(UnitOfWorkExecutionContext executi
|
||||
UserName = userName,
|
||||
UserNick = user.NickName,
|
||||
DeltaPoints = pointsPerMonth,
|
||||
NewPoints = user.Points - (decimal)pointsPerMonth * (i + 1),
|
||||
NewPoints = user.Points - pointsPerMonth * (i + 1),
|
||||
ShopId = shop.ShopId,
|
||||
ShopAccount = shop.ShopAccount,
|
||||
Remark = remark,
|
||||
@@ -127,21 +138,4 @@ internal sealed class ShopRenewCommandHandler(UnitOfWorkExecutionContext executi
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new ShopRenewResponse { ShopId = shopId, UserPoints = newPoints, ExpireTime = newExpireTime };
|
||||
}
|
||||
|
||||
private static int CalculateNewExpireTime(DateTime expireBase, int duration)
|
||||
{
|
||||
DateTime newExpireDate;
|
||||
try
|
||||
{
|
||||
newExpireDate = expireBase.AddMonths(duration);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
throw new AppException("续费时长超出允许范围");
|
||||
}
|
||||
long timestamp = DateHelper.GetUnixTimestampInSeconds(newExpireDate);
|
||||
if (timestamp > int.MaxValue || timestamp < 0)
|
||||
throw new AppException("续费时间超出系统支持范围");
|
||||
return (int)timestamp;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using MAX.Framework.Conventions;
|
||||
|
||||
namespace Store.Users.Controllers;
|
||||
|
||||
/// <summary>用户模块接口;所有管理操作均以当前 JWT 对应的数据库账户进行授权。</summary>
|
||||
/// <summary>用户模块接口;所有管理操作均以当前登录身份对应的数据库账户进行授权。</summary>
|
||||
[ApiController]
|
||||
[DefaultHttpPost]
|
||||
[Authorize]
|
||||
@@ -22,16 +22,12 @@ public class UserController(UserManagementService users, UserFundsService funds,
|
||||
/// <summary>获取本人或授权范围内的用户信息,响应不包含密码。</summary>
|
||||
[EndpointSummary("获取用户信息")]
|
||||
public Task<UserInfo> GetUser(string? userName, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.GetUser(userName, cancellationToken);
|
||||
}
|
||||
=> users.GetUser(userName, cancellationToken);
|
||||
|
||||
/// <summary>管理员查询全部用户;普通主账号仅查询本人及直属子账号。</summary>
|
||||
[EndpointSummary("分页查询所有用户")]
|
||||
public Task<UserPage<UserInfo>> GetAllUser(UserSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Search(request, false, cancellationToken);
|
||||
}
|
||||
=> users.Search(request, false, cancellationToken);
|
||||
|
||||
/// <summary>管理员可查询所有账号的子账号,普通主账号只能查询自己的直属子账号。</summary>
|
||||
/// <param name="request">管理员可通过 ParentUserName 筛选指定父账号;普通主账号仅可指定自己。</param>
|
||||
@@ -39,37 +35,27 @@ public class UserController(UserManagementService users, UserFundsService funds,
|
||||
[EndpointSummary("分页查询子账号")]
|
||||
[EndpointDescription("管理员不传 ParentUserName 时查询全部子账号,传入时按指定父账号筛选;普通主账号只能查询自己的子账号。")]
|
||||
public Task<UserPage<UserInfo>> GetSubUser(UserSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Search(request, true, cancellationToken);
|
||||
}
|
||||
=> users.Search(request, true, cancellationToken);
|
||||
|
||||
/// <summary>管理员创建主账号。</summary>
|
||||
[EndpointSummary("创建主账号")]
|
||||
public Task<UserInfo> AddUser(UserCreateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Create(request, false, cancellationToken);
|
||||
}
|
||||
=> users.Create(request, false, cancellationToken);
|
||||
|
||||
/// <summary>管理员可为指定主账号创建子账号,普通主账号仅可为自己创建子账号。</summary>
|
||||
[EndpointSummary("创建直属子账号")]
|
||||
public Task<UserInfo> AddSubUser(UserCreateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Create(request, true, cancellationToken);
|
||||
}
|
||||
=> users.Create(request, true, cancellationToken);
|
||||
|
||||
/// <summary>管理员修改任意账户;普通主账号仅修改本人及直属子账号,用户名不可变更。</summary>
|
||||
[EndpointSummary("修改用户资料")]
|
||||
public Task<UserInfo> UpdateUser(UserUpdateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Update(request, false, cancellationToken);
|
||||
}
|
||||
=> users.Update(request, false, cancellationToken);
|
||||
|
||||
/// <summary>管理员修改任意子账号,普通主账号仅修改直属子账号。</summary>
|
||||
[EndpointSummary("修改直属子账号资料")]
|
||||
public Task<UserInfo> UpdateSubUser(UserUpdateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Update(request, true, cancellationToken);
|
||||
}
|
||||
=> users.Update(request, true, cancellationToken);
|
||||
|
||||
/// <summary>删除无资金和业务关联的账户。</summary>
|
||||
[EndpointSummary("删除用户")]
|
||||
@@ -90,37 +76,27 @@ public class UserController(UserManagementService users, UserFundsService funds,
|
||||
/// <summary>查询授权账户的积分流水。</summary>
|
||||
[EndpointSummary("查询用户积分流水")]
|
||||
public Task<UserPage<UserPointsRecordEntity>> GetUserPoints(UserRecordSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Points(request.UserName, request, cancellationToken);
|
||||
}
|
||||
=> users.Points(request.UserName, request, cancellationToken);
|
||||
|
||||
/// <summary>查询授权账户的余额流水。</summary>
|
||||
[EndpointSummary("查询用户余额流水")]
|
||||
public Task<UserPage<UserPaymentRecordEntity>> GetUserBalance(UserRecordSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.Balance(request.UserName, request, cancellationToken);
|
||||
}
|
||||
=> users.Balance(request.UserName, request, cancellationToken);
|
||||
|
||||
/// <summary>管理员充值积分,或主账号向直属子账号转移自己的积分。</summary>
|
||||
[EndpointSummary("充值或转移用户积分")]
|
||||
public Task<UserFundsResponse> UpdateUserPoints(UserFundsRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return funds.Change(request, true, cancellationToken);
|
||||
}
|
||||
=> funds.Change(request, true, cancellationToken);
|
||||
|
||||
/// <summary>管理员充值或扣减余额,自动记录审计流水。</summary>
|
||||
[EndpointSummary("充值或扣减用户余额")]
|
||||
public Task<UserFundsResponse> UpdateUserBalance(UserFundsRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return funds.Change(request, false, cancellationToken);
|
||||
}
|
||||
=> funds.Change(request, false, cancellationToken);
|
||||
|
||||
/// <summary>提交本人或管理范围内账户的充值备注。</summary>
|
||||
[EndpointSummary("提交充值备注")]
|
||||
public Task<long> AddUserTopUpMsg(UserTopUpMessageRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.AddTopUpMessage(request, cancellationToken);
|
||||
}
|
||||
=> users.AddTopUpMessage(request, cancellationToken);
|
||||
|
||||
/// <summary>管理员可处理所有充值备注,普通主账号仅处理本人及直属子账号备注,不自动充值。</summary>
|
||||
[EndpointSummary("处理充值备注")]
|
||||
@@ -133,9 +109,7 @@ public class UserController(UserManagementService users, UserFundsService funds,
|
||||
/// <summary>按管理员、主账号或子账号的授权范围分页查询充值备注。</summary>
|
||||
[EndpointSummary("分页查询充值备注")]
|
||||
public Task<UserPage<UserTopUpMessageEntity>> GetUserTopUpMsg(UserSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.TopUpMessages(request, cancellationToken);
|
||||
}
|
||||
=> users.TopUpMessages(request, cancellationToken);
|
||||
|
||||
/// <summary>管理员维护代理充值二维码。</summary>
|
||||
[EndpointSummary("维护代理充值二维码")]
|
||||
@@ -148,15 +122,10 @@ public class UserController(UserManagementService users, UserFundsService funds,
|
||||
/// <summary>获取代理充值二维码。</summary>
|
||||
[EndpointSummary("获取代理充值二维码")]
|
||||
public Task<string?> GetTopUpQrcode(string agentCode, CancellationToken cancellationToken)
|
||||
{
|
||||
return users.GetTopUpQrcode(agentCode, cancellationToken);
|
||||
}
|
||||
=> users.GetTopUpQrcode(agentCode, cancellationToken);
|
||||
|
||||
/// <summary>管理员或主账号为授权账户的商铺续费,扣除店铺所属账户积分并记录实际操作者。</summary>
|
||||
[EndpointSummary("使用积分为商铺续费")]
|
||||
public Task<ShopRenewResponse> ShopRenew(ShopRenewRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 操作者身份、目标账户归属和消费额度统一在命令管道中检查。
|
||||
return mediator.Send(new ShopRenewCommand(request), cancellationToken);
|
||||
}
|
||||
=> mediator.Send(new ShopRenewCommand(request), cancellationToken);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# 用户模块
|
||||
|
||||
参考旧版 `D:/Works/KJD_WebService/KJD_WebService/BLL/WXXD/WXUserManage.cs`,沿用用户、积分、余额、充值备注等旧表。接口采用 JSON DTO 和现有 JWT 身份,不接受旧版拼接参数或客户端提供的操作者身份。
|
||||
参考旧版 `D:/Works/KJD_WebService/KJD_WebService/BLL/WXXD/WXUserManage.cs`,沿用用户、积分、余额、充值备注等旧表。接口采用 JSON DTO 和现有 Bearer Token 身份,不接受旧版拼接参数或客户端提供的操作者身份。
|
||||
|
||||
## 文件与职责
|
||||
|
||||
@@ -54,4 +54,4 @@ dotnet run --project Store.API/Store.Api.csproj
|
||||
|
||||
连接现有 MySQL 前确认账户表用户名唯一、密码列可容纳 Identity 哈希、资金相关表支持事务。应用不自动创建或修改旧表。旧系统直接比较明文密码的登录接口不能识别新哈希,需随调用方迁移。
|
||||
|
||||
本次不修改或运行 Test 项目。编译和独立参数/权限边界验证不能替代真实 MySQL 的并发与表结构联调。旧文件中的 E57 令牌、销售助手 Cookie、淘宝买手不属于本模块范围。可以继续使用“代码审查”检查实现质量。
|
||||
使用 `dotnet test Tests/Store.Users.Tests/Store.Users.Tests.csproj` 验证参数、权限、密码、资金和 HTTP 接口。SQLite 测试不能替代真实 MySQL 的并发与表结构联调。旧文件中的 E57 令牌、销售助手 Cookie、淘宝买手不属于本模块范围。可以继续使用“代码审查”检查实现质量。
|
||||
@@ -5,13 +5,13 @@ namespace Store.Users.Repositories;
|
||||
|
||||
public interface IUserRepository : IBaseRepository<UserEntity>
|
||||
{
|
||||
Task<UserEntity> GetUser(string userName);
|
||||
Task<UserEntity?> GetUser(string userName);
|
||||
}
|
||||
|
||||
/// <summary>集中处理旧系统主子账号的有效计费配置和积分权限继承。</summary>
|
||||
internal class UserRepository(IFreeSql freeSql) : BaseRepository<UserEntity>(freeSql), IUserRepository, ITransientDependency
|
||||
{
|
||||
public async Task<UserEntity> GetUser(string userName)
|
||||
public async Task<UserEntity?> GetUser(string userName)
|
||||
{
|
||||
var user = await Where(u => u.UserName == userName).FirstAsync();
|
||||
if (user == null || user.IsMainUser) return user;
|
||||
|
||||
@@ -11,9 +11,6 @@ internal static class UserAccessPolicy
|
||||
public static bool Owns(UserEntity actor, UserEntity target) => actor.IsMainUser
|
||||
&& string.Equals(target.ParentUserName, actor.UserName, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool CanRead(UserEntity actor, UserEntity target) =>
|
||||
CanUseSelfService(actor, target);
|
||||
|
||||
/// <summary>子账号可查询本人、修改本人密码、提交充值备注和为本人店铺续费。</summary>
|
||||
public static bool CanUseSelfService(UserEntity actor, UserEntity target) =>
|
||||
actor.Id == target.Id || CanManage(actor, target);
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed partial class UserManagementService
|
||||
var actor = await Actor(orm, ct);
|
||||
var message = await orm.Select<UserTopUpMessageEntity>().Where(m => m.Id == request.Id).FirstAsync(ct)
|
||||
?? throw new AppException("充值备注不存在");
|
||||
if (!IsAdmin(actor))
|
||||
if (!UserAccessPolicy.IsAdmin(actor))
|
||||
{
|
||||
var target = await orm.Select<UserEntity>().Where(u => u.UserName == message.UserName).FirstAsync(ct);
|
||||
if (target == null || !UserAccessPolicy.CanManage(actor, target))
|
||||
@@ -46,7 +46,7 @@ public sealed partial class UserManagementService
|
||||
RequestValidator.Validate(request);
|
||||
var actor = await Actor(orm, ct);
|
||||
var select = orm.Select<UserTopUpMessageEntity>();
|
||||
if (!IsAdmin(actor))
|
||||
if (!UserAccessPolicy.IsAdmin(actor))
|
||||
{
|
||||
if (actor.IsMainUser)
|
||||
select = select.Where(m => m.UserName == actor.UserName || orm.Select<UserEntity>()
|
||||
@@ -63,7 +63,7 @@ public sealed partial class UserManagementService
|
||||
{
|
||||
RequestValidator.Validate(request);
|
||||
var actor = await Actor(orm, ct);
|
||||
if (!IsAdmin(actor)) throw new AppException("仅管理员可以维护充值二维码", 403);
|
||||
if (!UserAccessPolicy.IsAdmin(actor)) throw new AppException("仅管理员可以维护充值二维码", 403);
|
||||
await orm.InsertOrUpdate<UserTopUpQrcodeEntity>().SetSource(new UserTopUpQrcodeEntity
|
||||
{
|
||||
AgentCode = request.AgentCode.Trim(), QrcodeBase64 = request.QrcodeBase64
|
||||
|
||||
@@ -24,7 +24,6 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
?? throw new UnauthorizedAccessException("登录账户不存在");
|
||||
}
|
||||
|
||||
private static bool IsAdmin(UserEntity actor) => UserAccessPolicy.IsAdmin(actor);
|
||||
/// <summary>仅本人、直属主账号或超级管理员可以读取账户详情。</summary>
|
||||
public async Task<UserInfo> GetUser(string? userName, CancellationToken ct)
|
||||
{
|
||||
@@ -33,7 +32,7 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
RequestValidator.Validate(new UserNameRequest { UserName = name });
|
||||
var target = await orm.Select<UserEntity>().Where(u => u.UserName == name).FirstAsync(ct)
|
||||
?? throw new AppException("用户不存在");
|
||||
if (!UserAccessPolicy.CanRead(actor, target))
|
||||
if (!UserAccessPolicy.CanUseSelfService(actor, target))
|
||||
throw new AppException("无权访问该账户", 403);
|
||||
return await query.GetUser(target.UserName);
|
||||
}
|
||||
@@ -43,12 +42,12 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
{
|
||||
RequestValidator.Validate(request);
|
||||
var actor = await Actor(orm, ct);
|
||||
if (!IsAdmin(actor) && !actor.IsMainUser) throw new AppException("子账号不能管理账户列表", 403);
|
||||
if (!UserAccessPolicy.IsAdmin(actor) && !actor.IsMainUser) throw new AppException("子账号不能管理账户列表", 403);
|
||||
var select = orm.Select<UserEntity>();
|
||||
if (subUsersOnly)
|
||||
{
|
||||
var parentUserName = request.ParentUserName?.Trim();
|
||||
if (IsAdmin(actor))
|
||||
if (UserAccessPolicy.IsAdmin(actor))
|
||||
{
|
||||
// 管理员省略父账号时查询所有子账号,但不混入主账号。
|
||||
select = select.Where(u => u.ParentUserName != null && u.ParentUserName.Trim() != "");
|
||||
@@ -63,7 +62,7 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
select = select.Where(u => u.ParentUserName == actor.UserName && (u.SaLevel == null || u.SaLevel != UserAdminLevel.SuperAdmin));
|
||||
}
|
||||
}
|
||||
else if (!IsAdmin(actor))
|
||||
else if (!UserAccessPolicy.IsAdmin(actor))
|
||||
select = select.Where(u => u.Id == actor.Id || (u.ParentUserName == actor.UserName && (u.SaLevel == null || u.SaLevel != UserAdminLevel.SuperAdmin)));
|
||||
if (!subUsersOnly && !string.IsNullOrWhiteSpace(request.ParentUserName))
|
||||
{
|
||||
@@ -79,7 +78,8 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
}
|
||||
var total = await select.CountAsync(ct);
|
||||
var items = await select.OrderByDescending(u => u.Id).Page(request.Page, request.PageSize).ToListAsync(ct);
|
||||
return new UserPage<UserInfo>(total, items.Select(u => new UserMapper().ToInfo(u)).ToList());
|
||||
var mapper = new UserMapper();
|
||||
return new UserPage<UserInfo>(total, items.Select(mapper.ToInfo).ToList());
|
||||
}
|
||||
|
||||
/// <summary>创建账户并初始化余额。主账号仅由管理员创建,子账号的归属由服务端指定。</summary>
|
||||
@@ -94,12 +94,12 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
// 锁住主账号,使其创建子账号和删除操作串行化。
|
||||
actor = await db.Select<UserEntity>().Where(u => u.Id == actor.Id).ForUpdate().FirstAsync(ct)
|
||||
?? throw new AppException("操作者账户不存在");
|
||||
if (subUser ? !IsAdmin(actor) && !actor.IsMainUser : !IsAdmin(actor)) throw new AppException("无权创建该账户", 403);
|
||||
if (subUser ? !UserAccessPolicy.IsAdmin(actor) && !actor.IsMainUser : !UserAccessPolicy.IsAdmin(actor)) throw new AppException("无权创建该账户", 403);
|
||||
var parentName = "";
|
||||
if (subUser)
|
||||
{
|
||||
parentName = string.IsNullOrWhiteSpace(request.ParentUserName) ? actor.UserName : request.ParentUserName.Trim();
|
||||
if (!IsAdmin(actor) && !string.Equals(parentName, actor.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
if (!UserAccessPolicy.IsAdmin(actor) && !string.Equals(parentName, actor.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
throw new AppException("只能在自己的账户下创建子账号", 403);
|
||||
var parent = await db.Select<UserEntity>().Where(u => u.UserName == parentName).ForUpdate().FirstAsync(ct)
|
||||
?? throw new AppException("父账号不存在");
|
||||
@@ -186,7 +186,7 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
var name = string.IsNullOrWhiteSpace(request.UserName) ? actor.UserName : request.UserName.Trim();
|
||||
var target = await orm.Select<UserEntity>().Where(u => u.UserName == name).FirstAsync(ct)
|
||||
?? throw new AppException("用户不存在");
|
||||
if (target.Id == actor.Id && !IsAdmin(actor))
|
||||
if (target.Id == actor.Id && !UserAccessPolicy.IsAdmin(actor))
|
||||
{
|
||||
RequestValidator.Validate(new UserPasswordProofRequest { OldPassword = request.OldPassword! });
|
||||
if (!UserPassword.Verify(target, request.OldPassword!)) throw new AppException("原密码错误");
|
||||
@@ -242,18 +242,18 @@ public sealed partial class UserManagementService(IFreeSql orm, ICurrentUser cur
|
||||
if (request.GroupId.HasValue && (subUser ? request.GroupId == UserGroup.MainAccount : request.GroupId != UserGroup.MainAccount))
|
||||
throw new AppException("主账号必须使用主号分组,子账号不能使用主号分组");
|
||||
// 普通主账号只能授予自己已有的权限,不能通过创建子账号提升权限。
|
||||
if (!IsAdmin(actor) && request.Authority != null)
|
||||
if (!UserAccessPolicy.IsAdmin(actor) && request.Authority != null)
|
||||
{
|
||||
var allowed = Split(actor.Authority);
|
||||
if (Split(request.Authority).Any(a => !allowed.Contains(a))) throw new AppException("不能授予超出自身范围的权限", 403);
|
||||
}
|
||||
if (!IsAdmin(actor) && (request.OemId != null || request.ShopPointsPerMonth != null
|
||||
if (!UserAccessPolicy.IsAdmin(actor) && (request.OemId != null || request.ShopPointsPerMonth != null
|
||||
|| request.PostagePerOrder != null || request.RefundPostagePerOrder != null
|
||||
|| request.GiftOemAmountPerOrder != null || request.SyncLogisticsPerOrder != null))
|
||||
throw new AppException("仅管理员可以修改计费配置", 403);
|
||||
if (request.SaLevel.HasValue)
|
||||
{
|
||||
if (!IsAdmin(actor)) throw new AppException("无权设置管理员级别", 403);
|
||||
if (!UserAccessPolicy.IsAdmin(actor)) throw new AppException("无权设置管理员级别", 403);
|
||||
user.SaLevel = request.SaLevel;
|
||||
}
|
||||
user.NickName = request.NickName?.Trim() ?? user.NickName;
|
||||
|
||||
Reference in new issue
Block a user