用户订单金额及数量统计

This commit is contained in:
yanglei 2026-01-16 15:37:45 +08:00
parent fd05bd94ca
commit fbb194b437
12 changed files with 490 additions and 13 deletions

View File

@ -3,9 +3,13 @@ package com.ebike.feign.clients;
import com.cdzy.common.model.response.JsonResult; import com.cdzy.common.model.response.JsonResult;
import com.ebike.feign.component.FeignTokenInterceptor; import com.ebike.feign.component.FeignTokenInterceptor;
import com.ebike.feign.config.ExampleFeignConfiguration; import com.ebike.feign.config.ExampleFeignConfiguration;
import com.ebike.feign.model.dto.FeignEbikeOrderStatisticsDto;
import com.ebike.feign.model.vo.FeignEbikeRefundOrderDetailVo; import com.ebike.feign.model.vo.FeignEbikeRefundOrderDetailVo;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
/** /**
@ -25,4 +29,13 @@ public interface UserFeignClient {
*/ */
@GetMapping("/ebikeRefund/api/refundOrderDetail") @GetMapping("/ebikeRefund/api/refundOrderDetail")
JsonResult<FeignEbikeRefundOrderDetailVo> refundOrderDetail(@RequestParam("refundId") Long refundId); JsonResult<FeignEbikeRefundOrderDetailVo> refundOrderDetail(@RequestParam("refundId") Long refundId);
/**
* 查询不同运营商的订单数据及订单金额
*
* @param dto 订单请求参数
* @return 订单数据及订单金额
*/
@PostMapping("/ebikeOrder/api/getOrderStatistics")
JsonResult<?> getOrderStatistics(@RequestBody @Validated FeignEbikeOrderStatisticsDto dto);
} }

View File

@ -0,0 +1,31 @@
package com.ebike.feign.model.dto;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 订单统计请求参数
*
* @author yanglei
* @since 2026-01-16 11:06
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FeignEbikeOrderStatisticsDto {
/**
* 运营商id
*/
private Long operatorId;
/**
* 时间 1-今天 2-近7天 3-近30天
*/
@NotNull(message = "日期指标不能为空")
private Integer timeRange;
}

View File

@ -0,0 +1,44 @@
package com.ebike.feign.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @author yanglei
* @since 2026-01-16 11:23
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FeignEbikeOrderStatisticsVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 运营商id
*/
private Long operatorId;
/**
* 订单数量
*/
private Integer orderCount;
/**
* 订单金额
*/
private BigDecimal orderAmount;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,46 @@
package com.cdzy.operations.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.cdzy.common.enums.Code;
import com.cdzy.common.ex.EbikeException;
import com.cdzy.common.model.response.CommonStaffInfo;
import com.cdzy.common.model.response.JsonResult;
import com.ebike.feign.clients.UserFeignClient;
import com.ebike.feign.model.dto.FeignEbikeOrderStatisticsDto;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
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.RestController;
/**
* @author yanglei
* @since 2026-01-16 15:15
*/
@RestController
@RequestMapping("/statistics")
public class EbikeStatisticsController {
@Resource
private UserFeignClient userFeignClient;
/**
* 查询用户订单统计
*
* @param dto 参数参数
* @return 用户订单数据统计
*/
@PostMapping("/getOrderStatistics")
public JsonResult<?> getOrderStatistics(@RequestBody @Validated FeignEbikeOrderStatisticsDto dto) {
Long staffId = StpUtil.getLoginIdAsLong();
CommonStaffInfo staffInfo = StpUtil.getSession().getModel(staffId.toString(), CommonStaffInfo.class);
dto.setOperatorId(staffInfo.getOperatorId());
JsonResult<?> jsonResult = userFeignClient.getOrderStatistics(dto);
if (jsonResult.getCode() != Code.SUCCESS) {
throw new EbikeException(jsonResult.getMessage());
}
return JsonResult.success(jsonResult.getData());
}
}

View File

@ -199,17 +199,4 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
} }
/**
* 创建时间过滤条件
*
* @param duration 持续时间分钟
* @param operator 比较运算符
*/
private String createTimeFilter(int duration, String operator) {
return String.format("%s %s CURRENT_TIMESTAMP - INTERVAL '%d minutes'",
EBIKE_PAYMENT.CREATE_TIME.getName(),
operator,
duration);
}
} }

View File

