feat 驾驶舱接口

This commit is contained in:
xxm1995
2024-03-17 19:39:21 +08:00
parent 7cead5ab4e
commit f5a9bf72aa
10 changed files with 458 additions and 19 deletions

View File

@@ -163,24 +163,9 @@ public class SimplePayOrderTest {
## 🛣️ 路线图
> 当前处于功能开发阶段,部分功能可能会有调整,`V2.1.0`时将作为正式生产可用版本进行发布之后会保证系统版本非大版本升级时API接口和数据接口向前兼容
[**开发进度和任务池**](/_doc/Task.md)
[**更新记录**](/_doc/ChangeLog.md)
### 2.0.X版本:
- [x] 对账比对功能实现
- [ ] 支持转账、分账操作
- [ ] 云闪付支付支持
- [ ] 支付宝和微信增加V3版本接口支持
- [ ] 消息通知支持消息中间件模式
### 2.1.X版本:
- [ ] 增加账户金额表
- [ ] 增加统计管理
- [ ] 支持微信消息通知
- [ ] 支持钉钉消息通知
- [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况
[**当前开发进度和任务池**](/_doc/Task.md)
[**历史更新记录**](/_doc/ChangeLog.md)
## 🥂 Bootx 项目合集
- Bootx-Platform单体版脚手架 [Gitee地址](https://gitee.com/bootx/bootx-platform)
@@ -197,7 +182,7 @@ QQ扫码加入QQ交流群
微信扫码加入微信交流群
<p>
<img alt="微信图片_20240226144703" height="500" src="https://jsd.cdn.zzko.cn/gh/xxm1995/picx-images-hosting@master/connect/微信图片_20240310224719.4jnuoluofy.webp" width="330"/>
<img alt="微信图片_20240226144703" height="500" src="https://jsd.cdn.zzko.cn/gh/xxm1995/picx-images-hosting@master/connect/7ea3e91d78f07dad16a480f5f48f74e.70a3dbwew7.webp" width="330"/>
</p>
## 🍻 鸣谢

View File

@@ -1,10 +1,15 @@
2.0.4:
- [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
- [ ] 首页驾驶舱功能: 各通道收入和支付情况
- [x] 第一排: (数字格式)显示今日收入、支出金额,支付总订单数量、退款总订单数, 时间分支分为: 今日金额/昨日金额/七天内金额
- [x] 第二排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
- [x] 第三排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
- [ ] 报表功能
- [ ] 各通道收入和支付情况
- [ ] 微信新增V3版本接口
- [ ] 增加转账功能
- [ ] 云闪付支持对账功能
- [ ] 结算台DEMO增加云闪付示例
2.0.x 版本内容
- [ ] 统一关闭接口增加使用撤销关闭订单

View File

@@ -0,0 +1,75 @@
package cn.bootx.platform.daxpay.admin.controller.report;
import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.service.core.report.service.CockpitReportService;
import cn.bootx.platform.daxpay.service.dto.report.ChannelLineReport;
import cn.bootx.platform.daxpay.service.param.report.CockpitReportQuery;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 驾驶舱接口
* @author xxm
* @since 2024/3/17
*/
@IgnoreAuth// TODO 测试用, 后续删除
@Tag(name = "驾驶舱接口")
@RestController
@RequestMapping("/report/cockpit")
@RequiredArgsConstructor
public class CockpitReportController {
private final CockpitReportService cockpitReportService;
@Operation(summary = "支付金额(分)")
@GetMapping("/getPayAmount")
public ResResult<Integer> getPayAmount(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getPayAmount(query));
}
@Operation(summary = "退款金额(分)")
@GetMapping("/getRefundAmount")
public ResResult<Integer> getRefundAmount(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getRefundAmount(query));
}
@Operation(summary = "支付订单数量")
@GetMapping("/getPayOrderCount")
public ResResult<Integer> getPayOrderCount(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getPayOrderCount(query));
}
@Operation(summary = "退款订单数量")
@GetMapping("/getRefundOrderCount")
public ResResult<Integer> getRefundOrderCount(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getRefundOrderCount(query));
}
@Operation(summary = "支付通道折线图")
@GetMapping("/getPayChannelLine")
public ResResult<List<ChannelLineReport>> getPayChannelLine(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getPayChannelLine(query));
}
@Operation(summary = "退款通道折线图")
@GetMapping("/getRefundChannelLine")
public ResResult<List<ChannelLineReport>> getRefundChannelLine(@ParameterObject CockpitReportQuery query){
ValidationUtil.validateParam(query);
return Res.ok(cockpitReportService.getRefundChannelLine(query));
}
}

