refactor(payment): Controller @RequestParam散参数治理, Dashboard查询对象化(TradeRangeQuery)合并Service双套重载, POST多参数改@RequestBody, EasyPay删除useCancel, 清理15处裸注解

This commit is contained in:
daxpay
2026-08-01 13:11:55 +08:00
parent 84c6f939b9
commit ec0e6e01bc
32 changed files with 345 additions and 547 deletions

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.payment.admin.result.dashboard.AdminDashboardHeaderCountResult;
import cn.daxpay.open.payment.admin.service.dashboard.AdminDashboardTradeService;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
import cn.daxpay.open.payment.trade.report.result.MerchantRankItemResult;
@@ -18,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -59,108 +59,59 @@ public class AdminDashboardTradeController {
/// 同时传 start/end 时按区间模式; 否则按 date 快捷模式
@Operation(summary = "交易概览(今日/昨日或自定义区间)")
@GetMapping("/overview")
public Result<TradeOverviewResult> overview(
@RequestParam(required = false) String date,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.overview(start, end));
}
return Res.ok(adminDashboardTradeService.overview(date != null ? date : "today"));
public Result<TradeOverviewResult> overview(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.overview(query));
}
// ===== 趋势 =====
@Operation(summary = "交易趋势(指定天数或自定义区间)")
@GetMapping("/trend")
public Result<List<TradeTrendItemResult>> trend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.trend(start, end));
}
return Res.ok(adminDashboardTradeService.trend(days));
public Result<List<TradeTrendItemResult>> trend(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.trend(query));
}
@Operation(summary = "退款趋势(指定天数或自定义区间)")
@GetMapping("/refund-trend")
public Result<List<RefundTrendItemResult>> refundTrend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.refundTrend(start, end));
}
return Res.ok(adminDashboardTradeService.refundTrend(days));
public Result<List<RefundTrendItemResult>> refundTrend(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.refundTrend(query));
}
// ===== 渠道 =====
@Operation(summary = "支付渠道分布(指定天数或自定义区间)")
@GetMapping("/provider-dist")
public Result<List<ProviderDistItemResult>> providerDist(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.providerDist(start, end));
}
return Res.ok(adminDashboardTradeService.providerDist(days));
public Result<List<ProviderDistItemResult>> providerDist(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.providerDist(query));
}
@Operation(summary = "支付渠道成功率(指定天数或自定义区间)")
@GetMapping("/provider-success")
public Result<List<ProviderSuccessItemResult>> providerSuccess(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.providerSuccess(start, end));
}
return Res.ok(adminDashboardTradeService.providerSuccess(days));
public Result<List<ProviderSuccessItemResult>> providerSuccess(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.providerSuccess(query));
}
// ===== 时段 =====
@Operation(summary = "时段分布(日均): 区间内按小时汇总后除以天数, 补齐 0-23")
@GetMapping("/hourly-dist")
public Result<List<HourlyDistItemResult>> hourlyDist(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.hourlyDist(start, end));
}
return Res.ok(adminDashboardTradeService.hourlyDist(days));
public Result<List<HourlyDistItemResult>> hourlyDist(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.hourlyDist(query));
}
// ===== 金额区间 =====
@Operation(summary = "金额区间分桶(指定天数或自定义区间)")
@GetMapping("/amount-range")
public Result<List<AmountRangeItemResult>> amountRange(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.amountRange(start, end));
}
return Res.ok(adminDashboardTradeService.amountRange(days));
public Result<List<AmountRangeItemResult>> amountRange(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.amountRange(query));
}
// ===== 商户排名 =====
@Operation(summary = "商户交易额排名(指定天数或自定义区间)")
@GetMapping("/merchant-rank")
public Result<List<MerchantRankItemResult>> merchantRank(
@RequestParam(defaultValue = "7") int days,
@RequestParam(defaultValue = "10") int limit,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(adminDashboardTradeService.merchantRank(start, end, limit));
}
return Res.ok(adminDashboardTradeService.merchantRank(days, limit));
public Result<List<MerchantRankItemResult>> merchantRank(TradeRangeQuery query) {
return Res.ok(adminDashboardTradeService.merchantRank(query));
}
}

View File