@ -14,7 +14,9 @@ import com.cdzy.user.model.vo.EbikeUserAllOrdersVo;
import com.cdzy.user.service.EbikeOrderService; import com.cdzy.user.service.EbikeOrderService;
import com.ebike.feign.clients.PaymentFeignClient; import com.ebike.feign.clients.PaymentFeignClient;
import com.ebike.feign.model.dto.FeignEbikeDto; import com.ebike.feign.model.dto.FeignEbikeDto;
import com.ebike.feign.model.dto.FeignEbikeOrderStatisticsDto;
import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo;
import com.ebike.feign.model.vo.FeignEbikeOrderStatisticsVo;
import com.ebike.feign.model.vo.FeignEbikeWxHandleNotifyVo; import com.ebike.feign.model.vo.FeignEbikeWxHandleNotifyVo;
import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.paginate.Page;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -157,4 +159,16 @@ public class EbikeOrderController {
} }
return JsonResult.success(jsonResult.getData()); return JsonResult.success(jsonResult.getData());
} }
/**
* 查询不同运营商的订单数据及订单金额
*
* @param dto 订单请求参数
* @return 订单数据及订单金额
*/
@PostMapping("/api/getOrderStatistics")
public JsonResult<?> getOrderStatistics(@RequestBody @Validated FeignEbikeOrderStatisticsDto dto) {
List<FeignEbikeOrderStatisticsVo> result = ebikeOrderService.getOrderStatistics(dto);
return JsonResult.success(result);
}
} }

View File

@ -0,0 +1,42 @@
package com.cdzy.user.enums;
import com.cdzy.user.utils.DateUtils;
import java.time.LocalDateTime;
/**
* @author yanglei
* @since 2026-01-16 14:42
*/
public enum TimeRangeEnum {
TODAY(1),
SEVEN_DAYS_RANGE(2),
THIRTY_DAYS_RANGE(3);
private final int code;
TimeRangeEnum(int code) {
this.code = code;
}
public static TimeRangeEnum fromCode(Integer code) {
if (code == null) {
return null;
}
for (TimeRangeEnum range : values()) {
if (range.code == code) {
return range;
}
}
throw new IllegalArgumentException("Invalid timeRange code: " + code);
}
public LocalDateTime[] getRange() {
return switch (this) {
case TODAY -> DateUtils.getTodayRange();
case SEVEN_DAYS_RANGE -> DateUtils.getLast7DaysRange();
case THIRTY_DAYS_RANGE -> DateUtils.getLast30DaysRange();
};
}
}

View File

@ -2,8 +2,10 @@ package com.cdzy.user.mapper;
import com.cdzy.user.model.entity.EbikeOrder; import com.cdzy.user.model.entity.EbikeOrder;
import com.cdzy.user.model.vo.EbikeRevenueStatisticsVo; import com.cdzy.user.model.vo.EbikeRevenueStatisticsVo;
import com.ebike.feign.model.vo.FeignEbikeOrderStatisticsVo;
import com.mybatisflex.core.BaseMapper; import com.mybatisflex.core.BaseMapper;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
@ -21,4 +23,15 @@ public interface EbikeOrderMapper extends BaseMapper<EbikeOrder> {
* @return 营收统计 * @return 营收统计
*/ */
List<EbikeRevenueStatisticsVo> selectRevenueComparison(); List<EbikeRevenueStatisticsVo> selectRevenueComparison();
/**
* 查询不同运营商的订单数据及订单金额
*
* @param operatorId 运营商id
* @param startTime 开始时间
* @param endTime 结束时间
* @return 订单数据及订单金额
*/
List<FeignEbikeOrderStatisticsVo> getOrderStatistics(Long operatorId, LocalDateTime startTime, LocalDateTime endTime);
} }

View File

@ -9,7 +9,9 @@ import com.cdzy.user.model.vo.EbikeOrderDetailVo;
import com.cdzy.user.model.vo.EbikeRevenueStatisticsVo; import com.cdzy.user.model.vo.EbikeRevenueStatisticsVo;
import com.cdzy.user.model.vo.EbikeUserAllOrdersVo; import com.cdzy.user.model.vo.EbikeUserAllOrdersVo;
import com.ebike.feign.model.dto.FeignEbikeDto; import com.ebike.feign.model.dto.FeignEbikeDto;
import com.ebike.feign.model.dto.FeignEbikeOrderStatisticsDto;
import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo;
import com.ebike.feign.model.vo.FeignEbikeOrderStatisticsVo;
import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService; import com.mybatisflex.core.service.IService;
@ -113,4 +115,13 @@ public interface EbikeOrderService extends IService<EbikeOrder> {
* @return 订单详情 * @return 订单详情
*/ */
EbikeOrderDetailVo getInfo(Long orderId); EbikeOrderDetailVo getInfo(Long orderId);
/**
* 查询不同运营商的订单数据及订单金额
*
* @param dto 订单请求参数
* @return 订单数据及订单金额
*/
List<FeignEbikeOrderStatisticsVo> getOrderStatistics(FeignEbikeOrderStatisticsDto dto);
} }

View File

