更新多个功能
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
package cn.novalon.gym.manage.payment.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付记录响应DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "支付记录")
|
||||
public class PaymentRecordResponse {
|
||||
|
||||
@Schema(description = "订单ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "用户编号(会员ID)")
|
||||
private Long memberId;
|
||||
|
||||
@Schema(description = "支付方式(ALIPAY/WECHAT)")
|
||||
private String tradeType;
|
||||
|
||||
@Schema(description = "购买内容(商品描述)")
|
||||
private String goodsDesc;
|
||||
|
||||
@Schema(description = "订单类型(MEMBER_CARD/GROUP_COURSE/GOODS)")
|
||||
private String orderType;
|
||||
|
||||
@Schema(description = "支付金额(元)")
|
||||
private BigDecimal transAmt;
|
||||
|
||||
@Schema(description = "支付状态(PENDING/SUCCESS/FAIL/CLOSED)")
|
||||
private String payStatus;
|
||||
|
||||
@Schema(description = "支付时间")
|
||||
private String payTime;
|
||||
|
||||
@Schema(description = "汇付流水号")
|
||||
private String hfSeqId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private String createdAt;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cn.novalon.gym.manage.payment.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 营业额统计数据
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "营业额统计数据")
|
||||
public class RevenueStatistics {
|
||||
|
||||
@Schema(description = "今日收入(元)")
|
||||
private BigDecimal todayIncome;
|
||||
|
||||
@Schema(description = "今日退款(元)")
|
||||
private BigDecimal todayRefund;
|
||||
|
||||
@Schema(description = "今日订单数")
|
||||
private Long todayOrderCount;
|
||||
|
||||
@Schema(description = "当月收入(元)")
|
||||
private BigDecimal monthIncome;
|
||||
|
||||
@Schema(description = "当月退款(元)")
|
||||
private BigDecimal monthRefund;
|
||||
|
||||
@Schema(description = "当月订单数")
|
||||
private Long monthOrderCount;
|
||||
|
||||
@Schema(description = "当年收入(元)")
|
||||
private BigDecimal yearIncome;
|
||||
|
||||
@Schema(description = "当年退款(元)")
|
||||
private BigDecimal yearRefund;
|
||||
|
||||
@Schema(description = "当年订单数")
|
||||
private Long yearOrderCount;
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.novalon.gym.manage.payment.handler;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.ApiResponse;
|
||||
import cn.novalon.gym.manage.payment.service.PaymentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 支付营业数据 Handler
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-06-29
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Tag(name = "支付营业数据", description = "营业额统计与支付记录查询")
|
||||
public class PaymentRevenueHandler {
|
||||
|
||||
private final PaymentService paymentService;
|
||||
|
||||
public PaymentRevenueHandler(PaymentService paymentService) {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取营业额统计", description = "获取今日、当月、当年的收入和退款情况")
|
||||
public Mono<ServerResponse> getRevenueStatistics(ServerRequest request) {
|
||||
log.info("[Revenue] 查询营业额统计");
|
||||
|
||||
return paymentService.getRevenueStatistics()
|
||||
.flatMap(stats -> ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success(stats)))
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Revenue] 查询营业额统计失败", e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("查询营业额统计失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "查询支付记录", description = "分页查询用户支付记录,支持按会员ID、支付状态、支付方式筛选")
|
||||
public Mono<ServerResponse> getPaymentRecords(ServerRequest request) {
|
||||
String memberIdStr = request.queryParam("memberId").orElse(null);
|
||||
Long memberId = null;
|
||||
if (memberIdStr != null && !memberIdStr.isEmpty()) {
|
||||
try {
|
||||
memberId = Long.parseLong(memberIdStr);
|
||||
} catch (NumberFormatException e) {
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("会员ID格式不正确"));
|
||||
}
|
||||
}
|
||||
|
||||
String payStatus = request.queryParam("payStatus").orElse(null);
|
||||
String tradeType = request.queryParam("tradeType").orElse(null);
|
||||
int page = Integer.parseInt(request.queryParam("page").orElse("1"));
|
||||
int pageSize = Integer.parseInt(request.queryParam("pageSize").orElse("20"));
|
||||
|
||||
log.info("[Revenue] 查询支付记录: memberId={}, payStatus={}, tradeType={}, page={}, pageSize={}",
|
||||
memberId, payStatus, tradeType, page, pageSize);
|
||||
|
||||
return paymentService.getPaymentRecords(memberId, payStatus, tradeType, page, pageSize)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.success(result)))
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Revenue] 查询支付记录失败", e);
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(ApiResponse.error("查询支付记录失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
}
|
||||
+21
@@ -8,6 +8,7 @@ import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
@@ -38,4 +39,24 @@ public interface PaymentOrderRepository extends R2dbcRepository<PaymentOrder, Lo
|
||||
Flux<PaymentOrder> findAllByDeletedAtIsNull();
|
||||
|
||||
Flux<PaymentOrder> findByMemberIdAndDeletedAtIsNull(Long memberId);
|
||||
|
||||
// ===== 营业额统计 =====
|
||||
|
||||
/** 统计指定时间范围内成功支付的金额总和 */
|
||||
@Query("SELECT COALESCE(SUM(trans_amt), 0) FROM payment_order WHERE pay_status = 'SUCCESS' AND pay_time >= :startTime AND pay_time < :endTime AND deleted_at IS NULL")
|
||||
Mono<BigDecimal> sumSuccessAmount(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
/** 统计指定时间范围内的成功订单数 */
|
||||
@Query("SELECT COUNT(*) FROM payment_order WHERE pay_status = 'SUCCESS' AND pay_time >= :startTime AND pay_time < :endTime AND deleted_at IS NULL")
|
||||
Mono<Long> countSuccessOrders(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
// ===== 支付记录分页查询 =====
|
||||
|
||||
/** 按条件分页查询支付记录 */
|
||||
@Query("SELECT * FROM payment_order WHERE (:memberId IS NULL OR member_id = :memberId) AND (:payStatus IS NULL OR pay_status = :payStatus) AND (:tradeType IS NULL OR trade_type = :tradeType) AND deleted_at IS NULL ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
|
||||
Flux<PaymentOrder> findPaymentRecords(Long memberId, String payStatus, String tradeType, int limit, int offset);
|
||||
|
||||
/** 按条件统计支付记录总数 */
|
||||
@Query("SELECT COUNT(*) FROM payment_order WHERE (:memberId IS NULL OR member_id = :memberId) AND (:payStatus IS NULL OR pay_status = :payStatus) AND (:tradeType IS NULL OR trade_type = :tradeType) AND deleted_at IS NULL")
|
||||
Mono<Long> countPaymentRecords(Long memberId, String payStatus, String tradeType);
|
||||
}
|
||||
|
||||
+8
@@ -1,7 +1,9 @@
|
||||
package cn.novalon.gym.manage.payment.service;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentRecordResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.RevenueStatistics;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -30,4 +32,10 @@ public interface PaymentService {
|
||||
Mono<PaymentResponse> getPendingOrder(Long memberId, String orderType);
|
||||
|
||||
Mono<Boolean> closeOrder(Long memberId, String orderId);
|
||||
|
||||
/** 获取营业额统计(今日/当月/当年) */
|
||||
Mono<RevenueStatistics> getRevenueStatistics();
|
||||
|
||||
/** 分页查询支付记录 */
|
||||
Mono<Map<String, Object>> getPaymentRecords(Long memberId, String payStatus, String tradeType, int page, int pageSize);
|
||||
}
|
||||
+74
@@ -3,7 +3,9 @@ package cn.novalon.gym.manage.payment.service.impl;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.payment.config.HuifuProperties;
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentRecordResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.RevenueStatistics;
|
||||
import cn.novalon.gym.manage.payment.entity.PaymentOrder;
|
||||
import cn.novalon.gym.manage.payment.repository.PaymentOrderRepository;
|
||||
import cn.novalon.gym.manage.payment.service.PaymentNotifyService;
|
||||
@@ -934,4 +936,76 @@ public class PaymentServiceImpl implements PaymentService {
|
||||
})
|
||||
.switchIfEmpty(Mono.just(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RevenueStatistics> getRevenueStatistics() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 今日
|
||||
LocalDateTime todayStart = now.toLocalDate().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
// 当月
|
||||
LocalDateTime monthStart = now.toLocalDate().withDayOfMonth(1).atStartOfDay();
|
||||
LocalDateTime monthEnd = monthStart.plusMonths(1);
|
||||
// 当年
|
||||
LocalDateTime yearStart = now.toLocalDate().withDayOfYear(1).atStartOfDay();
|
||||
LocalDateTime yearEnd = yearStart.plusYears(1);
|
||||
|
||||
Mono<BigDecimal> todayIncomeMono = paymentOrderRepository.sumSuccessAmount(todayStart, todayEnd);
|
||||
Mono<Long> todayCountMono = paymentOrderRepository.countSuccessOrders(todayStart, todayEnd);
|
||||
Mono<BigDecimal> monthIncomeMono = paymentOrderRepository.sumSuccessAmount(monthStart, monthEnd);
|
||||
Mono<Long> monthCountMono = paymentOrderRepository.countSuccessOrders(monthStart, monthEnd);
|
||||
Mono<BigDecimal> yearIncomeMono = paymentOrderRepository.sumSuccessAmount(yearStart, yearEnd);
|
||||
Mono<Long> yearCountMono = paymentOrderRepository.countSuccessOrders(yearStart, yearEnd);
|
||||
|
||||
return Mono.zip(todayIncomeMono, todayCountMono, monthIncomeMono, monthCountMono, yearIncomeMono, yearCountMono)
|
||||
.map(tuple -> RevenueStatistics.builder()
|
||||
.todayIncome(tuple.getT1() != null ? tuple.getT1() : BigDecimal.ZERO)
|
||||
.todayRefund(BigDecimal.ZERO) // 退款功能暂未实现
|
||||
.todayOrderCount(tuple.getT2() != null ? tuple.getT2() : 0L)
|
||||
.monthIncome(tuple.getT3() != null ? tuple.getT3() : BigDecimal.ZERO)
|
||||
.monthRefund(BigDecimal.ZERO) // 退款功能暂未实现
|
||||
.monthOrderCount(tuple.getT4() != null ? tuple.getT4() : 0L)
|
||||
.yearIncome(tuple.getT5() != null ? tuple.getT5() : BigDecimal.ZERO)
|
||||
.yearRefund(BigDecimal.ZERO) // 退款功能暂未实现
|
||||
.yearOrderCount(tuple.getT6() != null ? tuple.getT6() : 0L)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> getPaymentRecords(Long memberId, String payStatus, String tradeType, int page, int pageSize) {
|
||||
int offset = (page - 1) * pageSize;
|
||||
|
||||
Mono<List<PaymentRecordResponse>> recordsMono = paymentOrderRepository
|
||||
.findPaymentRecords(memberId, payStatus, tradeType, pageSize, offset)
|
||||
.map(order -> PaymentRecordResponse.builder()
|
||||
.id(order.getId())
|
||||
.orderNo(order.getOrderNo())
|
||||
.memberId(order.getMemberId())
|
||||
.tradeType(order.getTradeType())
|
||||
.goodsDesc(order.getGoodsDesc())
|
||||
.orderType(order.getOrderType())
|
||||
.transAmt(order.getTransAmt())
|
||||
.payStatus(order.getPayStatus())
|
||||
.payTime(order.getPayTime() != null
|
||||
? order.getPayTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
: null)
|
||||
.hfSeqId(order.getHfSeqId())
|
||||
.createdAt(order.getCreatedAt() != null
|
||||
? order.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
: null)
|
||||
.build())
|
||||
.collectList();
|
||||
|
||||
Mono<Long> totalMono = paymentOrderRepository.countPaymentRecords(memberId, payStatus, tradeType);
|
||||
|
||||
return Mono.zip(recordsMono, totalMono)
|
||||
.map(tuple -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("records", tuple.getT1());
|
||||
result.put("total", tuple.getT2());
|
||||
result.put("page", page);
|
||||
result.put("pageSize", pageSize);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user