View File

@@ -0,0 +1,48 @@
package cn.bootx.platform.daxpay.service.core.report.dao;
import cn.bootx.platform.daxpay.service.core.report.entity.ChannelOrderLine;
import cn.bootx.platform.daxpay.service.param.report.CockpitReportQuery;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 查询报表
* @author xxm
* @since 2024/3/17
*/
@Mapper
public interface CockpitReportMapper {
/**
* 获取支付金额
*/
Integer getPayAmount(CockpitReportQuery query);
/**
* 获取退款金额
*/
Integer getRefundAmount(CockpitReportQuery query);
/**
* 获取支付订单数量
*/
Integer getPayOrderCount(CockpitReportQuery query);
/**
* 获取退款订单数量
*/
Integer getRefundOrderCount(CockpitReportQuery query);
/**
* 支付通道订单折线图
*/
List<ChannelOrderLine> getPayChannelLine(CockpitReportQuery query);
/**
* 退款通道订单折线图
*/
List<ChannelOrderLine> getRefundChannelLine(CockpitReportQuery query);
}

View File

@@ -0,0 +1,25 @@
package cn.bootx.platform.daxpay.service.core.report.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 通道订单折线图
* @author xxm
* @since 2024/3/17
*/
@Data
@Accessors(chain = true)
@Schema(title = "通道订单折线图")
public class ChannelOrderLine {
@Schema(description = "通道编码")
private String channel;
@Schema(description = "订单金额")
private Integer count;
@Schema(description = "订单数量")
private Integer sum;
}

View File

@@ -0,0 +1,29 @@
package cn.bootx.platform.daxpay.service.core.report.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付通道占比饼图
* @author xxm
* @since 2024/3/17
*/
@Data
@Accessors(chain = true)
@Schema(title = "支付通道占比饼图")
public class ChannelProportionPie {
@Schema(description = "通道编码")
private String code;
@Schema(description = "通道名称")
private String name;
@Schema(description = "占比")
private Double proportion;
@Schema(description = "数量")
private Integer count;
}

View File