@@ -35,7 +35,7 @@ public class DyPlatformAppCapabilityController {
@GetMapping("/list-by-product")
public Result<List<DyPlatformAppCapabilityResult>> listByProduct(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
return Res.ok(dyPlatformAppCapabilityService.listByProduct(product));
}
@@ -52,7 +52,7 @@ public class DyPlatformAppCapabilityController {
@GetMapping("/list-supported-capabilities")
public Result<List<DyCapabilityOption>> listSupportedCapabilities(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
return Res.ok(dyPlatformAppCapabilityService.listSupportedCapabilities(product));
}
}

View File

@@ -1,14 +1,14 @@
package cn.daxpay.open.payment.admin.controller.trade;
import cn.daxpay.open.payment.admin.service.trade.OrderCloseAdminService;
import cn.daxpay.open.payment.trade.order.param.OrderCloseParam;
import cn.daxpay.open.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -28,10 +28,8 @@ public class OrderCloseAdminController {
@PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE)
@Operation(summary = "关闭订单(网关/普通统一入口)")
@PostMapping("/close")
public Result<Void> close(
@NotNull(message = "{validation.field.id.notNull}") Long containerId,
@NotBlank String tradeType) {
orderCloseAdminService.close(containerId, tradeType);
public Result<Void> close(@RequestBody @Valid OrderCloseParam param) {
orderCloseAdminService.close(param.getContainerId(), param.getTradeType());
return Res.ok();
}
}

View File

@@ -35,7 +35,7 @@ public class WxPlatformAppCapabilityController {
@GetMapping("/list-by-product")
public Result<List<WxPlatformAppCapabilityResult>> listByProduct(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
return Res.ok(wxPlatformAppCapabilityService.listByProduct(product));
}
@@ -52,7 +52,7 @@ public class WxPlatformAppCapabilityController {
@GetMapping("/list-supported-capabilities")
public Result<List<WxCapabilityOption>> listSupportedCapabilities(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
return Res.ok(wxPlatformAppCapabilityService.listSupportedCapabilities(product));
}
}

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.payment.admin.dao.dashboard.AdminTradeReportMapper;
import cn.daxpay.open.payment.admin.result.dashboard.AdminDashboardHeaderCountResult;
import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager;
import cn.daxpay.open.payment.merchant.dao.info.MerchantInfoManager;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
import cn.daxpay.open.payment.trade.report.result.MerchantRankItemResult;
@@ -55,25 +56,33 @@ public class AdminDashboardTradeService {
// ===== 概览 =====
public TradeOverviewResult overview(String date) {
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate target = "yesterday".equalsIgnoreCase(date) ? today.minusDays(1) : today;
OffsetDateTime start = target.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = target.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime prevStart = start.minusDays(1);
OffsetDateTime prevEnd = start;
/// 交易概览(含上期环比)
///
/// 区间模式(query 同时传 start + end)优先; 否则按 date 快捷模式(today/yesterday)。
public TradeOverviewResult overview(TradeRangeQuery query) {
OffsetDateTime start;
OffsetDateTime end;
OffsetDateTime prevStart;
OffsetDateTime prevEnd;
if (query.getStart() != null && query.getEnd() != null) {
// 区间模式: 上期为等长前移
start = tradeReportSupport.parseDateStart(query.getStart());
end = tradeReportSupport.parseDateEndExclusive(query.getEnd());
long daysSpan = java.time.Duration.between(start, end).toDays();
prevStart = start.minusDays(daysSpan);
prevEnd = start;
} else {
// 快捷模式: today / yesterday, 上期为前一天
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate target = "yesterday".equalsIgnoreCase(query.getDate()) ? today.minusDays(1) : today;
start = target.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
end = target.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
prevStart = start.minusDays(1);
prevEnd = start;
}
return aggregateOverview(start, end, prevStart, prevEnd);
}
public TradeOverviewResult overview(String start, String end) {
OffsetDateTime startUtc = tradeReportSupport.parseDateStart(start);
OffsetDateTime endUtc = tradeReportSupport.parseDateEndExclusive(end);
long daysSpan = java.time.Duration.between(startUtc, endUtc).toDays();
OffsetDateTime prevStart = startUtc.minusDays(daysSpan);
OffsetDateTime prevEnd = startUtc;
return aggregateOverview(startUtc, endUtc, prevStart, prevEnd);
}
private TradeOverviewResult aggregateOverview(
OffsetDateTime start, OffsetDateTime end,
OffsetDateTime prevStart, OffsetDateTime prevEnd) {
@@ -99,110 +108,54 @@ public class AdminDashboardTradeService {
// ===== 趋势 =====
public List<TradeTrendItemResult> trend(int days) {
int safeDays = tradeReportSupport.clampDays(days);
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate startDate = today.minusDays(safeDays - 1L);
OffsetDateTime start = startDate.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = today.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
return tradeReportSupport.fillTrendDays(startDate, safeDays, adminTradeReportMapper.trend(start, end));
public List<TradeTrendItemResult> trend(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int safeDays = tradeReportSupport.clampDays((int) java.time.Duration.between(range[0], range[1]).toDays());
LocalDate startDate = range[0].atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillTrendDays(startDate, safeDays, adminTradeReportMapper.trend(range[0], range[1]));
}
public List<TradeTrendItemResult> trend(String startStr, String endStr) {
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long days = java.time.Duration.between(start, end).toDays();
int safeDays = tradeReportSupport.clampDays((int) days);
LocalDate startDate = start.atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillTrendDays(startDate, safeDays, adminTradeReportMapper.trend(start, end));
}
public List<RefundTrendItemResult> refundTrend(int days) {
int safeDays = tradeReportSupport.clampDays(days);
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate startDate = today.minusDays(safeDays - 1L);
OffsetDateTime start = startDate.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = today.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
public List<RefundTrendItemResult> refundTrend(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int safeDays = tradeReportSupport.clampDays((int) java.time.Duration.between(range[0], range[1]).toDays());
LocalDate startDate = range[0].atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillRefundTrendDays(
startDate, safeDays, adminTradeReportMapper.refundTrend(start, end));
}
public List<RefundTrendItemResult> refundTrend(String startStr, String endStr) {
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long days = java.time.Duration.between(start, end).toDays();
int safeDays = tradeReportSupport.clampDays((int) days);
LocalDate startDate = start.atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillRefundTrendDays(
startDate, safeDays, adminTradeReportMapper.refundTrend(start, end));
startDate, safeDays, adminTradeReportMapper.refundTrend(range[0], range[1]));
}
// ===== 渠道 =====
public List<ProviderDistItemResult> providerDist(int days) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
public List<ProviderDistItemResult> providerDist(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return adminTradeReportMapper.providerDist(range[0], range[1]);
}
public List<ProviderDistItemResult> providerDist(String startStr, String endStr) {
return adminTradeReportMapper.providerDist(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr));
}
public List<ProviderSuccessItemResult> providerSuccess(int days) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
public List<ProviderSuccessItemResult> providerSuccess(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return adminTradeReportMapper.providerSuccess(range[0], range[1]);
}
public List<ProviderSuccessItemResult> providerSuccess(String startStr, String endStr) {
return adminTradeReportMapper.providerSuccess(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr));
}
// ===== 时段 / 金额 =====
public List<HourlyDistItemResult> hourlyDist(int days) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
int daysSpan = tradeReportSupport.clampDays(days);
public List<HourlyDistItemResult> hourlyDist(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
long daysSpan = Math.max(1L, java.time.Duration.between(range[0], range[1]).toDays());
return tradeReportSupport.toDailyAverage(
tradeReportSupport.fillHourlyDist(adminTradeReportMapper.hourlyDist(range[0], range[1])),
daysSpan);
}
public List<HourlyDistItemResult> hourlyDist(String startStr, String endStr) {
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long daysSpan = Math.max(1L, java.time.Duration.between(start, end).toDays());
return tradeReportSupport.toDailyAverage(
tradeReportSupport.fillHourlyDist(adminTradeReportMapper.hourlyDist(start, end)),
daysSpan);
}
public List<AmountRangeItemResult> amountRange(int days) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
public List<AmountRangeItemResult> amountRange(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return tradeReportSupport.fillAmountRange(adminTradeReportMapper.amountRange(range[0], range[1]));
}
public List<AmountRangeItemResult> amountRange(String startStr, String endStr) {
return tradeReportSupport.fillAmountRange(adminTradeReportMapper.amountRange(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr)));
}
// ===== 商户排名 =====
public List<MerchantRankItemResult> merchantRank(int days, int limit) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
public List<MerchantRankItemResult> merchantRank(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int limit = tradeReportSupport.clampLimit(query.getLimit() == null ? 0 : query.getLimit());
return tradeReportSupport.computeMerchantProportion(
adminTradeReportMapper.merchantRank(range[0], range[1], tradeReportSupport.clampLimit(limit)));
}
public List<MerchantRankItemResult> merchantRank(String startStr, String endStr, int limit) {
return tradeReportSupport.computeMerchantProportion(adminTradeReportMapper.merchantRank(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr),
tradeReportSupport.clampLimit(limit)));
adminTradeReportMapper.merchantRank(range[0], range[1], limit));
}
}

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.payment.app.admin.controller.dashboard;
import cn.daxpay.open.payment.app.admin.service.dashboard.AppAdminDashboardTradeService;
import cn.daxpay.open.payment.admin.result.dashboard.AdminDashboardHeaderCountResult;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.TradeOverviewResult;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
@@ -11,7 +12,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/// # 工作台交易统计(运营移动端)
@@ -35,15 +35,10 @@ public class AppAdminDashboardTradeController {
}
/// 交易概览: 支持 today/yesterday 快捷模式 + 自定义区间模式(含环比)
/// 同时传 start/end 时按区间模式; 否则按 date 快捷模式
@Operation(summary = "交易概览(今日/昨日或自定义区间)")
@GetMapping("/overview")
public Result<TradeOverviewResult> overview(
@RequestParam(required = false) String date,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.overview(start, end));
}
return Res.ok(dashboardTradeService.overview(date != null ? date : "today"));
public Result<TradeOverviewResult> overview(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.overview(query));
}
}