@ -10,6 +10,7 @@ import com.cdzy.user.enums.EbikePaymentMethod;
import com.cdzy.user.enums.EbikePaymentTradeStatus; import com.cdzy.user.enums.EbikePaymentTradeStatus;
import com.cdzy.user.enums.OrderStatus; import com.cdzy.user.enums.OrderStatus;
import com.cdzy.user.enums.OrderType; import com.cdzy.user.enums.OrderType;
import com.cdzy.user.enums.TimeRangeEnum;
import com.cdzy.user.mapper.EbikeOrderMapper; import com.cdzy.user.mapper.EbikeOrderMapper;
import com.cdzy.user.model.dto.EbikeCostDetailDto; import com.cdzy.user.model.dto.EbikeCostDetailDto;
import com.cdzy.user.model.dto.EbikeUserCyclingDto; import com.cdzy.user.model.dto.EbikeUserCyclingDto;
@ -29,10 +30,12 @@ import com.cdzy.user.service.EbikeUserRealInfoService;
import com.cdzy.user.utils.StringUtils; import com.cdzy.user.utils.StringUtils;
import com.ebike.feign.clients.OperationsFeignClient; import com.ebike.feign.clients.OperationsFeignClient;
import com.ebike.feign.model.dto.FeignEbikeDto; import com.ebike.feign.model.dto.FeignEbikeDto;
import com.ebike.feign.model.dto.FeignEbikeOrderStatisticsDto;
import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo; import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo;
import com.ebike.feign.model.dto.FeignEbikeUserLockDto; import com.ebike.feign.model.dto.FeignEbikeUserLockDto;
import com.ebike.feign.model.vo.EbikeLockVo; import com.ebike.feign.model.vo.EbikeLockVo;
import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo;
import com.ebike.feign.model.vo.FeignEbikeOrderStatisticsVo;
import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl; import com.mybatisflex.spring.service.impl.ServiceImpl;
@ -352,6 +355,17 @@ public class EbikeOrderServiceImpl extends ServiceImpl<EbikeOrderMapper, EbikeOr
return ebikeOrderDetailVo; return ebikeOrderDetailVo;
} }
@Override
public List<FeignEbikeOrderStatisticsVo> getOrderStatistics(FeignEbikeOrderStatisticsDto dto) {
TimeRangeEnum timeRange = TimeRangeEnum.fromCode(dto.getTimeRange());
LocalDateTime[] range = timeRange.getRange();
return this.mapper.getOrderStatistics(
dto.getOperatorId(),
range[0],
range[1]
);
}
/** /**
* 计算增长率并格式化为百分比字符串 * 计算增长率并格式化为百分比字符串
* *

View File

@ -0,0 +1,241 @@
package com.cdzy.user.utils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
/**
* 时间工具类
*
* @author yanglei
* @since 2026-01-16 14:29
*/
public class DateUtils {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 获取今天的开始时间00:00:00
*/
public static LocalDateTime getTodayStart() {
return LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
}
/**
* 获取今天的结束时间23:59:59
*/
public static LocalDateTime getTodayEnd() {
return LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
}
/**
* 获取今天的时间范围
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getTodayRange() {
return new LocalDateTime[]{getTodayStart(), getTodayEnd()};
}
/**
* 获取近7天的时间范围包括今天
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getLast7DaysRange() {
LocalDateTime end = getTodayEnd();
LocalDateTime start = getTodayStart().minusDays(6);
return new LocalDateTime[]{start, end};
}
/**
* 获取近30天的时间范围包括今天
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getLast30DaysRange() {
LocalDateTime end = getTodayEnd();
LocalDateTime start = getTodayStart().minusDays(29);
return new LocalDateTime[]{start, end};
}
/**
* 获取指定天数前的时间范围
* @param days 天数
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getLastDaysRange(int days) {
LocalDateTime end = getTodayEnd();
LocalDateTime start = getTodayStart().minusDays(days - 1);
return new LocalDateTime[]{start, end};
}
/**
* 获取本周的时间范围
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getThisWeekRange() {
LocalDate today = LocalDate.now();
LocalDate startOfWeek = today.with(java.time.DayOfWeek.MONDAY);
LocalDate endOfWeek = today.with(java.time.DayOfWeek.SUNDAY);
LocalDateTime start = LocalDateTime.of(startOfWeek, LocalTime.MIN);
LocalDateTime end = LocalDateTime.of(endOfWeek, LocalTime.MAX);
return new LocalDateTime[]{start, end};
}
/**
* 获取本月的时间范围
* @return 包含开始时间和结束时间的数组 [startTime, endTime]
*/
public static LocalDateTime[] getThisMonthRange() {
LocalDate today = LocalDate.now();
LocalDate startOfMonth = today.withDayOfMonth(1);
LocalDate endOfMonth = today.withDayOfMonth(today.lengthOfMonth());
LocalDateTime start = LocalDateTime.of(startOfMonth, LocalTime.MIN);
LocalDateTime end = LocalDateTime.of(endOfMonth, LocalTime.MAX);
return new LocalDateTime[]{start, end};
}
/**
* 获取今天日期字符串yyyy-MM-dd
*/
public static String getTodayDateString() {
return LocalDate.now().format(DATE_FORMATTER);
}
/**
* 获取现在日期时间字符串yyyy-MM-dd HH:mm:ss
*/
public static String getNowDateTimeString() {
return LocalDateTime.now().format(DATETIME_FORMATTER);
}
/**
* 获取近7天每天的日期列表包括今天
* @return 日期字符串列表格式yyyy-MM-dd
*/
public static List<String> getLast7DaysDateList() {
return getDateList(7);
}
/**
* 获取近30天每天的日期列表包括今天
* @return 日期字符串列表格式yyyy-MM-dd
*/
public static List<String> getLast30DaysDateList() {
return getDateList(30);
}
/**
* 获取指定天数的日期列表
* @param days 天数
* @return 日期字符串列表格式yyyy-MM-dd
*/
public static List<String> getDateList(int days) {
List<String> dateList = new ArrayList<>();
LocalDate today = LocalDate.now();
for (int i = days - 1; i >= 0; i--) {
LocalDate date = today.minusDays(i);
dateList.add(date.format(DATE_FORMATTER));
}
return dateList;
}
/**
* 获取两个日期之间的所有日期列表
* @param startDate 开始日期
* @param endDate 结束日期
* @return 日期字符串列表格式yyyy-MM-dd
*/
public static List<String> getDateRangeList(LocalDate startDate, LocalDate endDate) {
List<String> dateList = new ArrayList<>();
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
for (long i = 0; i <= daysBetween; i++) {
LocalDate date = startDate.plusDays(i);
dateList.add(date.format(DATE_FORMATTER));
}
return dateList;
}
/**
* 格式化LocalDateTime为字符串
* @param dateTime 时间
* @return 格式化后的字符串 yyyy-MM-dd HH:mm:ss
*/
public static String formatDateTime(LocalDateTime dateTime) {
return dateTime != null ? dateTime.format(DATETIME_FORMATTER) : null;
}
/**
* 格式化LocalDate为字符串
* @param date 日期
* @return 格式化后的字符串 yyyy-MM-dd
*/
public static String formatDate(LocalDate date) {
return date != null ? date.format(DATE_FORMATTER) : null;
}
/**
* 字符串转LocalDateTime
* @param dateTimeStr 时间字符串 yyyy-MM-dd HH:mm:ss
*/
public static LocalDateTime parseDateTime(String dateTimeStr) {
return LocalDateTime.parse(dateTimeStr, DATETIME_FORMATTER);
}
/**
* 字符串转LocalDate
* @param dateStr 日期字符串 yyyy-MM-dd
*/
public static LocalDate parseDate(String dateStr) {
return LocalDate.parse(dateStr, DATE_FORMATTER);
}
/**
* 获取指定日期的开始时间
* @param date 日期
* @return 当天的开始时间
*/
public static LocalDateTime getStartOfDay(LocalDate date) {
return LocalDateTime.of(date, LocalTime.MIN);
}
/**
* 获取指定日期的结束时间
* @param date 日期
* @return 当天的结束时间
*/
public static LocalDateTime getEndOfDay(LocalDate date) {
return LocalDateTime.of(date, LocalTime.MAX);
}
/**
* 获取指定日期的前n天
* @param date 基准日期
* @param days 天数
* @return 前n天的日期
*/
public static LocalDate getDaysBefore(LocalDate date, int days) {
return date.minusDays(days);
}
/**
* 获取指定日期的后n天
* @param date 基准日期
* @param days 天数
* @return 后n天的日期
*/
public static LocalDate getDaysAfter(LocalDate date, int days) {
return date.plusDays(days);
}
}

View File

@ -55,4 +55,25 @@
ORDER BY operator_id; ORDER BY operator_id;
</select> </select>
<select id="getOrderStatistics" resultType="com.ebike.feign.model.vo.FeignEbikeOrderStatisticsVo">
SELECT
operator_id AS operatorId,
create_time::date::timestamp AS createTime,
COUNT(*) AS orderCount,
COALESCE(SUM(total_amount), 0) AS orderAmount
FROM ebike_order
WHERE 1 = 1
<if test="operatorId != null">
AND operator_id = #{operatorId}
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time &lt; #{endTime}
</if>
GROUP BY create_time::date, operator_id
ORDER BY create_time::date, operator_id
</select>
</mapper> </mapper>