@@ -0,0 +1,108 @@
package cn.bootx.platform.daxpay.service.core.report.service;
import cn.bootx.platform.common.core.function.CollectorsFunction;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.core.report.dao.CockpitReportMapper;
import cn.bootx.platform.daxpay.service.core.report.entity.ChannelOrderLine;
import cn.bootx.platform.daxpay.service.dto.report.ChannelLineReport;
import cn.bootx.platform.daxpay.service.param.report.CockpitReportQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 驾驶舱接口
* @author xxm
* @since 2024/3/15
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CockpitReportService {
private final CockpitReportMapper cockpitReportMapper;
/**
* 支付金额(分)
*/
public Integer getPayAmount(CockpitReportQuery query){
// 获取支付成功的订单
return cockpitReportMapper.getPayAmount(query);
}
/**
* 退款金额(分)
*/
public Integer getRefundAmount(CockpitReportQuery query){
return cockpitReportMapper.getRefundAmount(query);
}
/**
* 支付订单数量
*/
public Integer getPayOrderCount(CockpitReportQuery query){
return cockpitReportMapper.getPayOrderCount(query);
}
/**
* 退款订单数量
*/
public Integer getRefundOrderCount(CockpitReportQuery query){
return cockpitReportMapper.getRefundOrderCount(query);
}
/**
* (折线图)显示各通道支付分为支付金额和订单数,
*/
public List<ChannelLineReport> getPayChannelLine(CockpitReportQuery query){
Map<String, ChannelOrderLine> lineMap = cockpitReportMapper.getPayChannelLine(query)
.stream()
.collect(Collectors.toMap(ChannelOrderLine::getChannel, Function.identity(), CollectorsFunction::retainLatest));
// 根据系统中有的通道编码,获取对应的通道名称
return Arrays.stream(PayChannelEnum.values())
.map(e->{
ChannelOrderLine channelOrderLine = lineMap.get(e.getCode());
ChannelLineReport channelLineReport = new ChannelLineReport()
.setChannelCode(e.getCode())
.setChannelName(e.getName());
if (Objects.isNull(channelOrderLine)){
channelLineReport.setOrderAmount(0).setOrderCount(0);
} else {
channelLineReport.setOrderAmount(channelOrderLine.getCount())
.setOrderCount(channelOrderLine.getSum());
}
return channelLineReport;
}).collect(Collectors.toList());
}
/**
* (折线图)显示各通道退款金额和订单数,
*/
public List<ChannelLineReport> getRefundChannelLine(CockpitReportQuery query){
Map<String, ChannelOrderLine> lineMap = cockpitReportMapper.getRefundChannelLine(query)
.stream()
.collect(Collectors.toMap(ChannelOrderLine::getChannel, Function.identity(), CollectorsFunction::retainLatest));
// 根据系统中有的通道编码,获取对应的通道名称
return Arrays.stream(PayChannelEnum.values())
.map(e->{
ChannelOrderLine channelOrderLine = lineMap.get(e.getCode());
ChannelLineReport channelLineReport = new ChannelLineReport()
.setChannelCode(e.getCode())
.setChannelName(e.getName());
if (Objects.isNull(channelOrderLine)){
channelLineReport.setOrderAmount(0).setOrderCount(0);
} else {
channelLineReport.setOrderAmount(channelOrderLine.getCount())
.setOrderCount(channelOrderLine.getSum());
}
return channelLineReport;
}).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,29 @@
package cn.bootx.platform.daxpay.service.dto.report;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付通道折线图报表
* @author xxm
* @since 2024/3/17
*/
@Data
@Accessors(chain = true)
@Schema(title = "支付通道折线图报表")
public class ChannelLineReport {
@Schema(description = "支付通道编码")
private String channelCode;
@Schema(description = "支付通道名称")
private String channelName;
@Schema(description = "订单金额")
private Integer orderAmount;
@Schema(description = "订单数量")
private Integer orderCount;
}

View File

@@ -0,0 +1,35 @@
package cn.bootx.platform.daxpay.service.param.report;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 驾驶舱报表查询参数
* @author xxm
* @since 2024/3/15
*/
@Data
@Accessors(chain = true)
@Schema(title = "驾驶舱报表查询参数")
public class CockpitReportQuery {
@NotNull(message = "开始时间不得为空")
@Schema(description = "开始时间")
@JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private LocalDateTime startTime;
@NotNull(message = "开始时间不得为空")
@Schema(description = "开始时间")
@JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private LocalDateTime endTime;
}

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bootx.platform.daxpay.service.core.report.dao.CockpitReportMapper">
<!-- 查询支付金额 -->
<select id="getPayAmount" resultType="java.lang.Integer">
SELECT
sum(amount)
FROM
pay_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS IN ( 'success', 'refunding', 'partial_refund', 'refunded' );
</select>
<!-- 查询退款金额 -->
<select id="getRefundAmount" resultType="java.lang.Integer">
SELECT
sum(amount)
FROM
pay_refund_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS = 'success';
</select>
<!-- 查询支付订单数量 -->
<select id="getPayOrderCount" resultType="java.lang.Integer">
SELECT
count(*)
FROM
pay_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS IN ( 'success', 'refunding', 'partial_refund', 'refunded' );
</select>
<!-- 查询退款订单数量 -->
<select id="getRefundOrderCount" resultType="java.lang.Integer">
SELECT
count(*)
FROM
pay_refund_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS = 'success';
</select>
<!-- 支付通道折线图 -->
<select id="getPayChannelLine" resultType="cn.bootx.platform.daxpay.service.core.report.entity.ChannelOrderLine">
SELECT
channel,
count(*) as count,
sum(amount) as sum
FROM
pay_channel_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS IN ( 'success', 'refunding', 'partial_refund', 'refunded' )
GROUP BY
channel
</select>
<!-- 退款通道折线图 -->
<select id="getRefundChannelLine" resultType="cn.bootx.platform.daxpay.service.core.report.entity.ChannelOrderLine">
SELECT
channel,
count(*) as count,
sum(amount) as sum
FROM
pay_refund_channel_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS = 'success'
GROUP BY
channel
</select>
<!-- 支付通道占比饼图 -->
<select id="getPayChannelPie" resultType="cn.bootx.platform.daxpay.service.core.report.entity.ChannelProportionPie">
SELECT
channel,
name,
count(*) as count,
sum(amount) as sum
FROM
pay_channel_order
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
AND STATUS IN ( 'success', 'refunding', 'partial_refund', 'refunded' )
GROUP BY
channel
</select>
<!-- -->
<select id="getRefundChannelPie" resultType="cn.bootx.platform.daxpay.service.core.report.entity.ChannelProportionPie">
</select>
</mapper>