View File

@@ -1,14 +1,14 @@
package cn.daxpay.open.payment.app.admin.controller.trade;
import cn.daxpay.open.payment.app.admin.service.trade.AppAdminOrderCloseService;
import cn.daxpay.open.payment.trade.order.param.OrderCloseParam;
import cn.daxpay.open.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -28,10 +28,8 @@ public class AppAdminOrderCloseController {
@PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE)
@Operation(summary = "关闭订单(网关/普通统一入口)")
@PostMapping("/close")
public Result<Void> close(
@NotNull(message = "{validation.field.id.notNull}") Long containerId,
@NotBlank String tradeType) {
appAdminOrderCloseService.close(containerId, tradeType);
public Result<Void> close(@RequestBody @Valid OrderCloseParam param) {
appAdminOrderCloseService.close(param.getContainerId(), param.getTradeType());
return Res.ok();
}
}

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.payment.app.admin.service.dashboard;
import cn.daxpay.open.payment.admin.result.dashboard.AdminDashboardHeaderCountResult;
import cn.daxpay.open.payment.admin.service.dashboard.AdminDashboardTradeService;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.TradeOverviewResult;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -20,13 +21,8 @@ public class AppAdminDashboardTradeService {
return adminDashboardTradeService.headerCounts();
}
/// 交易概览快捷模式today / yesterday
public TradeOverviewResult overview(String date) {
return adminDashboardTradeService.overview(date);
}
/// 交易概览区间模式start + endyyyy-MM-dd
public TradeOverviewResult overview(String start, String end) {
return adminDashboardTradeService.overview(start, end);
/// 交易概览(区间模式优先, 否则按 date 快捷模式)
public TradeOverviewResult overview(TradeRangeQuery query) {
return adminDashboardTradeService.overview(query);
}
}

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.payment.app.merchant.controller.dashboard;
import cn.daxpay.open.payment.app.merchant.service.dashboard.AppMerchantDashboardTradeService;
import cn.daxpay.open.payment.merchant.result.dashboard.MchDashboardHeaderCountResult;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.DimRankItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
@@ -18,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -43,102 +43,53 @@ public class AppMerchantDashboardTradeController {
}
/// 交易概览: 支持 today/yesterday 快捷模式 + 自定义区间模式(含环比)
/// 同时传 start/end 时按区间模式; 否则按 date 快捷模式
@Operation(summary = "交易概览(今日/昨日或自定义区间)")
@GetMapping("/overview")
public Result<TradeOverviewResult> overview(
@RequestParam(required = false) String date,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.overview(start, end));
}
return Res.ok(dashboardTradeService.overview(date != null ? date : "today"));
public Result<TradeOverviewResult> overview(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.overview(query));
}
@Operation(summary = "交易趋势")
@GetMapping("/trend")
public Result<List<TradeTrendItemResult>> trend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.trend(start, end));
}
return Res.ok(dashboardTradeService.trend(days));
public Result<List<TradeTrendItemResult>> trend(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.trend(query));
}
@Operation(summary = "退款趋势")
@GetMapping("/refund-trend")
public Result<List<RefundTrendItemResult>> refundTrend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.refundTrend(start, end));
}
return Res.ok(dashboardTradeService.refundTrend(days));
public Result<List<RefundTrendItemResult>> refundTrend(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.refundTrend(query));
}
@Operation(summary = "支付渠道分布")
@GetMapping("/provider-dist")
public Result<List<ProviderDistItemResult>> providerDist(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.providerDist(start, end));
}
return Res.ok(dashboardTradeService.providerDist(days));
public Result<List<ProviderDistItemResult>> providerDist(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.providerDist(query));
}
@Operation(summary = "支付渠道成功率")
@GetMapping("/provider-success")
public Result<List<ProviderSuccessItemResult>> providerSuccess(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.providerSuccess(start, end));
}
return Res.ok(dashboardTradeService.providerSuccess(days));
public Result<List<ProviderSuccessItemResult>> providerSuccess(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.providerSuccess(query));
}
@Operation(summary = "时段分布(日均)")
@GetMapping("/hourly-dist")
public Result<List<HourlyDistItemResult>> hourlyDist(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.hourlyDist(start, end));
}
return Res.ok(dashboardTradeService.hourlyDist(days));
public Result<List<HourlyDistItemResult>> hourlyDist(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.hourlyDist(query));
}
@Operation(summary = "金额区间分桶")
@GetMapping("/amount-range")
public Result<List<AmountRangeItemResult>> amountRange(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.amountRange(start, end));
}
return Res.ok(dashboardTradeService.amountRange(days));
public Result<List<AmountRangeItemResult>> amountRange(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.amountRange(query));
}
/// 维度排行: dim=channelMch|app|store
@Operation(summary = "维度交易额排名(通道商户/应用/门店)")
@GetMapping("/dim-rank")
public Result<List<DimRankItemResult>> dimRank(
@RequestParam(defaultValue = "app") String dim,
@RequestParam(defaultValue = "7") int days,
@RequestParam(defaultValue = "10") int limit,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(dashboardTradeService.dimRank(dim, start, end, limit));
}
return Res.ok(dashboardTradeService.dimRank(dim, days, limit));
public Result<List<DimRankItemResult>> dimRank(TradeRangeQuery query) {
return Res.ok(dashboardTradeService.dimRank(query));
}
}

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.payment.app.merchant.service.dashboard;
import cn.daxpay.open.payment.merchant.result.dashboard.MchDashboardHeaderCountResult;
import cn.daxpay.open.payment.merchant.service.dashboard.MchDashboardTradeService;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.DimRankItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
@@ -29,83 +30,43 @@ public class AppMerchantDashboardTradeService {
return mchDashboardTradeService.headerCounts();
}
/// 交易概览快捷模式today / yesterday
public TradeOverviewResult overview(String date) {
return mchDashboardTradeService.overview(date);
/// 交易概览(区间模式优先, 否则按 date 快捷模式)
public TradeOverviewResult overview(TradeRangeQuery query) {
return mchDashboardTradeService.overview(query);
}
/// 交易概览(区间模式)
public TradeOverviewResult overview(String start, String end) {
return mchDashboardTradeService.overview(start, end);
/// 交易趋势
public List<TradeTrendItemResult> trend(TradeRangeQuery query) {
return mchDashboardTradeService.trend(query);
}
/// 交易趋势(快捷天数)
public List<TradeTrendItemResult> trend(int days) {
return mchDashboardTradeService.trend(days);
/// 退款趋势
public List<RefundTrendItemResult> refundTrend(TradeRangeQuery query) {
return mchDashboardTradeService.refundTrend(query);
}
/// 交易趋势(区间模式)
public List<TradeTrendItemResult> trend(String start, String end) {
return mchDashboardTradeService.trend(start, end);
/// 支付渠道分布
public List<ProviderDistItemResult> providerDist(TradeRangeQuery query) {
return mchDashboardTradeService.providerDist(query);
}
/// 退款趋势(快捷天数)
public List<RefundTrendItemResult> refundTrend(int days) {
return mchDashboardTradeService.refundTrend(days);
/// 支付渠道成功率
public List<ProviderSuccessItemResult> providerSuccess(TradeRangeQuery query) {
return mchDashboardTradeService.providerSuccess(query);
}
/// 退款趋势(区间模式)
public List<RefundTrendItemResult> refundTrend(String start, String end) {
return mchDashboardTradeService.refundTrend(start, end);
/// 时段分布(日均)
public List<HourlyDistItemResult> hourlyDist(TradeRangeQuery query) {
return mchDashboardTradeService.hourlyDist(query);
}
/// 支付渠道分布(快捷天数)
public List<ProviderDistItemResult> providerDist(int days) {
return mchDashboardTradeService.providerDist(days);
/// 金额区间分桶
public List<AmountRangeItemResult> amountRange(TradeRangeQuery query) {
return mchDashboardTradeService.amountRange(query);
}
/// 支付渠道分布(区间模式)
public List<ProviderDistItemResult> providerDist(String start, String end) {
return mchDashboardTradeService.providerDist(start, end);
}
/// 支付渠道成功率(快捷天数)
public List<ProviderSuccessItemResult> providerSuccess(int days) {
return mchDashboardTradeService.providerSuccess(days);
}
/// 支付渠道成功率(区间模式)
public List<ProviderSuccessItemResult> providerSuccess(String start, String end) {
return mchDashboardTradeService.providerSuccess(start, end);
}
/// 时段分布(快捷天数)
public List<HourlyDistItemResult> hourlyDist(int days) {
return mchDashboardTradeService.hourlyDist(days);
}
/// 时段分布(区间模式)
public List<HourlyDistItemResult> hourlyDist(String start, String end) {
return mchDashboardTradeService.hourlyDist(start, end);
}
/// 金额区间分桶(快捷天数)
public List<AmountRangeItemResult> amountRange(int days) {
return mchDashboardTradeService.amountRange(days);
}
/// 金额区间分桶(区间模式)
public List<AmountRangeItemResult> amountRange(String start, String end) {
return mchDashboardTradeService.amountRange(start, end);
}
/// 维度排行(快捷天数)
public List<DimRankItemResult> dimRank(String dim, int days, int limit) {
return mchDashboardTradeService.dimRank(dim, days, limit);
}
/// 维度排行(区间模式)
public List<DimRankItemResult> dimRank(String dim, String start, String end, int limit) {
return mchDashboardTradeService.dimRank(dim, start, end, limit);
/// 维度排行: dim=channelMch|app|store
public List<DimRankItemResult> dimRank(TradeRangeQuery query) {
return mchDashboardTradeService.dimRank(query);
}
}

View File

@@ -0,0 +1,26 @@
package cn.daxpay.open.payment.trade.order.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 订单关闭请求参数
///
/// 统一入口关闭网关支付/普通支付订单的请求体。
@Data
@Accessors(chain = true)
@Schema(title = "订单关闭请求参数")
public class OrderCloseParam {
/// 业务容器ID(pay_gateway_order / pay_normal_order 主键)
@NotNull(message = "{validation.field.id.notNull}")
@Schema(description = "业务容器ID")
private Long containerId;
/// 容器类型: gateway=网关支付, normal=普通支付
@NotBlank
@Schema(description = "容器类型: gateway / normal")
private String tradeType;
}

View File

@@ -0,0 +1,45 @@
package cn.daxpay.open.payment.trade.report.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 交易统计区间查询参数
///
/// 工作台/分析页聚合统计的统一查询条件, 适用于运营端/商户端/移动端 Dashboard。
/// 作为 GET 请求的查询对象, 由 Spring 自动绑定 query string。
///
/// ## 参数约定
/// - **天数模式**: [days] 参数(近 N 天含今天)
/// - **区间模式**: [start] + [end] 参数(yyyy-MM-dd, 均包含)
/// - 两者二选一: 同时传时优先区间模式
/// (由 [cn.daxpay.open.payment.trade.report.support.TradeReportSupport#resolveRange] 统一解析)
@Data
@Accessors(chain = true)
@Schema(title = "交易统计区间查询参数")
public class TradeRangeQuery {
/// 快捷模式日期: today / yesterday(仅 overview 接口使用)
@Schema(description = "快捷模式日期: today / yesterday")
private String date;
/// 区间模式开始日期(yyyy-MM-dd, 包含)
@Schema(description = "区间开始日期(yyyy-MM-dd)")
private String start;
/// 区间模式结束日期(yyyy-MM-dd, 包含)
@Schema(description = "区间结束日期(yyyy-MM-dd)")
private String end;
/// 天数模式: 近 N 天含今天(为空时由解析器按 7 天兜底)
@Schema(description = "近 N 天含今天")
private Integer days;
/// 排行榜返回条数(为空时由解析器按 10 兜底, 上限 50)
@Schema(description = "排行榜返回条数")
private Integer limit;
/// 维度: channelMch / app / store(仅维度排行接口使用, 为空时按 app 兜底)
@Schema(description = "维度: channelMch / app / store")
private String dim;
}

View File

@@ -1,5 +1,6 @@
package cn.daxpay.open.payment.trade.report.support;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.DimRankItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
@@ -29,6 +30,9 @@ public class TradeReportSupport {
private static final int TREND_DAYS_MIN = 1;
private static final int TREND_DAYS_MAX = 365;
/// 天数模式默认值(未传 days 时兜底)
private static final int DEFAULT_RANGE_DAYS = 7;
private static final int RANK_LIMIT_MAX = 50;
private static final int RANK_LIMIT_DEFAULT = 10;
@@ -56,6 +60,21 @@ public class TradeReportSupport {
return new OffsetDateTime[]{start, end};
}
/// 统一解析查询条件为半开区间 `[start, end)`
///
/// 区间模式优先(start + end 都非空), 否则按 days 天数模式(days 为空时按 [DEFAULT_RANGE_DAYS] 兜底)。
/// 供运营端/商户端/移动端 Dashboard 各统计接口共用, 消除 Controller 层的 if 分支。
public OffsetDateTime[] resolveRange(TradeRangeQuery query) {
if (query.getStart() != null && query.getEnd() != null) {
return new OffsetDateTime[]{
parseDateStart(query.getStart()),
parseDateEndExclusive(query.getEnd())
};
}
int days = query.getDays() != null ? query.getDays() : DEFAULT_RANGE_DAYS;
return daysRange(days);
}
/// 解析 yyyy-MM-dd 为当日 00:00 CST
public OffsetDateTime parseDateStart(String date) {
return LocalDate.parse(date, DATE_FMT).atStartOfDay(ZONE_CST).toOffsetDateTime();

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.payment.merchant.result.dashboard.MchDashboardHeaderCountResult;
import cn.daxpay.open.payment.merchant.service.dashboard.MchDashboardTradeService;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.DimRankItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
@@ -18,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -43,100 +43,50 @@ public class MchDashboardTradeController {
@Operation(summary = "交易概览(今日/昨日或自定义区间)")
@GetMapping("/overview")
public Result<TradeOverviewResult> overview(
@RequestParam(required = false) String date,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.overview(start, end));
}
return Res.ok(mchDashboardTradeService.overview(date != null ? date : "today"));
public Result<TradeOverviewResult> overview(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.overview(query));
}
@Operation(summary = "交易趋势")
@GetMapping("/trend")
public Result<List<TradeTrendItemResult>> trend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.trend(start, end));
}
return Res.ok(mchDashboardTradeService.trend(days));
public Result<List<TradeTrendItemResult>> trend(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.trend(query));
}
@Operation(summary = "退款趋势")
@GetMapping("/refund-trend")
public Result<List<RefundTrendItemResult>> refundTrend(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.refundTrend(start, end));
}
return Res.ok(mchDashboardTradeService.refundTrend(days));
public Result<List<RefundTrendItemResult>> refundTrend(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.refundTrend(query));
}
@Operation(summary = "支付渠道分布")
@GetMapping("/provider-dist")
public Result<List<ProviderDistItemResult>> providerDist(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.providerDist(start, end));
}
return Res.ok(mchDashboardTradeService.providerDist(days));
public Result<List<ProviderDistItemResult>> providerDist(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.providerDist(query));
}
@Operation(summary = "支付渠道成功率")
@GetMapping("/provider-success")
public Result<List<ProviderSuccessItemResult>> providerSuccess(
@RequestParam(defaultValue = "30") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.providerSuccess(start, end));
}
return Res.ok(mchDashboardTradeService.providerSuccess(days));
public Result<List<ProviderSuccessItemResult>> providerSuccess(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.providerSuccess(query));
}
@Operation(summary = "时段分布(日均)")
@GetMapping("/hourly-dist")
public Result<List<HourlyDistItemResult>> hourlyDist(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.hourlyDist(start, end));
}
return Res.ok(mchDashboardTradeService.hourlyDist(days));
public Result<List<HourlyDistItemResult>> hourlyDist(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.hourlyDist(query));
}
@Operation(summary = "金额区间分桶")
@GetMapping("/amount-range")
public Result<List<AmountRangeItemResult>> amountRange(
@RequestParam(defaultValue = "7") int days,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.amountRange(start, end));
}
return Res.ok(mchDashboardTradeService.amountRange(days));
public Result<List<AmountRangeItemResult>> amountRange(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.amountRange(query));
}
/// 维度排行: dim=channelMch|app|store
@Operation(summary = "维度交易额排名(通道商户/应用/门店)")
@GetMapping("/dim-rank")
public Result<List<DimRankItemResult>> dimRank(
@RequestParam(defaultValue = "app") String dim,
@RequestParam(defaultValue = "7") int days,
@RequestParam(defaultValue = "10") int limit,
@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
if (start != null && end != null) {
return Res.ok(mchDashboardTradeService.dimRank(dim, start, end, limit));
}
return Res.ok(mchDashboardTradeService.dimRank(dim, days, limit));
public Result<List<DimRankItemResult>> dimRank(TradeRangeQuery query) {
return Res.ok(mchDashboardTradeService.dimRank(query));
}
}

