feat: 框架代码同步 (#346)

[skip ci]

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
2025-09-30 15:16:58 +08:00
committed by GitHub
parent c8896896ed
commit 03d4b54e77
35 changed files with 220 additions and 100 deletions

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0.8 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0.9 AS base
WORKDIR /app WORKDIR /app
EXPOSE 8080 EXPOSE 8080
RUN apt update RUN apt update

View File

@@ -106,6 +106,7 @@ USDT
本部门和下一级部门数据 本部门和下一级部门数据
本部门和所有子部门数据 本部门和所有子部门数据
本部门数据 本部门数据
来自
框架 框架
比较数据库结构 比较数据库结构
注册 注册
@@ -153,6 +154,7 @@ USDT
请求日志导出 请求日志导出
调试 调试
身份证 身份证
转给
转账支出 转账支出
转账收入 转账收入
运行 运行

View File

@@ -144,10 +144,14 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
#endif #endif
#pragma warning disable S2326 #pragma warning disable S2326
// ReSharper disable UnusedTypeParameter
// ReSharper disable UnusedParameter.Local
private static Task<IActionResult> GetExportFileStreamAsync<TExport>( private static Task<IActionResult> GetExportFileStreamAsync<TExport>(
string fileName string fileName
, object list , object list
) { ) {
// ReSharper restore UnusedParameter.Local
// ReSharper restore UnusedTypeParameter
#pragma warning restore S2326 #pragma warning restore S2326
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -7,9 +7,6 @@ namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// </summary> /// </summary>
public record CreateDepositOrderReq : Sys_DepositOrder public record CreateDepositOrderReq : Sys_DepositOrder
{ {
/// <inheritdoc cref="Sys_DepositOrder.ActualPayAmount" />
public override long ActualPayAmount { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.DepositOrderStatus" /> /// <inheritdoc cref="Sys_DepositOrder.DepositOrderStatus" />
public override DepositOrderStatues DepositOrderStatus { get; init; } = DepositOrderStatues.WaitingForPayment; public override DepositOrderStatues DepositOrderStatus { get; init; } = DepositOrderStatues.WaitingForPayment;

View File

@@ -6,7 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CronExpressionDescriptor" Version="2.44.0"/> <PackageReference Include="CronExpressionDescriptor" Version="2.44.0"/>
<PackageReference Include="Cronos" Version="0.11.1"/> <PackageReference Include="Cronos" Version="0.11.1"/>
<PackageReference Include="ExcelMapper" Version="6.0.611"/> <PackageReference Include="ExcelMapper" Version="6.0.612"/>
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/> <PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -110,7 +110,7 @@ public static class Chars
public const string RGX_PAY_PASSWORD = """^\d{6}$"""; public const string RGX_PAY_PASSWORD = """^\d{6}$""";
public const string RGX_TELEPHONE = """^((\d{3,4}\-)|)\d{7,8}(|([-\u8f6c]{1}\d{1,5}))$"""; public const string RGX_TELEPHONE = """^((\d{3,4}\-)|)\d{7,8}(|([-\u8f6c]{1}\d{1,5}))$""";
public const string RGX_UP_AND_LOWER_NUMBER = """^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$"""; public const string RGX_UP_AND_LOWER_NUMBER = """^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$""";
public const string RGX_URL = """^https?://[\x00-\x1F\x21-\x7F]+$"""; public const string RGX_URL = """^https?://[\x21-\x7E]+$""";
public const string RGX_USERNAME = """^[\u4e00-\u9fa5a-zA-Z0-9_-]{2,16}$"""; public const string RGX_USERNAME = """^[\u4e00-\u9fa5a-zA-Z0-9_-]{2,16}$""";
public const string RGX_VERIFY_CODE = """^\d{4}$"""; public const string RGX_VERIFY_CODE = """^\d{4}$""";

View File

@@ -1,11 +1,10 @@
#pragma warning disable RCS1194
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
/// NetAdmin异常基类 /// NetAdmin异常基类
/// </summary> /// </summary>
#pragma warning disable RCS1194
public abstract class NetAdminException(string message, Exception innerException) : Exception(message, innerException) public abstract class NetAdminException(string message, Exception innerException) : Exception(message, innerException)
#pragma warning restore RCS1194
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="NetAdminException" /> class. /// Initializes a new instance of the <see cref="NetAdminException" /> class.

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,9 +7,5 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 外部接口调用未得到预期的结果 /// 外部接口调用未得到预期的结果
/// </remarks> /// </remarks>
#pragma warning disable RCS1194
public sealed class NetAdminExternalErrorException(string message, Exception innerException = null) public sealed class NetAdminExternalErrorException(string message, Exception innerException = null)
#pragma warning restore RCS1194 : NetAdminException(ErrorCodes.ExternalError, message, innerException);
: NetAdminException(ErrorCodes.ExternalError, message, innerException)
{
}

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,8 +7,4 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 并发执行时锁竞争失败 /// 并发执行时锁竞争失败
/// </remarks> /// </remarks>
#pragma warning disable RCS1194 public sealed class NetAdminGetLockerException(string message = null) : NetAdminInvalidOperationException(message);
public sealed class NetAdminGetLockerException(string message = null) : NetAdminInvalidOperationException(message)
{
}
#pragma warning restore RCS1194

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194, DesignedForInheritance
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,9 +7,5 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 参数格式错误、内容校验错误等 /// 参数格式错误、内容校验错误等
/// </remarks> /// </remarks>
#pragma warning disable DesignedForInheritance, RCS1194
public class NetAdminInvalidInputException(string message = null, Exception innerException = null) public class NetAdminInvalidInputException(string message = null, Exception innerException = null)
#pragma warning restore RCS1194, DesignedForInheritance : NetAdminException(ErrorCodes.InvalidInput, message, innerException);
: NetAdminException(ErrorCodes.InvalidInput, message, innerException)
{
}

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194, DesignedForInheritance
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,9 +7,5 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 非正常的业务流程或逻辑 /// 非正常的业务流程或逻辑
/// </remarks> /// </remarks>
#pragma warning disable DesignedForInheritance, RCS1194
public class NetAdminInvalidOperationException(string message, Exception innerException = null) public class NetAdminInvalidOperationException(string message, Exception innerException = null)
#pragma warning restore RCS1194, DesignedForInheritance : NetAdminException(ErrorCodes.InvalidOperation, message, innerException);
: NetAdminException(ErrorCodes.InvalidOperation, message, innerException)
{
}

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,7 +7,5 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 运行结果是非预期的,例如事务失败回滚 /// 运行结果是非预期的,例如事务失败回滚
/// </remarks> /// </remarks>
#pragma warning disable RCS1194
public sealed class NetAdminUnexpectedException(string message, Exception innerException = null) public sealed class NetAdminUnexpectedException(string message, Exception innerException = null)
#pragma warning restore RCS1194
: NetAdminException(ErrorCodes.Unexpected, message, innerException); : NetAdminException(ErrorCodes.Unexpected, message, innerException);

View File

@@ -1,3 +1,4 @@
#pragma warning disable RCS1194
namespace NetAdmin.Infrastructure.Exceptions; namespace NetAdmin.Infrastructure.Exceptions;
/// <summary> /// <summary>
@@ -6,10 +7,7 @@ namespace NetAdmin.Infrastructure.Exceptions;
/// <remarks> /// <remarks>
/// 手动调用模型验证方法抛出 /// 手动调用模型验证方法抛出
/// </remarks> /// </remarks>
#pragma warning disable RCS1194 public sealed class NetAdminValidateException(Dictionary<string, string[]> validateResults) : NetAdminInvalidInputException
public sealed class NetAdminValidateException(Dictionary<string, string[]> validateResults)
#pragma warning restore RCS1194
: NetAdminInvalidInputException
{ {
/// <summary> /// <summary>
/// 验证结果 /// 验证结果

View File

@@ -6,7 +6,7 @@
<PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.8" Label="refs"/> <PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.8" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.8" Label="refs"/> <PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.8" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.17" Label="refs"/> <PackageReference Include="Gurion" Version="1.2.17" Label="refs"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.8"/> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.9"/>
<PackageReference Include="Minio" Version="6.0.5"/> <PackageReference Include="Minio" Version="6.0.5"/>
<PackageReference Include="NSExt" Version="2.3.8"/> <PackageReference Include="NSExt" Version="2.3.8"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7"/> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7"/>

View File

@@ -19,8 +19,8 @@ public sealed class FreeSqlBuilder(DatabaseOptions databaseOptions)
) { ) {
var freeSql = new FreeSql.FreeSqlBuilder() var freeSql = new FreeSql.FreeSqlBuilder()
#if DBTYPE_SQLSERVER #if DBTYPE_SQLSERVER
.UseConnectionFactory(databaseOptions.DbType, () => new SqlConnection(databaseOptions.ConnStr)) .UseConnectionFactory(databaseOptions.DbType, () => new SqlConnection(databaseOptions.ConnStr))
.UseAdoConnectionPool(true) .UseAdoConnectionPool(true)
#else #else
.UseConnectionString(databaseOptions.DbType, databaseOptions.ConnStr) .UseConnectionString(databaseOptions.DbType, databaseOptions.ConnStr)
#endif #endif

View File

@@ -1,3 +1,4 @@
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.WalletTrade; using NetAdmin.Domain.Dto.Sys.WalletTrade;
namespace NetAdmin.SysComponent.Application.Modules.Sys; namespace NetAdmin.SysComponent.Application.Modules.Sys;
@@ -11,6 +12,16 @@ public interface IWalletTradeModule : ICrudModule<CreateWalletTradeReq, QueryWal
, DelReq // 删除类型 , DelReq // 删除类型
> >
{ {
/// <summary>
/// 获取自助充值条形图数据
/// </summary>
Task<IEnumerable<GetBarChartRsp>> GetSelfDepositBarChartAsync(QueryReq<QueryWalletTradeReq> req);
/// <summary>
/// 消费总数计数
/// </summary>
Task<long> TotalAmountAsync(QueryReq<QueryWalletTradeReq> req);
/// <summary> /// <summary>
/// 从他人账户转账给自己 /// 从他人账户转账给自己
/// </summary> /// </summary>

View File

@@ -10,6 +10,6 @@ public interface IUserInviteService : IService, IUserInviteModule
/// </summary> /// </summary>
Task<List<long>> GetAssociatedUserIdAsync( Task<List<long>> GetAssociatedUserIdAsync(
long userId long userId
, bool up = true , bool up = true
); );
} }

View File

@@ -94,8 +94,7 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
var toPointRate = req.PaymentMode switch var toPointRate = req.PaymentMode switch
{ {
PaymentModes.USDT => config.UsdToPointRate PaymentModes.USDT => config.UsdToPointRate
, PaymentModes.Alipay => config.CnyToPointRate , PaymentModes.Alipay or PaymentModes.WeChat => config.CnyToPointRate
, PaymentModes.WeChat => config.CnyToPointRate
, _ => throw new ArgumentOutOfRangeException(nameof(req)) , _ => throw new ArgumentOutOfRangeException(nameof(req))
}; };
@@ -221,8 +220,14 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
/// <inheritdoc /> /// <inheritdoc />
public async Task<int> ReceivedConfirmationAsync(JobReq req) { public async Task<int> ReceivedConfirmationAsync(JobReq req) {
req.ThrowIfInvalid(); req.ThrowIfInvalid();
// 初始化返回值
var ret = 0; var ret = 0;
// 获取最新配置信息
var config = await S<IConfigService>().GetLatestConfigAsync().ConfigureAwait(false); var config = await S<IConfigService>().GetLatestConfigAsync().ConfigureAwait(false);
// 查询所有状态为“待确认”的充值订单
var waitConfirmList = (await QueryAsync( var waitConfirmList = (await QueryAsync(
new QueryReq<QueryDepositOrderReq> new QueryReq<QueryDepositOrderReq>
{ {
@@ -239,59 +244,36 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
} }
) )
.ConfigureAwait(false)).ToList(); .ConfigureAwait(false)).ToList();
// 调用 TronScan API 获取指定地址的代币转账记录
var apiResult = await S<ITronScanClient>() var apiResult = await S<ITronScanClient>()
.TransfersAsync(S<IOptions<TronScanOptions>>().Value.Token, req.Count!.Value, config.Trc20ReceiptAddress) .TransfersAsync(S<IOptions<TronScanOptions>>().Value.Token, req.Count!.Value, config.Trc20ReceiptAddress)
.ConfigureAwait(false); .ConfigureAwait(false);
// 如果没有获取到交易数据,则直接返回 0
if (apiResult.TokenTransfers == null) {
return ret;
}
// 遍历所有已确认且成功的 USDT 交易
foreach (var apiItem in apiResult.TokenTransfers.Where(x => foreach (var apiItem in apiResult.TokenTransfers.Where(x =>
x.TokenInfo.TokenAbbr == "USDT" && x.Confirmed && x.ContractRet == "SUCCESS" && x.FinalResult == "SUCCESS" x.TokenInfo.TokenAbbr == "USDT" && x.Confirmed && x.ContractRet == "SUCCESS" && x.FinalResult == "SUCCESS"
)) { )) {
// 匹配本地订单中金额一致、创建时间早于交易时间的订单
var order = waitConfirmList.SingleOrDefault(x => x.ActualPayAmount == apiItem.Quant.Int64() / 1000); var order = waitConfirmList.SingleOrDefault(x => x.ActualPayAmount == apiItem.Quant.Int64() / 1000);
if (order == null || order.CreatedTime > apiItem.BlockTs.Time()) { if (order == null || order.CreatedTime > apiItem.BlockTs.Time()) {
continue; continue;
} }
try { try {
// 使用工作单元模式原子性地更新订单状态并创建钱包交易记录
await S<UnitOfWorkManager>() await S<UnitOfWorkManager>()
.AtomicOperateAsync(async () => .AtomicOperateAsync(async () => await OrderSuccessAsync(order, apiItem).ConfigureAwait(false))
{
var updated = await UpdateAsync(
new Sys_DepositOrder
{
DepositOrderStatus = DepositOrderStatues.Succeeded
, Version = order.Version
, FinishTimestamp = DateTime.Now.TimeUnixUtcMs()
, PaidAccount = apiItem.FromAddress
, PaidTime = apiItem.BlockTs.Time()
, PaymentFinger = apiItem.TransactionId
}, [nameof(Sys_DepositOrder.DepositOrderStatus), nameof(Sys_DepositOrder.FinishTimestamp), nameof(Sys_DepositOrder.PaidAccount), nameof(Sys_DepositOrder.PaidTime), nameof(Sys_DepositOrder.PaymentFinger)]
, null, a => a.Id == order.Id && a.DepositOrderStatus == DepositOrderStatues.PaymentConfirming
)
.ConfigureAwait(false);
if (updated != 1) {
throw new NetAdminUnexpectedException(Ln.);
}
_ = await S<IWalletTradeService>()
.CreateAsync(
new CreateWalletTradeReq
{
Amount = order.DepositPoint
, BusinessOrderNumber = order.Id
, TradeDirection = TradeDirections.Income
, TradeType = TradeTypes.SelfDeposit
, OwnerId = order.OwnerId
, OwnerDeptId = order.OwnerDeptId
}
)
.ConfigureAwait(false)
?? throw new NetAdminUnexpectedException(Ln.);
}
)
.ConfigureAwait(false); .ConfigureAwait(false);
ret++; ret++;
} }
catch { catch {
// ignore // 忽略异常,防止中断整个流程
} }
} }
@@ -304,6 +286,53 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
return QueryInternal(req with { Order = Orders.None }).WithNoLockNoWait().SumAsync(req.GetSumExp<Sys_DepositOrder>()); return QueryInternal(req with { Order = Orders.None }).WithNoLockNoWait().SumAsync(req.GetSumExp<Sys_DepositOrder>());
} }
/// <summary>
/// 将指定订单标记为成功,并创建对应的钱包交易记录。
/// </summary>
/// <param name="order">本地待处理的订单信息。</param>
/// <param name="apiItem">来自链上的交易信息。</param>
/// <exception cref="NetAdminUnexpectedException">当发生未预期的网络管理异常时抛出此异常</exception>
private async Task OrderSuccessAsync(
QueryDepositOrderRsp order
, TokenTransferInfo apiItem
) {
// 更新订单状态为成功,并填充支付相关信息
var updated = await UpdateAsync(
new Sys_DepositOrder
{
DepositOrderStatus = DepositOrderStatues.Succeeded
, Version = order.Version
, FinishTimestamp = DateTime.Now.TimeUnixUtcMs()
, PaidAccount = apiItem.FromAddress
, PaidTime = apiItem.BlockTs.Time()
, PaymentFinger = apiItem.TransactionId
}, [nameof(Sys_DepositOrder.DepositOrderStatus), nameof(Sys_DepositOrder.FinishTimestamp), nameof(Sys_DepositOrder.PaidAccount), nameof(Sys_DepositOrder.PaidTime), nameof(Sys_DepositOrder.PaymentFinger)]
, null, a => a.Id == order.Id && a.DepositOrderStatus == DepositOrderStatues.PaymentConfirming
)
.ConfigureAwait(false);
// 如果更新失败影响行数不为1抛出异常
if (updated != 1) {
throw new NetAdminUnexpectedException(Ln.);
}
// 创建钱包交易记录
_ = await S<IWalletTradeService>()
.CreateAsync(
new CreateWalletTradeReq
{
Amount = order.DepositPoint
, BusinessOrderNumber = order.Id
, TradeDirection = TradeDirections.Income
, TradeType = TradeTypes.SelfDeposit
, OwnerId = order.OwnerId
, OwnerDeptId = order.OwnerDeptId
}
)
.ConfigureAwait(false)
?? throw new NetAdminUnexpectedException(Ln.);
}
private ISelect<Sys_DepositOrder> QueryInternal(QueryReq<QueryDepositOrderReq> req) { private ISelect<Sys_DepositOrder> QueryInternal(QueryReq<QueryDepositOrderReq> req) {
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereIf(req.Filter?.Id > 0, a => a.Id == req.Filter.Id); var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereIf(req.Filter?.Id > 0, a => a.Id == req.Filter.Id);

View File

@@ -20,6 +20,7 @@ public sealed class UserWalletService(BasicRepository<Sys_UserWallet, long> rpo)
, Owner = new Sys_User , Owner = new Sys_User
{ {
Id = a.Owner.Id Id = a.Owner.Id
, DeptId = a.Owner.DeptId
, UserName = a.Owner.UserName , UserName = a.Owner.UserName
, Avatar = a.Owner.Avatar , Avatar = a.Owner.Avatar
, Roles = a.Owner.Roles , Roles = a.Owner.Roles

View File

@@ -2,6 +2,7 @@ using System.Net.Http.Headers;
using Ganss.Excel; using Ganss.Excel;
using NetAdmin.Application.Extensions; using NetAdmin.Application.Extensions;
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.User; using NetAdmin.Domain.Dto.Sys.User;
using NetAdmin.Domain.Dto.Sys.UserInvite; using NetAdmin.Domain.Dto.Sys.UserInvite;
using NetAdmin.Domain.Dto.Sys.UserWallet; using NetAdmin.Domain.Dto.Sys.UserWallet;
@@ -99,7 +100,7 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
.ConfigureAwait(false) .ConfigureAwait(false)
?? throw new NetAdminUnexpectedException(Ln.); ?? throw new NetAdminUnexpectedException(Ln.);
var ret = await Rpo var ret = await Rpo
.InsertAsync(req with { BalanceBefore = wallet.AvailableBalance + wallet.FrozenBalance, OwnerDeptId = wallet.OwnerDeptId }) .InsertAsync(req with { BalanceBefore = wallet.AvailableBalance + wallet.FrozenBalance, OwnerDeptId = wallet.Owner.DeptId })
.ConfigureAwait(false); .ConfigureAwait(false);
return ret.Adapt<QueryWalletTradeRsp>(); return ret.Adapt<QueryWalletTradeRsp>();
} }
@@ -172,6 +173,27 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
return ret.Adapt<QueryWalletTradeRsp>(); return ret.Adapt<QueryWalletTradeRsp>();
} }
/// <inheritdoc />
public async Task<IEnumerable<GetBarChartRsp>> GetSelfDepositBarChartAsync(QueryReq<QueryWalletTradeReq> req) {
req.ThrowIfInvalid();
var df = new DynamicFilterInfo
{
Field = nameof(Sys_WalletTrade.TradeType), Operator = DynamicFilterOperators.Eq, Value = TradeTypes.SelfDeposit
};
var newdf = req.DynamicFilter.Add(df);
var ret = await QueryInternal(req with { Order = Orders.None, DynamicFilter = newdf })
.WithNoLockNoWait()
.GroupBy(a => new { a.CreatedTime.Year, a.CreatedTime.Month, a.CreatedTime.Day, a.CreatedTime.Hour })
.ToListAsync(a => new GetBarChartRsp
{
Timestamp = new DateTime(a.Key.Year, a.Key.Month, a.Key.Day, a.Key.Hour, 0, 0, DateTimeKind.Unspecified)
, Value = (int)Math.Floor(a.Sum(a.Value.Amount) / 100)
}
)
.ConfigureAwait(false);
return ret.OrderBy(x => x.Timestamp);
}
/// <inheritdoc /> /// <inheritdoc />
public async Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req) { public async Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req) {
req.ThrowIfInvalid(); req.ThrowIfInvalid();
@@ -208,6 +230,13 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
.ConfigureAwait(false); .ConfigureAwait(false);
} }
/// <inheritdoc />
public Task<long> TotalAmountAsync(QueryReq<QueryWalletTradeReq> req) {
req.ThrowIfInvalid();
var total = QueryInternal(req with { Order = Orders.None }).WithNoLockNoWait().Sum(a => a.Amount) / 100;
return Task.FromResult((long)Math.Abs(Math.Floor(total)));
}
/// <inheritdoc /> /// <inheritdoc />
public async Task<int> TransferFromAnotherAccountAsync(TransferReq req) { public async Task<int> TransferFromAnotherAccountAsync(TransferReq req) {
// 检查源账户是不是自己的下级 // 检查源账户是不是自己的下级
@@ -230,7 +259,7 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
OwnerDeptId = fromUser.DeptId OwnerDeptId = fromUser.DeptId
, Amount = -req.Amount , Amount = -req.Amount
, OwnerId = fromUser.Id , OwnerId = fromUser.Id
, Summary = req.Summary , Summary = $"{req.Summary} ({Ln.转给}: {UserToken.UserName}/{UserToken.Id})"
, TradeDirection = TradeDirections.Expense , TradeDirection = TradeDirections.Expense
, TradeType = TradeTypes.TransferExpense , TradeType = TradeTypes.TransferExpense
} }
@@ -242,7 +271,7 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
new CreateWalletTradeReq new CreateWalletTradeReq
{ {
Amount = req.Amount Amount = req.Amount
, Summary = req.Summary , Summary = $"{req.Summary} ({Ln.来自}: {fromUser.UserName}/{fromUser.Id})"
, TradeDirection = TradeDirections.Income , TradeDirection = TradeDirections.Income
, TradeType = TradeTypes.TransferIncome , TradeType = TradeTypes.TransferIncome
, OwnerId = UserToken.Id , OwnerId = UserToken.Id
@@ -267,7 +296,7 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
new CreateWalletTradeReq new CreateWalletTradeReq
{ {
Amount = -req.Amount Amount = -req.Amount
, Summary = req.Summary , Summary = $"{req.Summary} ({Ln.转给}: {toUser.UserName}/{toUser.Id})"
, TradeDirection = TradeDirections.Expense , TradeDirection = TradeDirections.Expense
, TradeType = TradeTypes.TransferExpense , TradeType = TradeTypes.TransferExpense
, OwnerId = UserToken.Id , OwnerId = UserToken.Id
@@ -283,7 +312,7 @@ public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rp
OwnerDeptId = toUser.DeptId OwnerDeptId = toUser.DeptId
, Amount = req.Amount , Amount = req.Amount
, OwnerId = toUser.Id , OwnerId = toUser.Id
, Summary = req.Summary , Summary = $"{req.Summary} ({Ln.来自}: {UserToken.UserName}/{UserToken.Id})"
, TradeDirection = TradeDirections.Income , TradeDirection = TradeDirections.Income
, TradeType = TradeTypes.TransferIncome , TradeType = TradeTypes.TransferIncome
} }

View File

@@ -15,7 +15,7 @@ public sealed class RequestLogCache(IDistributedCache cache, IRequestLogService
/// <inheritdoc /> /// <inheritdoc />
#if !DEBUG #if !DEBUG
public async Task<long> CountAsync(QueryReq<QueryRequestLogReq> req) public async Task<long> CountAsync(QueryReq<QueryRequestLogReq> req)
#else #else
public Task<long> CountAsync(QueryReq<QueryRequestLogReq> req) public Task<long> CountAsync(QueryReq<QueryRequestLogReq> req)
#endif #endif
{ {

View File

@@ -1,3 +1,4 @@
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.WalletTrade; using NetAdmin.Domain.Dto.Sys.WalletTrade;
namespace NetAdmin.SysComponent.Cache.Sys; namespace NetAdmin.SysComponent.Cache.Sys;
@@ -46,6 +47,11 @@ public sealed class WalletTradeCache(IDistributedCache cache, IWalletTradeServic
return Service.GetAsync(req); return Service.GetAsync(req);
} }
/// <inheritdoc />
public Task<IEnumerable<GetBarChartRsp>> GetSelfDepositBarChartAsync(QueryReq<QueryWalletTradeReq> req) {
return Service.GetSelfDepositBarChartAsync(req);
}
/// <inheritdoc /> /// <inheritdoc />
public Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req) { public Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req) {
return Service.PagedQueryAsync(req); return Service.PagedQueryAsync(req);
@@ -61,6 +67,11 @@ public sealed class WalletTradeCache(IDistributedCache cache, IWalletTradeServic
return Service.SumAsync(req); return Service.SumAsync(req);
} }
/// <inheritdoc />
public Task<long> TotalAmountAsync(QueryReq<QueryWalletTradeReq> req) {
return Service.TotalAmountAsync(req);
}
/// <inheritdoc /> /// <inheritdoc />
public Task<int> TransferFromAnotherAccountAsync(TransferReq req) { public Task<int> TransferFromAnotherAccountAsync(TransferReq req) {
return Service.TransferFromAnotherAccountAsync(req); return Service.TransferFromAnotherAccountAsync(req);

View File

@@ -1,3 +1,4 @@
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.WalletTrade; using NetAdmin.Domain.Dto.Sys.WalletTrade;
namespace NetAdmin.SysComponent.Host.Controllers.Sys; namespace NetAdmin.SysComponent.Host.Controllers.Sys;
@@ -70,6 +71,13 @@ public sealed class WalletTradeController(IWalletTradeCache cache) : ControllerB
return Cache.GetAsync(req); return Cache.GetAsync(req);
} }
/// <summary>
/// 获取自助充值条形图数据
/// </summary>
public Task<IEnumerable<GetBarChartRsp>> GetSelfDepositBarChartAsync(QueryReq<QueryWalletTradeReq> req) {
return Cache.GetSelfDepositBarChartAsync(req);
}
/// <summary> /// <summary>
/// 分页查询钱包交易 /// 分页查询钱包交易
/// </summary> /// </summary>
@@ -92,6 +100,13 @@ public sealed class WalletTradeController(IWalletTradeCache cache) : ControllerB
return Cache.SumAsync(req); return Cache.SumAsync(req);
} }
/// <summary>
/// 消费总数计数
/// </summary>
public Task<long> TotalAmountAsync(QueryReq<QueryWalletTradeReq> req) {
return Cache.TotalAmountAsync(req);
}
/// <summary> /// <summary>
/// 从他人账户转账给自己 /// 从他人账户转账给自己
/// </summary> /// </summary>

View File

@@ -3,7 +3,7 @@
<ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/> <ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.8"/> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.9"/>
<PackageReference Include="xunit" Version="2.9.3"/> <PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -10,13 +10,13 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.2", "@element-plus/icons-vue": "2.3.2",
"ace-builds": "1.43.2", "ace-builds": "1.43.3",
"aieditor": "1.4.0", "aieditor": "1.4.1",
"axios": "1.11.0", "axios": "1.12.2",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"dayjs": "1.11.14", "dayjs": "1.11.18",
"echarts": "6.0.0", "echarts": "6.0.0",
"element-plus": "2.11.1", "element-plus": "2.11.4",
"json-bigint": "1.0.0", "json-bigint": "1.0.0",
"markdown-it": "14.1.0", "markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0", "markdown-it-emoji": "3.0.0",
@@ -24,8 +24,8 @@
"qrcode-svg": "1.1.0", "qrcode-svg": "1.1.0",
"sortablejs": "1.15.6", "sortablejs": "1.15.6",
"vkbeautify": "0.99.3", "vkbeautify": "0.99.3",
"vue": "3.5.20", "vue": "3.5.22",
"vue-i18n": "11.1.11", "vue-i18n": "11.1.12",
"vue-router": "4.5.1", "vue-router": "4.5.1",
"vue3-ace-editor": "2.2.4", "vue3-ace-editor": "2.2.4",
"vue3-json-viewer": "2.4.1", "vue3-json-viewer": "2.4.1",
@@ -36,9 +36,9 @@
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.91.0", "sass": "1.93.2",
"terser": "5.43.1", "terser": "5.44.0",
"vite": "7.1.3" "vite": "7.1.7"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@@ -0,0 +1,9 @@
<template>
<svg height="256" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path d="M436.8 212.8m-22.4 0a22.4 22.4 0 1 0 44.8 0 22.4 22.4 0 1 0-44.8 0Z" />
<path d="M572.8 212.8m-22.4 0a22.4 22.4 0 1 0 44.8 0 22.4 22.4 0 1 0-44.8 0Z" />
<path
d="M872 304c-11.2 0-22.4 1.6-32 4.8l-8-25.6c-19.2-57.6-51.2-107.2-94.4-145.6l57.6-62.4c14.4-16 14.4-41.6-1.6-56-16-14.4-41.6-14.4-56 1.6L672 91.2c-46.4-24-99.2-38.4-153.6-38.4-57.6 0-112 14.4-160 41.6L288 20.8c-14.4-16-40-17.6-56-1.6-16 14.4-17.6 40-1.6 56l62.4 67.2c-40 38.4-72 86.4-89.6 142.4l-9.6 28.8c-12.8-4.8-27.2-8-41.6-8C94.4 304 48 350.4 48 408v161.6C48 625.6 94.4 672 152 672h1.6c27.2 0 52.8-11.2 72-28.8v110.4c0 20.8 12.8 38.4 32 43.2v107.2c0 57.6 46.4 104 104 104h1.6c57.6 0 104-46.4 104-104V800h96v104c0 57.6 46.4 104 104 104h1.6c57.6 0 104-46.4 104-104V800h1.6c25.6 0 46.4-20.8 46.4-46.4V656c16 9.6 35.2 16 56 16h1.6c57.6 0 104-46.4 104-104v-160c-6.4-57.6-52.8-104-110.4-104z m-140.8-48H302.4c43.2-75.2 124.8-121.6 214.4-121.6 89.6 0 169.6 46.4 214.4 121.6zM176 568c0 12.8-11.2 24-24 24S128 580.8 128 568v-160c0-12.8 11.2-24 24-24s24 11.2 24 24v160z m208 336c0 12.8-11.2 24-24 24h-1.6c-12.8 0-24-11.2-24-24V800h48v104zM464 720H304V336h432v384H464z m224 184c0 12.8-11.2 24-24 24h-1.6c-12.8 0-24-11.2-24-24V800h48v104z m208-336c0 12.8-11.2 24-24 24h-1.6c-12.8 0-24-11.2-24-24v-160c0-12.8 11.2-24 24-24s24 11.2 24 24v160z" />
</svg>
</template>
<script lang="ts" setup></script>

View File

@@ -0,0 +1,6 @@
<template>
<svg height="256" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path
d="M704 106.666667a210.346667 210.346667 0 0 1-59.733333 132.266666A209.706667 209.706667 0 0 1 512 298.666667a204.8 204.8 0 0 1 192-192z m-45.226667 277.333333a131.626667 131.626667 0 0 0-69.333333 20.053333 138.88 138.88 0 0 1-146.56 0 130.986667 130.986667 0 0 0-69.333333-20.053333h-7.04c-69.12 3.626667-131.84 85.333333-131.84 170.666667 0 90.026667 30.08 160 100.693333 234.666666 40.96 42.666667 46.506667 42.666667 58.453333 42.666667a50.133333 50.133333 0 0 0 18.56-3.413333 290.773333 290.773333 0 0 1 207.786667 0 48.426667 48.426667 0 0 0 18.346667 3.413333c11.946667 0 17.493333 0 58.88-42.666667A395.946667 395.946667 0 0 0 768 689.493333 215.466667 215.466667 0 0 1 675.413333 512a217.6 217.6 0 0 1 33.493334-115.626667 106.666667 106.666667 0 0 0-42.666667-12.16h-7.253333m0-85.333333h11.733333A214.613333 214.613333 0 0 1 832 395.733333 128 128 0 0 0 760.746667 512 125.653333 125.653333 0 0 0 874.666667 640a422.613333 422.613333 0 0 1-115.626667 208.213333c-42.666667 44.586667-69.76 69.546667-120.533333 69.546667a139.093333 139.093333 0 0 1-49.066667-8.96 203.52 203.52 0 0 0-146.56 0 138.24 138.24 0 0 1-49.066667 8.96c-50.56 0-77.866667-24.96-120.32-69.546667C196.906667 768 149.333333 677.546667 149.333333 554.666667s92.373333-249.386667 213.333334-256h11.52a215.68 215.68 0 0 1 114.56 33.066666 53.76 53.76 0 0 0 56.106666 0A215.68 215.68 0 0 1 658.773333 298.666667z" />
</svg>
</template>

View File

@@ -80,4 +80,6 @@ export { default as template } from './template.vue'
export { default as order } from './order.vue' export { default as order } from './order.vue'
export { default as income } from './income.vue' export { default as income } from './income.vue'
export { default as transfer } from './transfer.vue' export { default as transfer } from './transfer.vue'
export { default as deposit } from './deposit.vue' export { default as deposit } from './deposit.vue'
export { default as apple } from './apple.vue'
export { default as android } from './android.vue'

View File

@@ -3,6 +3,7 @@
v-model="visible" v-model="visible"
:full-screen="dialogFullScreen.includes(mode)" :full-screen="dialogFullScreen.includes(mode)"
:title="titleMap[mode]" :title="titleMap[mode]"
:width="dialogWidth"
@closed="$emit(`closed`)" @closed="$emit(`closed`)"
destroy-on-close destroy-on-close
ref="dialog"> ref="dialog">
@@ -25,7 +26,7 @@
:prop="i"> :prop="i">
<el-date-picker <el-date-picker
v-bind="item.detail?.props" v-bind="item.detail?.props"
v-if="i.endsWith(`Time`)" v-if="i.endsWith(`Time`) && !item.detail?.is"
v-model="form[i]" v-model="form[i]"
:disabled="item.disabled?.includes(mode)" :disabled="item.disabled?.includes(mode)"
type="datetime" type="datetime"
@@ -76,6 +77,7 @@
v-model:value3="form[item.detail.vModel[2]]" v-model:value3="form[item.detail.vModel[2]]"
v-model:value4="form[item.detail.vModel[3]]" v-model:value4="form[item.detail.vModel[3]]"
v-model:value5="form[item.detail.vModel[4]]" v-model:value5="form[item.detail.vModel[4]]"
v-model:value6="form[item.detail.vModel[5]]"
:disabled="item.disabled?.includes(mode)" :disabled="item.disabled?.includes(mode)"
:is="item.detail.is" /> :is="item.detail.is" />
</template> </template>
@@ -229,6 +231,7 @@ export default {
dialogFullScreen: { type: Array }, dialogFullScreen: { type: Array },
tabs: { type: Array }, tabs: { type: Array },
formInline: { type: Boolean }, formInline: { type: Boolean },
dialogWidth: { type: String, default: '50%' },
formLabelWidth: { type: Number, default: 12 }, formLabelWidth: { type: Number, default: 12 },
}, },
} }

View File

@@ -598,6 +598,7 @@ export default {
showSelection: { type: Boolean, default: true }, showSelection: { type: Boolean, default: true },
showSearchMine: { type: Boolean, default: false }, showSearchMine: { type: Boolean, default: false },
totalCountLabel: { type: String, default: `总数` }, totalCountLabel: { type: String, default: `总数` },
dialogWidth: { type: String, default: '50%' },
}, },
watch: {}, watch: {},
} }

File diff suppressed because one or more lines are too long

View File

@@ -493,6 +493,14 @@ textarea {
margin-top: 1rem; margin-top: 1rem;
} }
.ml-2 {
margin-left: 0.5rem;
}
.ml-4 {
margin-left: 1rem;
}
.mr-2 { .mr-2 {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
@@ -504,9 +512,11 @@ textarea {
.mt-8 { .mt-8 {
margin-top: 2rem; margin-top: 2rem;
} }
.mb-4 { .mb-4 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.mb-8 { .mb-8 {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
@@ -646,7 +656,13 @@ textarea {
margin-right: 0 !important; margin-right: 0 !important;
width: 50%; width: 50%;
} }
.el-form-item-w33p { .el-form-item-w33p {
margin-right: 0 !important; margin-right: 0 !important;
width: 33%; width: 33%;
}
.el-form-item-w25p {
margin-right: 0 !important;
width: 25%;
} }

View File

@@ -3,7 +3,7 @@
<div class="widgets-content"> <div class="widgets-content">
<div class="widgets" ref="widgets"> <div class="widgets" ref="widgets">
<div class="widgets-wrapper"> <div class="widgets-wrapper">
<div v-if="nowCompsList.length <= 0" class="no-widgets"> <div v-if="nowCompsList?.length <= 0" class="no-widgets">
<el-empty :description="$t('没有部件啦')" :image-size="280" /> <el-empty :description="$t('没有部件啦')" :image-size="280" />
</div> </div>
<el-row :gutter="15"> <el-row :gutter="15">
@@ -181,7 +181,7 @@ export default {
return this.allCompsList.filter((item) => !item.disabled && !myGrid?.includes(item.key)) return this.allCompsList.filter((item) => !item.disabled && !myGrid?.includes(item.key))
}, },
nowCompsList() { nowCompsList() {
return this.grid.compsList.reduce(function (a, b) { return this.grid.compsList?.reduce(function (a, b) {
return a.concat(b) return a.concat(b)
}) })
}, },

View File

@@ -31,7 +31,7 @@
<script> <script>
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
const scPasswordStrength = defineAsyncComponent(() => import('@/components/sc-password-strength')) const scPasswordStrength = defineAsyncComponent(() => import('@/components/sc-password-strength'))
const naFormPassword = defineAsyncComponent(() => import('@/config/na-form-password')) import naFormPassword from '@/config/na-form-password'
export default { export default {
components: { components: {

View File

@@ -65,7 +65,7 @@
isSwitch: { onChange: `setSelfDepositAllowed` }, isSwitch: { onChange: `setSelfDepositAllowed` },
}, },
'user.enabled': { 'user.enabled': {
label: $t(`启用`), label: $t(`账号已启用`),
width: 120, width: 120,
align: `center`, align: `center`,
show: [`list`], show: [`list`],