View File

@@ -78,7 +78,7 @@ public class MchDyChannelAppCapabilityController {
@GetMapping("/list-supported-capabilities")
public Result<List<DyCapabilityOption>> listSupportedCapabilities(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
// 只读产品元数据,无需通道商户归属校验
return Res.ok(dyPlatformAppCapabilityService.listSupportedCapabilities(product));
}

View File

@@ -1,14 +1,14 @@
package cn.daxpay.open.payment.merchant.controller.trade;
import cn.daxpay.open.payment.merchant.service.trade.MchOrderCloseService;
import cn.daxpay.open.payment.trade.order.param.OrderCloseParam;
import cn.daxpay.open.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -28,10 +28,8 @@ public class MchOrderCloseController {
@PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE)
@Operation(summary = "关闭订单(网关/普通统一入口)")
@PostMapping("/close")
public Result<Void> close(
@NotNull(message = "{validation.field.id.notNull}") Long containerId,
@NotBlank String tradeType) {
mchOrderCloseService.close(containerId, tradeType);
public Result<Void> close(@RequestBody @Valid OrderCloseParam param) {
mchOrderCloseService.close(param.getContainerId(), param.getTradeType());
return Res.ok();
}
}

View File

@@ -78,7 +78,7 @@ public class MchWxChannelAppCapabilityController {
@GetMapping("/list-supported-capabilities")
public Result<List<WxCapabilityOption>> listSupportedCapabilities(
@NotBlank(message = "{validation.field.product.notBlank}")
@Parameter(description = "支付产品编码") @RequestParam String product) {
@Parameter(description = "支付产品编码") String product) {
// 只读产品元数据,无需通道商户归属校验
return Res.ok(wxPlatformAppCapabilityService.listSupportedCapabilities(product));
}

View File

@@ -11,6 +11,7 @@ import cn.daxpay.open.payment.merchant.entity.appinfo.MchAppInfo;
import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant;
import cn.daxpay.open.payment.merchant.entity.store.MchStoreInfo;
import cn.daxpay.open.payment.merchant.result.dashboard.MchDashboardHeaderCountResult;
import cn.daxpay.open.payment.trade.report.param.TradeRangeQuery;
import cn.daxpay.open.payment.trade.report.result.AmountRangeItemResult;
import cn.daxpay.open.payment.trade.report.result.DimRankItemResult;
import cn.daxpay.open.payment.trade.report.result.HourlyDistItemResult;
@@ -79,27 +80,34 @@ public class MchDashboardTradeService {
// ===== 概览 =====
public TradeOverviewResult overview(String date) {
/// 交易概览(含上期环比)
///
/// 区间模式(query 同时传 start + end)优先; 否则按 date 快捷模式(today/yesterday)。
public TradeOverviewResult overview(TradeRangeQuery query) {
String mchNo = requireMchNo();
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate target = "yesterday".equalsIgnoreCase(date) ? today.minusDays(1) : today;
OffsetDateTime start = target.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = target.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime prevStart = start.minusDays(1);
OffsetDateTime prevEnd = start;
OffsetDateTime start;
OffsetDateTime end;
OffsetDateTime prevStart;
OffsetDateTime prevEnd;
if (query.getStart() != null && query.getEnd() != null) {
// 区间模式: 上期为等长前移
start = tradeReportSupport.parseDateStart(query.getStart());
end = tradeReportSupport.parseDateEndExclusive(query.getEnd());
long daysSpan = java.time.Duration.between(start, end).toDays();
prevStart = start.minusDays(daysSpan);
prevEnd = start;
} else {
// 快捷模式: today / yesterday, 上期为前一天
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate target = "yesterday".equalsIgnoreCase(query.getDate()) ? today.minusDays(1) : today;
start = target.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
end = target.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
prevStart = start.minusDays(1);
prevEnd = start;
}
return aggregateOverview(mchNo, start, end, prevStart, prevEnd);
}
public TradeOverviewResult overview(String start, String end) {
String mchNo = requireMchNo();
OffsetDateTime startUtc = tradeReportSupport.parseDateStart(start);
OffsetDateTime endUtc = tradeReportSupport.parseDateEndExclusive(end);
long daysSpan = java.time.Duration.between(startUtc, endUtc).toDays();
OffsetDateTime prevStart = startUtc.minusDays(daysSpan);
OffsetDateTime prevEnd = startUtc;
return aggregateOverview(mchNo, startUtc, endUtc, prevStart, prevEnd);
}
private TradeOverviewResult aggregateOverview(
String mchNo,
OffsetDateTime start, OffsetDateTime end,
@@ -126,130 +134,62 @@ public class MchDashboardTradeService {
// ===== 趋势 =====
public List<TradeTrendItemResult> trend(int days) {
public List<TradeTrendItemResult> trend(TradeRangeQuery query) {
String mchNo = requireMchNo();
int safeDays = tradeReportSupport.clampDays(days);
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate startDate = today.minusDays(safeDays - 1L);
OffsetDateTime start = startDate.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = today.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int safeDays = tradeReportSupport.clampDays((int) java.time.Duration.between(range[0], range[1]).toDays());
LocalDate startDate = range[0].atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillTrendDays(
startDate, safeDays, mchTradeReportMapper.trend(start, end, mchNo));
startDate, safeDays, mchTradeReportMapper.trend(range[0], range[1], mchNo));
}
public List<TradeTrendItemResult> trend(String startStr, String endStr) {
public List<RefundTrendItemResult> refundTrend(TradeRangeQuery query) {
String mchNo = requireMchNo();
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long days = java.time.Duration.between(start, end).toDays();
int safeDays = tradeReportSupport.clampDays((int) days);
LocalDate startDate = start.atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillTrendDays(
startDate, safeDays, mchTradeReportMapper.trend(start, end, mchNo));
}
public List<RefundTrendItemResult> refundTrend(int days) {
String mchNo = requireMchNo();
int safeDays = tradeReportSupport.clampDays(days);
LocalDate today = LocalDate.now(TradeReportSupport.ZONE_CST);
LocalDate startDate = today.minusDays(safeDays - 1L);
OffsetDateTime start = startDate.atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime end = today.plusDays(1).atStartOfDay(TradeReportSupport.ZONE_CST).toOffsetDateTime();
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int safeDays = tradeReportSupport.clampDays((int) java.time.Duration.between(range[0], range[1]).toDays());
LocalDate startDate = range[0].atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillRefundTrendDays(
startDate, safeDays, mchTradeReportMapper.refundTrend(start, end, mchNo));
}
public List<RefundTrendItemResult> refundTrend(String startStr, String endStr) {
String mchNo = requireMchNo();
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long days = java.time.Duration.between(start, end).toDays();
int safeDays = tradeReportSupport.clampDays((int) days);
LocalDate startDate = start.atZoneSameInstant(TradeReportSupport.ZONE_CST).toLocalDate();
return tradeReportSupport.fillRefundTrendDays(
startDate, safeDays, mchTradeReportMapper.refundTrend(start, end, mchNo));
startDate, safeDays, mchTradeReportMapper.refundTrend(range[0], range[1], mchNo));
}
// ===== 渠道 =====
public List<ProviderDistItemResult> providerDist(int days) {
public List<ProviderDistItemResult> providerDist(TradeRangeQuery query) {
String mchNo = requireMchNo();
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return mchTradeReportMapper.providerDist(range[0], range[1], mchNo);
}
public List<ProviderDistItemResult> providerDist(String startStr, String endStr) {
public List<ProviderSuccessItemResult> providerSuccess(TradeRangeQuery query) {
String mchNo = requireMchNo();
return mchTradeReportMapper.providerDist(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr),
mchNo);
}
public List<ProviderSuccessItemResult> providerSuccess(int days) {
String mchNo = requireMchNo();
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return mchTradeReportMapper.providerSuccess(range[0], range[1], mchNo);
}
public List<ProviderSuccessItemResult> providerSuccess(String startStr, String endStr) {
String mchNo = requireMchNo();
return mchTradeReportMapper.providerSuccess(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr),
mchNo);
}
// ===== 时段 / 金额 =====
public List<HourlyDistItemResult> hourlyDist(int days) {
public List<HourlyDistItemResult> hourlyDist(TradeRangeQuery query) {
String mchNo = requireMchNo();
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
int daysSpan = tradeReportSupport.clampDays(days);
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
long daysSpan = Math.max(1L, java.time.Duration.between(range[0], range[1]).toDays());
return tradeReportSupport.toDailyAverage(
tradeReportSupport.fillHourlyDist(mchTradeReportMapper.hourlyDist(range[0], range[1], mchNo)),
daysSpan);
}
public List<HourlyDistItemResult> hourlyDist(String startStr, String endStr) {
public List<AmountRangeItemResult> amountRange(TradeRangeQuery query) {
String mchNo = requireMchNo();
OffsetDateTime start = tradeReportSupport.parseDateStart(startStr);
OffsetDateTime end = tradeReportSupport.parseDateEndExclusive(endStr);
long daysSpan = Math.max(1L, java.time.Duration.between(start, end).toDays());
return tradeReportSupport.toDailyAverage(
tradeReportSupport.fillHourlyDist(mchTradeReportMapper.hourlyDist(start, end, mchNo)),
daysSpan);
}
public List<AmountRangeItemResult> amountRange(int days) {
String mchNo = requireMchNo();
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
return tradeReportSupport.fillAmountRange(mchTradeReportMapper.amountRange(range[0], range[1], mchNo));
}
public List<AmountRangeItemResult> amountRange(String startStr, String endStr) {
String mchNo = requireMchNo();
return tradeReportSupport.fillAmountRange(mchTradeReportMapper.amountRange(
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr),
mchNo));
}
// ===== 维度排名 =====
/// @param dim channelMch | app | store
public List<DimRankItemResult> dimRank(String dim, int days, int limit) {
OffsetDateTime[] range = tradeReportSupport.daysRange(days);
return dimRank(requireMchNo(), dim, range[0], range[1], limit);
}
public List<DimRankItemResult> dimRank(String dim, String startStr, String endStr, int limit) {
return dimRank(
requireMchNo(),
dim,
tradeReportSupport.parseDateStart(startStr),
tradeReportSupport.parseDateEndExclusive(endStr),
limit);
/// dim = channelMch | app | store(为空时按 app 兜底)
public List<DimRankItemResult> dimRank(TradeRangeQuery query) {
OffsetDateTime[] range = tradeReportSupport.resolveRange(query);
int limit = query.getLimit() == null ? 0 : query.getLimit();
return dimRank(requireMchNo(), query.getDim(), range[0], range[1], limit);
}
private List<DimRankItemResult> dimRank(

View File

@@ -11,7 +11,7 @@ import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -42,14 +42,7 @@ public class PlatformFileController {
@Operation(summary = "确认上传")
@PostMapping("/upload/confirm")
public Result<Void> confirmUpload(
@NotNull(message = "{validation.field.fileId.notNull}") @RequestParam Long fileId,
@NotNull(message = "{validation.field.objectKey.notBlank}") @RequestParam String objectKey,
@RequestParam(required = false) String etag) {
var param = new FileUploadConfirmParam();
param.setFileId(fileId);
param.setObjectKey(objectKey);
param.setEtag(etag);
public Result<Void> confirmUpload(@RequestBody @Valid FileUploadConfirmParam param) {
platformFileService.confirmUpload(param);
return Res.ok();
}

View File

@@ -1,8 +1,8 @@
package cn.daxpay.open.platform.capability.file.param;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/// # 文件上传确认请求参数
///
@@ -11,9 +11,11 @@ import java.io.Serializable;
public class FileUploadConfirmParam {
/// 文件ID
@NotNull(message = "{validation.field.fileId.notNull}")
private Long fileId;
/// 对象Key
@NotNull(message = "{validation.field.objectKey.notBlank}")
private String objectKey;
/// 对象ETag

View File

@@ -79,7 +79,7 @@ public class SystemSensitiveWordController {
@Operation(summary = "词面是否存在")
@GetMapping("/exists-by-word")
public Result<Boolean> existsByWord(
@RequestParam String word,
String word,
@RequestParam(required = false) Long id) {
return Res.ok(systemSensitiveWordService.existsByWord(word, id));
}

View File

@@ -40,7 +40,7 @@ public class OnlineUserController {
@PermCode(code = PermCodes.Action.KICKOUT)
@Operation(summary = "强制用户下线")
@PostMapping("/kickout")
public Result<Void> kickout(@RequestParam String sessionId) {
public Result<Void> kickout(String sessionId) {
onlineUserService.kickout(sessionId);
return Res.ok();
}

View File

@@ -21,7 +21,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -77,7 +76,7 @@ public class SocialLoginConfigController {
@PostMapping("/update-enabled")
public Result<Void> updateEnabled(
@NotBlank(message = "{validation.field.source.notBlank}") String source,
@RequestParam Boolean enabled) {
Boolean enabled) {
socialLoginConfigService.updateEnabled(source, enabled);
return Res.ok();
}

View File

@@ -52,7 +52,7 @@ public class SocialEndpoint {
@Operation(summary = "生成授权地址")
@GetMapping("/render/{source}")
public Result<String> render(@PathVariable String source,
@RequestParam String client,
String client,
@RequestParam(required = false) String mode,
@RequestParam(required = false) Boolean silent) {
return Res.ok(socialLoginService.generateAuthorizeUrl(source, client, mode, silent));
@@ -97,7 +97,7 @@ public class SocialEndpoint {
@IgnoreAuth(login = true)
@Operation(summary = "解除第三方账号绑定")
@PostMapping("/unbind")
public Result<Void> unbind(@RequestParam String source) {
public Result<Void> unbind(String source) {
socialLoginService.unbind(SecurityUtil.getUserId(), source);
return Res.ok();
}

View File

@@ -2,6 +2,7 @@ package cn.daxpay.open.platform.system.controller.protocol;
import cn.daxpay.open.platform.system.enums.UserProtocolClientTypeEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolTypeEnum;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolCopyParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolContentResult;
@@ -19,6 +20,7 @@ import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@@ -162,9 +164,8 @@ public class UserProtocolController {
@PermCode(code = PermCodes.Action.MANAGE)
@Operation(summary = "复制到其他端")
@PostMapping("/copy-to-client")
public Result<Long> copyToClient(@NotNull(message = "{validation.field.id.notNull}") Long id,
@NotNull(message = "{validation.field.clientType.notBlank}") String clientType){
return Res.ok(userProtocolService.copyToClient(id, clientType));
public Result<Long> copyToClient(@RequestBody @Valid UserProtocolCopyParam param){
return Res.ok(userProtocolService.copyToClient(param.getId(), param.getClientType()));
}
}

View File

@@ -0,0 +1,26 @@
package cn.daxpay.open.platform.system.param.protocol;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 协议复制请求参数
///
/// 复制协议到其他端(连同各语言的当前生效版本一起复制)的请求体。
@Data
@Accessors(chain = true)
@Schema(title = "协议复制请求参数")
public class UserProtocolCopyParam {
/// 源协议ID
@NotNull(message = "{validation.field.id.notNull}")
@Schema(description = "源协议ID")
private Long id;
/// 目标端类型
@NotBlank(message = "{validation.field.clientType.notBlank}")
@Schema(description = "目标端类型")
private String clientType;
}

View File

@@ -55,13 +55,13 @@ public class EasyPayApiV1Controller {
@Operation(summary = "查询订单状态(内部)")
@GetMapping("/order/status")
public Result<EasyPayOrderStatusResult> queryOrderStatus(@RequestParam @NotNull Long orderId) {
public Result<EasyPayOrderStatusResult> queryOrderStatus(@NotNull Long orderId) {
return Res.ok(easyPayAssistService.queryOrderStatus(orderId));
}
@Operation(summary = "收银台订单信息(内部)")
@GetMapping("/order/info")
public Result<EasyPaySubmitInfoResult> orderInfo(@RequestParam @NotNull Long id) {
public Result<EasyPaySubmitInfoResult> orderInfo(@NotNull Long id) {
return Res.ok(easyPayAssistService.findSubmitInfo(id));
}
}

View File

@@ -73,13 +73,13 @@ public class EasyPayApiV2Controller {
@Operation(summary = "查询订单状态(内部)")
@GetMapping("/order/status")
public Result<EasyPayOrderStatusResult> queryOrderStatus(@RequestParam @NotNull Long orderId) {
public Result<EasyPayOrderStatusResult> queryOrderStatus(@NotNull Long orderId) {
return Res.ok(easyPayAssistService.queryOrderStatus(orderId));
}
@Operation(summary = "收银台订单信息(内部)")
@GetMapping("/order/info")
public Result<EasyPaySubmitInfoResult> orderInfo(@RequestParam @NotNull Long id) {
public Result<EasyPaySubmitInfoResult> orderInfo(@NotNull Long id) {
return Res.ok(easyPayAssistService.findSubmitInfo(id));
}

View File

@@ -18,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/// # 易支付协议订单(运营端)
@@ -59,12 +58,11 @@ public class EasyPayOrderAdminController {
}
@PermCode(code = PermCodes.Action.MANAGE)
@Operation(summary = "关闭/撤销易支付订单")
@Operation(summary = "关闭易支付订单")
@PostMapping("/close")
public Result<Void> close(
@NotNull(message = "{validation.field.id.notNull}") Long id,
@RequestParam(defaultValue = "false") boolean useCancel) {
easyPayOrderAdminQueryService.close(id, useCancel);
@NotNull(message = "{validation.field.id.notNull}") Long id) {
easyPayOrderAdminQueryService.close(id);
return Res.ok();
}
}

View File

@@ -18,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/// # 易支付协议订单(商户端)
@@ -62,9 +61,8 @@ public class EasyPayOrderMchController {
@Operation(summary = "关闭/撤销易支付订单")
@PostMapping("/close")
public Result<Void> close(
@NotNull(message = "{validation.field.id.notNull}") Long id,
@RequestParam(defaultValue = "false") boolean useCancel) {
easyPayOrderMchQueryService.close(id, useCancel);
@NotNull(message = "{validation.field.id.notNull}") Long id) {
easyPayOrderMchQueryService.close(id);
return Res.ok();
}
}

View File

@@ -73,8 +73,8 @@ public class EasyPayOrderAdminQueryService {
return paySyncService.syncPayOrder(trade);
}
/// 关闭/撤销订单(直达内核资金凭证关单)
public void close(Long id, boolean useCancel) {
/// 关闭订单(直达内核资金凭证关单)
public void close(Long id) {
EasyPayOrder entity = easyPayOrderManager.findById(id)
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
if (entity.getOrderId() == null) {
@@ -82,6 +82,6 @@ public class EasyPayOrderAdminQueryService {
}
PayTrade trade = payTradeManager.findByContainerId(entity.getOrderId(), PayTradeTypeEnum.NORMAL.getCode())
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
payCloseService.closeOrder(trade, useCancel);
payCloseService.closeOrder(trade, false);
}
}

View File

@@ -71,8 +71,8 @@ public class EasyPayOrderMchQueryService {
return paySyncService.syncPayOrder(trade);
}
/// 关闭/撤销订单(直达内核资金凭证关单)
public void close(Long id, boolean useCancel) {
/// 关闭订单(直达内核资金凭证关单)
public void close(Long id) {
EasyPayOrder entity = easyPayOrderManager.findById(id)
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
if (entity.getOrderId() == null) {
@@ -80,7 +80,7 @@ public class EasyPayOrderMchQueryService {
}
PayTrade trade = payTradeManager.findByContainerId(entity.getOrderId(), PayTradeTypeEnum.NORMAL.getCode())
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
payCloseService.closeOrder(trade, useCancel);
payCloseService.closeOrder(trade, false);
}
/// 解析并强制写入当前商户号