Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

This commit is contained in:
jason 2023-10-12 22:21:17 +08:00
commit 2e207019b3
109 changed files with 2356 additions and 980 deletions

View File

@ -28,16 +28,22 @@ CREATE INDEX trade_statistics_time_index
ON trade_statistics (time);
-- 菜单
INSERT INTO `ruoyi-vue-pro`.system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
VALUES ('统计管理', '', 1, 4, 0, '/statistics', 'ep:data-line', '', '');
SELECT @parentId := LAST_INSERT_ID();
INSERT INTO `ruoyi-vue-pro`.system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
VALUES ('交易统计', '', 2, 4, @parentId, 'trade', 'fa-solid:credit-card', 'statistics/trade/index', 'TradeStatistics');
-- 交易统计
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
VALUES ('交易统计', '', 2, 1, @parentId, 'trade', 'fa-solid:credit-card', 'statistics/trade/index', 'TradeStatistics');
SELECT @parentId := LAST_INSERT_ID();
-- 按钮
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('交易统计查询', 'statistics:trade:query', 3, 1, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('交易统计导出', 'statistics:trade:export', 3, 2, @parentId, '', '', '', 0);
-- 会员统计
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
VALUES ('会员统计', '', 2, 2, @parentId, 'member', 'ep:avatar', 'statistics/member/index', 'MemberStatistics');
SELECT @parentId := LAST_INSERT_ID();
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('会员统计查询', 'statistics:member:query', 3, 1, @parentId, '', '', '', 0);

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.collect.ImmutableMap;
import java.util.*;
@ -50,6 +51,13 @@ public class CollectionUtils {
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
}
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
if (ArrayUtil.isEmpty(from)) {
return new ArrayList<>();
}
return convertList(Arrays.asList(from), func);
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
@ -64,6 +72,13 @@ public class CollectionUtils {
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
return map.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
@ -162,8 +177,8 @@ public class CollectionUtils {
/**
* 对比老新两个列表找出新增修改删除的数据
*
* @param oldList 老列表
* @param newList 新列表
* @param oldList 老列表
* @param newList 新列表
* @param sameFunc 对比函数返回 true 表示相同返回 false 表示不同
* 注意same 是通过每个元素的标识判断它们是不是同一个数据
* @return [新增列表修改列表删除列表]

View File

@ -7,12 +7,16 @@ import cn.hutool.core.text.csv.CsvUtil;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* 区域工具类
@ -108,7 +112,7 @@ public class AreaUtils {
// 递归父节点
area = area.getParent();
if (area == null
|| ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
|| ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
break;
}
sb.insert(0, separator);
@ -116,4 +120,37 @@ public class AreaUtils {
return sb.toString();
}
/**
* 获取指定类型的区域列表
*
* @param type 区域类型
* @param func 转换函数
* @param <T> 结果类型
* @return 区域列表
*/
public static <T> List<T> getByType(AreaTypeEnum type, Function<Area, T> func) {
return convertList(areas.values(), func, area -> type.getType().equals(area.getType()));
}
// TODO @疯狂注释写下
public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
// TODO @疯狂这种不要用 while true因为万一脏数据可能会死循环可以转换成 for (int i = 0; i < Byte.MAX; i++) 一般是优先层级
do {
Area area = AreaUtils.getArea(id);
if (area == null) {
return null;
}
if (type.getType().equals(area.getType())) {
return area.getId();
}
if (area.getParent() == null || area.getParent().getId() == null) {
return null;
}
id = area.getParent().getId();
} while (true);
}
}

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
import javax.validation.Valid;
// TODO @芋艿后面也再撸撸这几个接口
/**
* 拼团记录 API 接口
*
@ -29,9 +28,9 @@ public interface CombinationRecordApi {
* 创建开团记录
*
* @param reqDTO 请求 DTO
* @return 开团记录编号
* @return key 开团记录编号value 团长编号
*/
Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
KeyValue<Long, Long> createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
/**
* 查询拼团记录是否成功

View File

@ -10,13 +10,22 @@ import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinResp
public interface SeckillActivityApi {
/**
* 更新秒杀库存
* 更新秒杀库存减少
*
* @param id 活动编号
* @param skuId sku 编号
* @param count 数量
* @param id 活动编号
* @param skuId sku 编号
* @param count 数量(正数)
*/
void updateSeckillStock(Long id, Long skuId, Integer count);
void updateSeckillStockDecr(Long id, Long skuId, Integer count);
/**
* 更新秒杀库存增加
*
* @param id 活动编号
* @param skuId sku 编号
* @param count 数量(正数)
*/
void updateSeckillStockIncr(Long id, Long skuId, Integer count);
/**
* 下单前校验是否参与秒杀活动
@ -24,8 +33,8 @@ public interface SeckillActivityApi {
* 如果校验失败则抛出业务异常
*
* @param activityId 活动编号
* @param skuId SKU 编号
* @param count 数量
* @param skuId SKU 编号
* @param count 数量
* @return 秒杀信息
*/
SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@ -28,8 +29,9 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
recordService.validateCombinationRecord(userId, activityId, headId, skuId, count);
}
// TODO @puhui999搞个创建的 RespDTO
@Override
public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
return recordService.createCombinationRecord(reqDTO);
}

View File

@ -18,8 +18,13 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
private SeckillActivityService activityService;
@Override
public void updateSeckillStock(Long id, Long skuId, Integer count) {
activityService.updateSeckillStock(id, skuId, count);
public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
activityService.updateSeckillStockDecr(id, skuId, count);
}
@Override
public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
activityService.updateSeckillStockIncr(id, skuId, count);
}
@Override

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.combination;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
@ -39,11 +40,26 @@ public class CombinationRecordController {
@Lazy
private CombinationRecordService combinationRecordService;
// TODO @puhui999getBargainRecordPage getBargainRecordPage 是不是可以合并然后 CombinationRecordReqPageVO 加一个 headId
// 然后如果 headId 非空并且第一页单独多查询一条 head 放到第 0 个位置相当于说第一页特殊一点
@GetMapping("/page")
@Operation(summary = "获得拼团记录分页")
@PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
public CommonResult<PageResult<CombinationRecordPageItemRespVO>> getBargainRecordPage(@Valid CombinationRecordReqPageVO pageVO) {
PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage(pageVO);
List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
// TODO @puhui999商品没读取
return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
}
@GetMapping("/page-by-headId")
@Operation(summary = "获得拼团记录分页")
@PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
public CommonResult<PageResult<CombinationRecordPageItemRespVO>> getBargainRecordPage(@Valid CombinationRecordReqPage2VO pageVO) {
// 包含团长和团员的分页记录
PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage2(pageVO);
List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
@ -54,10 +70,11 @@ public class CombinationRecordController {
@PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() {
CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO();
summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录
summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null, null)); // 获取拼团用户参与数量
summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录
CombinationRecordStatusEnum.SUCCESS.getStatus(), null));
summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录
CombinationRecordStatusEnum.SUCCESS.getStatus(), null, CombinationRecordDO.HEAD_ID_GROUP));
summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(// 获取虚拟成团记录
null, Boolean.TRUE, CombinationRecordDO.HEAD_ID_GROUP));
return success(summaryVO);
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 拼团记录分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationRecordReqPage2VO extends PageParam {
@Schema(description = "团长编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "团长编号不能为空")
private Long headId;
}

View File

@ -0,0 +1,5 @@
### /promotion/activity/list-by-spu-ids 获得多个商品,近期参与的每个活动
GET {{appApi}}/promotion/activity/list-by-spu-ids?spuIds=222&spuIds=633
Authorization: Bearer {{appToken}}
Content-Type: application/json
tenant-id: {{appTenentId}}

View File

@ -45,19 +45,19 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
@Validated
public class AppBargainRecordController {
@Resource
private BargainHelpService bargainHelpService;
@Resource
private BargainRecordService bargainRecordService;
@Resource
private BargainActivityService bargainActivityService;
@Resource
private BargainHelpService bargainHelpService;
@Resource
private TradeOrderApi tradeOrderApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private TradeOrderApi tradeOrderApi;
@GetMapping("/get-summary")
@Operation(summary = "获得砍价记录的概要信息", description = "用于小程序首页")

View File

@ -46,8 +46,8 @@ public class AppCombinationRecordController {
@Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
// 1. 获得拼团记录数量
Long count = combinationRecordService.getCombinationRecordCount(null, null);
// 1. 获得拼团参与用户数量
Long count = combinationRecordService.getCombinationRecordCount(null, null, null);
if (count == 0) {
summary.setAvatars(Collections.emptyList());
summary.setUserCount(count);

View File

@ -28,7 +28,7 @@ import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -114,9 +114,6 @@ public interface CombinationActivityConvert {
ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
return convert(reqDTO).setVirtualGroup(false)
.setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中
.setStartTime(LocalDateTime.now()) // TODO @puhui999想了下这个 startTime 应该是团长的
// TODO @puhui999有团长的情况下expireTime 应该是团长的
.setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration()))
.setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录
// 用户信息
.setNickname(user.getNickname()).setAvatar(user.getAvatar())
@ -200,4 +197,35 @@ public interface CombinationActivityConvert {
return respVO;
}
/**
* 转换生成虚拟成团虚拟记录
*
* @param virtualGroupHeadRecords 虚拟成团团长记录列表
* @return 虚拟记录列表
*/
default List<CombinationRecordDO> convertVirtualGroupList(List<CombinationRecordDO> virtualGroupHeadRecords) {
List<CombinationRecordDO> createRecords = new ArrayList<>();
virtualGroupHeadRecords.forEach(headRecord -> {
// 计算需要创建的虚拟成团记录数量
int count = headRecord.getUserSize() - headRecord.getUserCount();
for (int i = 0; i < count; i++) {
// 基础信息和团长保持一致
CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(headRecord.getActivityId())
.setCombinationPrice(headRecord.getCombinationPrice()).setSpuId(headRecord.getSpuId()).setSpuName(headRecord.getSpuName())
.setPicUrl(headRecord.getPicUrl()).setSkuId(headRecord.getSkuId()).setHeadId(headRecord.getId())
.setStatus(headRecord.getStatus()) // 状态保持和创建时一致创建完成后会接着处理
.setVirtualGroup(headRecord.getVirtualGroup()).setExpireTime(headRecord.getExpireTime())
.setStartTime(headRecord.getStartTime()).setUserSize(headRecord.getUserSize()).setUserCount(headRecord.getUserCount());
// 虚拟信息
newRecord.setCount(0);
newRecord.setUserId(0L);
newRecord.setNickname("");
newRecord.setAvatar("");
newRecord.setOrderId(0L);
createRecords.add(newRecord);
}
});
return createRecords;
}
}

View File

@ -6,13 +6,14 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 砍价活动 Mapper
@ -85,25 +86,26 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
.last("LIMIT " + count));
}
// TODO @puhui999一个商品在统一时间不会参与多个活动so 是不是不用 inner join
// PS如果可以参与多个其实可以这样写 select * from promotion_bargain_activity group by spu_id ORDER BY create_time DESC通过 group 来过滤
// TODO @puhui999是不是返回 BargainActivityDO 更干净哈
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
*
* @param spuIds spu 编号
* @param status 状态
* @return 砍价活动列表
* @return 包含 spuId activityId map 对象列表
*/
@Select("SELECT p1.* " +
"FROM promotion_bargain_activity p1 " +
"INNER JOIN ( " +
" SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
" FROM promotion_bargain_activity " +
" WHERE spu_id IN #{spuIds} " +
" GROUP BY spu_id " +
") p2 " +
"ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
"ORDER BY p1.create_time DESC;")
List<BargainActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
return selectMaps(new QueryWrapper<BargainActivityDO>()
.select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
.in("spu_id", spuIds)
.eq("status", status)
.groupBy("spu_id"));
}
default List<BargainActivityDO> selectListByIds(Collection<Long> ids) {
return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
.in(BargainActivityDO::getId, ids)
.orderByDesc(BargainActivityDO::getCreateTime));
}
}

View File

@ -6,12 +6,13 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 拼团活动 Mapper
@ -43,24 +44,24 @@ public interface CombinationActivityMapper extends BaseMapperX<CombinationActivi
.last("LIMIT " + count));
}
// TODO @puhui999类似 BargainActivityMapper
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
* @param spuIds spu 编号
* @param status 状态
* @return 拼团活动列表
* @return 包含 spuId activityId map 对象列表
*/
@Select("SELECT p1.* " +
"FROM promotion_combination_activity p1 " +
"INNER JOIN ( " +
" SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
" FROM promotion_combination_activity " +
" WHERE spu_id IN #{spuIds} " +
" GROUP BY spu_id " +
") p2 " +
"ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
"ORDER BY p1.create_time DESC;")
List<CombinationActivityDO> selectListBySpuIds(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status);
default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
return selectMaps(new QueryWrapper<CombinationActivityDO>()
.select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
.in("spu_id", spuIds)
.eq("status", status)
.groupBy("spu_id"));
}
default List<CombinationActivityDO> selectListByIds(Collection<Long> ids) {
return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
.in(CombinationActivityDO::getId, ids)
.orderByDesc(CombinationActivityDO::getCreateTime));
}
}

View File

@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -100,13 +102,41 @@ public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO
.betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime()));
}
// TODO @puhui999这个最好把 headId 也作为一个参数因为有个要求 userCount它要 DISTINCT 整体可以参考 selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId
default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup) {
return selectCount(new LambdaQueryWrapperX<CombinationRecordDO>()
.eq(status != null || virtualGroup != null,
CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长
.eqIfPresent(CombinationRecordDO::getStatus, status)
.eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup));
default PageResult<CombinationRecordDO> selectPage(CombinationRecordReqPage2VO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<CombinationRecordDO>()
.eq(CombinationRecordDO::getId, pageVO.getHeadId())
.or()
.eq(CombinationRecordDO::getHeadId, pageVO.getHeadId()));
}
/**
* 查询指定条件的记录数
* 如果参数都为 null 时则查询用户拼团记录DISTINCT 去重也就是说查询会员表中的用户有多少人参与过拼团活动每个人只统计一次
*
* @param status 状态可为 null
* @param virtualGroup 是否虚拟成团可为 null
* @param headId 团长编号可为 null
* @return 记录数
*/
default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup, Long headId) {
return selectCount(new QueryWrapper<CombinationRecordDO>()
// TODO @puhui999这种偏逻辑性的不要给 mapper 可以考虑拆成 2 mapper上层也是 2 service
.select(status == null && virtualGroup == null && headId == null, "DISTINCT (user_id)")
.eq(status != null, "status", status)
.eq(virtualGroup != null, "virtual_group", virtualGroup)
.eq(headId != null, "head_id", headId)
.groupBy("user_id"));
}
default List<CombinationRecordDO> selectListByHeadIdAndStatusAndExpireTimeLt(Long headId, Integer status, LocalDateTime dateTime) {
return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
.eq(CombinationRecordDO::getHeadId, headId)
.eq(CombinationRecordDO::getStatus, status)
.lt(CombinationRecordDO::getExpireTime, dateTime));
}
default List<CombinationRecordDO> selectListByHeadIds(Collection<Long> headIds) {
return selectList(new LambdaQueryWrapperX<CombinationRecordDO>().in(CombinationRecordDO::getHeadId, headIds));
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@ -7,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 秒杀活动 Mapper
@ -37,18 +40,32 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
}
/**
* 更新活动库存
* 更新活动库存(减少)
*
* @param id 活动编号
* @param count 扣减的库存数量
* @param count 扣减的库存数量(正数)
* @return 影响的行数
*/
default int updateStock(Long id, int count) {
default int updateStockDecr(Long id, int count) {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
.eq(SeckillActivityDO::getId, id)
.gt(SeckillActivityDO::getTotalStock, 0)
.setSql("stock = stock + " + count)
.setSql("total_stock = total_stock - " + count));
.gt(SeckillActivityDO::getStock, count)
.setSql("stock = stock - " + count));
}
/**
* 更新活动库存增加
*
* @param id 活动编号
* @param count 增加的库存数量(正数)
* @return 影响的行数
*/
default int updateStockIncr(Long id, int count) {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
.eq(SeckillActivityDO::getId, id)
.setSql("stock = stock + " + count));
}
default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
@ -58,24 +75,25 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
.apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
}
// TODO @puhui999类似 BargainActivityMapper
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
*
* @param spuIds spu 编号
* @param status 状态
* @return 秒杀活动列表
* @return 包含 spuId activityId map 对象列表
*/
@Select("SELECT p1.* " +
"FROM promotion_seckill_activity p1 " +
"INNER JOIN ( " +
" SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
" FROM promotion_seckill_activity " +
" WHERE spu_id IN #{spuIds} " +
" GROUP BY spu_id " +
") p2 " +
"ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
"ORDER BY p1.create_time DESC;")
List<SeckillActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
return selectMaps(new QueryWrapper<SeckillActivityDO>()
.select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
.in("spu_id", spuIds)
.eq("status", status)
.groupBy("spu_id"));
}
default List<SeckillActivityDO> selectListByIds(Collection<Long> ids) {
return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
.in(SeckillActivityDO::getId, ids)
.orderByDesc(SeckillActivityDO::getCreateTime));
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -30,17 +31,32 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
}
/**
* 更新活动库存
* 更新活动库存减少
*
* @param id 活动编号
* @param count 扣减的库存数量
* @param count 扣减的库存数量(减少库存)
* @return 影响的行数
*/
default int updateStock(Long id, int count) {
default int updateStockDecr(Long id, int count) {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
.eq(SeckillProductDO::getId, id)
.gt(SeckillProductDO::getStock, count)
.ge(SeckillProductDO::getStock, count)
.setSql("stock = stock - " + count));
}
/**
* 更新活动库存增加
*
* @param id 活动编号
* @param count 需要增加的库存增加库存
* @return 影响的行数
*/
default int updateStockIncr(Long id, int count) {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
.eq(SeckillProductDO::getId, id)
.setSql("stock = stock + " + count));
}
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.promotion.job.combination;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 拼团过期 Job
*
* @author HUIHUI
*/
@Component
public class CombinationRecordExpireJob implements JobHandler {
@Resource
private CombinationRecordService combinationRecordService;
@Override
@TenantJob
public String execute(String param) {
KeyValue<Integer, Integer> keyValue = combinationRecordService.expireCombinationRecord();
return StrUtil.format("过期拼团 {} 个, 虚拟成团 {} 个", keyValue.getKey(), keyValue.getValue());
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.promotion.service.bargain;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@ -20,12 +22,11 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -84,10 +85,14 @@ public class BargainActivityServiceImpl implements BargainActivityService {
@Override
public void updateBargainActivityStock(Long id, Integer count) {
// 更新库存如果更新失败则抛出异常
int updateCount = bargainActivityMapper.updateStock(id, count);
if (updateCount == 0) {
throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
if (count < 0) {
// 更新库存如果更新失败则抛出异常
int updateCount = bargainActivityMapper.updateStock(id, count);
if (updateCount == 0) {
throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
}
} else if (count > 0) {
bargainActivityMapper.updateStock(id, count);
}
}
@ -139,7 +144,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
@Override
public List<BargainActivityDO> getBargainActivityList(Set<Long> ids) {
return bargainActivityMapper.selectBatchIds(ids);
return bargainActivityMapper.selectBatchIds(ids);
}
@Override
@ -178,7 +183,14 @@ public class BargainActivityServiceImpl implements BargainActivityService {
@Override
public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
return bargainActivityMapper.selectListBySpuIds(spuIds, status);
// 1. 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
// TODO @puhui999我想了下这种是不是只展示当前正在进行中的已经结束或者未开始的可能没啥意义
List<Map<String, Object>> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
return Collections.emptyList();
}
// 2. 查询活动详情
return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
@ -25,12 +26,12 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -228,7 +229,13 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
@Override
public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
return combinationActivityMapper.selectListBySpuIds(spuIds, status);
// 1.查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
List<Map<String, Object>> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
return Collections.emptyList();
}
// 2.查询活动详情
return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
}
}

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
@ -49,9 +50,9 @@ public interface CombinationRecordService {
* 创建拼团记录
*
* @param reqDTO 创建信息
* @return 开团记录编号
* @return key 开团记录编号 value 团长编号
*/
Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
/**
* 获得拼团记录
@ -90,9 +91,10 @@ public interface CombinationRecordService {
*
* @param status 状态-允许为空
* @param virtualGroup 是否虚拟成团-允许为空
* @param headId 团长编号允许空目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 可以设置
* @return 记录数
*/
Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup);
Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId);
/**
* 获取最近的 count 条拼团记录
@ -136,6 +138,14 @@ public interface CombinationRecordService {
*/
PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO);
/**
* 获取拼团记录分页数据通过团长查询
*
* @param pageVO 分页请求
* @return 拼团记录分页数据包括团长的
*/
PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO);
/**
* 拼团活动获得拼团记录数量 Map
*
@ -148,7 +158,6 @@ public interface CombinationRecordService {
@Nullable Integer status,
@Nullable Long headId);
/**
* 获取拼团记录
*
@ -167,5 +176,11 @@ public interface CombinationRecordService {
*/
void cancelCombinationRecord(Long userId, Long id, Long headId);
/**
* 处理过期拼团
*
* @return key 过期拼团数量, value 虚拟成团数量
*/
KeyValue<Integer, Integer> expireCombinationRecord();
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.combination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
@ -21,18 +23,18 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -49,23 +51,20 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
public class CombinationRecordServiceImpl implements CombinationRecordService {
@Resource
@Lazy
private CombinationActivityService combinationActivityService;
@Resource
private CombinationRecordMapper recordMapper;
private CombinationRecordMapper combinationRecordMapper;
@Resource
private MemberUserApi memberUserApi;
@Resource
@Lazy
private ProductSpuApi productSpuApi;
@Resource
@Lazy
private ProductSkuApi productSkuApi;
@Resource
private TradeOrderApi tradeOrderApi;
// TODO @芋艿在详细预览下
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
@ -73,13 +72,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
CombinationRecordDO record = validateCombinationRecord(userId, orderId);
// 更新状态
// TODO @puhui999不要整个更新new 一个出来why例如说两个线程都去更新这样存在相互覆盖的问题
record.setStatus(status);
recordMapper.updateById(record);
combinationRecordMapper.updateById(record);
}
private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
// 校验拼团是否存在
CombinationRecordDO recordDO = recordMapper.selectByUserIdAndOrderId(userId, orderId);
CombinationRecordDO recordDO = combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
if (recordDO == null) {
throw exception(COMBINATION_RECORD_NOT_EXISTS);
}
@ -108,7 +108,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
// 2. 父拼团是否存在,是否已经满了
if (headId != null) {
// 2.1. 查询进行中的父拼团
CombinationRecordDO record = recordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
if (record == null) {
throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
}
@ -143,7 +143,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
}
// 6.1 校验是否有拼团记录
List<CombinationRecordDO> recordList = recordMapper.selectListByUserIdAndActivityId(userId, activityId);
List<CombinationRecordDO> recordList = combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单不算数
if (CollUtil.isEmpty(recordList)) { // 如果为空说明可以参与直接返回
return new KeyValue<>(activity, product);
@ -164,32 +164,34 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
// 1. 校验拼团活动
KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
// 2.1 组合数据创建拼团记录
// 2. 组合数据创建拼团记录
MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
// 2.2 如果是团长需要设置 headId CombinationRecordDO#HEAD_ID_GROUP
// 2.1. 如果是团长需要设置 headId CombinationRecordDO#HEAD_ID_GROUP
if (record.getHeadId() == null) {
record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
record.setStartTime(LocalDateTime.now())
.setExpireTime(keyValue.getKey().getStartTime().plusHours(keyValue.getKey().getLimitDuration()))
.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
} else {
// 2.2.有团长的情况下需要设置开始时间和过期时间为团长的
CombinationRecordDO headRecord = combinationRecordMapper.selectByHeadId(record.getHeadId(),
CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); // 查询进行中的父拼团
record.setStartTime(headRecord.getStartTime()).setExpireTime(headRecord.getExpireTime());
}
recordMapper.insert(record);
combinationRecordMapper.insert(record);
if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
return record.getId();
// 3. 更新拼团记录
if (ObjUtil.notEqual(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
}
// TODO @puhui是不是这里的更新放到 order 模块那支付完成后
// 4更新拼团相关信息到订单
tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId());
// 4更新拼团记录
updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
return record.getId();
return new KeyValue<>(record.getId(), record.getHeadId());
}
/**
@ -204,31 +206,33 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
if (CollUtil.isEmpty(records)) {
return;
}
CombinationRecordDO headRecord = recordMapper.selectById(headId);
CombinationRecordDO headRecord = combinationRecordMapper.selectById(headId);
// 2. 批量更新记录
List<CombinationRecordDO> updateRecords = new ArrayList<>();
records.add(headRecord); // 加入团长团长也需要更新
boolean isFull = records.size() >= activity.getUserSize();
LocalDateTime now = LocalDateTime.now();
records.forEach(item -> {
CombinationRecordDO updateRecord = new CombinationRecordDO();
updateRecord.setId(item.getId()).setUserCount(records.size());
if (isFull) {
updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
updateRecord.setEndTime(now);
}
updateRecords.add(updateRecord);
});
recordMapper.updateBatch(updateRecords);
combinationRecordMapper.updateBatch(updateRecords);
}
@Override
public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) {
return recordMapper.selectByUserIdAndOrderId(userId, orderId);
return combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
}
@Override
public List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) {
return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
return combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
}
@Override
@ -241,52 +245,57 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
}
@Override
public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) {
return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup);
public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId) {
return combinationRecordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup, headId);
}
@Override
public List<CombinationRecordDO> getLatestCombinationRecordList(int count) {
return recordMapper.selectLatestList(count);
return combinationRecordMapper.selectLatestList(count);
}
@Override
public List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count) {
return recordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
return combinationRecordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
CombinationRecordDO.HEAD_ID_GROUP, count);
}
@Override
public CombinationRecordDO getCombinationRecordById(Long id) {
return recordMapper.selectById(id);
return combinationRecordMapper.selectById(id);
}
@Override
public List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId) {
return recordMapper.selectList(CombinationRecordDO::getHeadId, headId);
return combinationRecordMapper.selectList(CombinationRecordDO::getHeadId, headId);
}
@Override
public PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO) {
return recordMapper.selectPage(pageVO);
return combinationRecordMapper.selectPage(pageVO);
}
@Override
public PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO) {
return combinationRecordMapper.selectPage(pageVO);
}
@Override
public Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds,
@Nullable Integer status, @Nullable Long headId) {
return recordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
return combinationRecordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
}
@Override
public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) {
return recordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
return combinationRecordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelCombinationRecord(Long userId, Long id, Long headId) {
// 删除记录
recordMapper.deleteById(id);
combinationRecordMapper.deleteById(id);
// 需要更新的记录
List<CombinationRecordDO> updateRecords = new ArrayList<>();
@ -313,7 +322,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
});
} else { // 情况二团员
// 团长
CombinationRecordDO recordHead = recordMapper.selectById(headId);
CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId);
// 团员
List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
if (CollUtil.isEmpty(records)) {
@ -329,7 +338,114 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
}
// 更新拼团记录
recordMapper.updateBatch(updateRecords);
combinationRecordMapper.updateBatch(updateRecords);
}
@Override
public KeyValue<Integer, Integer> expireCombinationRecord() {
// TODO @puhui999数字一般是 1. 2. 这种格式哈
// 1获取所有正在进行中的过期的父拼团
List<CombinationRecordDO> headExpireRecords = combinationRecordMapper.selectListByHeadIdAndStatusAndExpireTimeLt(
CombinationRecordDO.HEAD_ID_GROUP, CombinationRecordStatusEnum.IN_PROGRESS.getStatus(), LocalDateTime.now());
if (CollUtil.isEmpty(headExpireRecords)) {
return new KeyValue<>(0, 0);
}
// 2. 获取拼团活动
// TODO @puhui999在自己模块里变量可以简略点例如说 activityList
List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityListByIds(
convertSet(headExpireRecords, CombinationRecordDO::getActivityId));
Map<Long, CombinationActivityDO> activityMap = convertMap(combinationActivities, CombinationActivityDO::getId);
// TODO @puhui999job 一般不建议异步跑因为可能下次跑结果上次还没跑完
// TODO 这里我们可以每个 record 处理下然后按照是否需要虚拟拼团各搞一个方法逻辑 + 事务这样保证 job 里面尽量不要大事务而是 n 个独立小事务的处理
// 3. 校验是否虚拟成团
List<CombinationRecordDO> virtualGroupHeadRecords = new ArrayList<>(); // 虚拟成团
for (Iterator<CombinationRecordDO> iterator = headExpireRecords.iterator(); iterator.hasNext(); ) {
CombinationRecordDO record = iterator.next();
// 3.1 不匹配则直接跳过
CombinationActivityDO activity = activityMap.get(record.getActivityId());
if (activity == null || !activity.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的
continue;
}
// 3.2 匹配则移除添加到虚拟成团中并结束寻找
virtualGroupHeadRecords.add(record);
iterator.remove();
break;
}
// 4.处理过期的拼团
getSelf().handleExpireRecord(headExpireRecords);
// 5.虚拟成团
getSelf().handleVirtualGroupRecord(virtualGroupHeadRecords);
return new KeyValue<>(headExpireRecords.size(), virtualGroupHeadRecords.size());
}
@Async
protected void handleExpireRecord(List<CombinationRecordDO> headExpireRecords) {
if (CollUtil.isEmpty(headExpireRecords)) {
return;
}
// 1.更新拼团记录
List<CombinationRecordDO> headsAndRecords = updateBatchCombinationRecords(headExpireRecords,
CombinationRecordStatusEnum.FAILED);
if (headsAndRecords == null) {
return;
}
// 2.订单取消 TODO 以现在的取消回滚逻辑好像只能循环了
headsAndRecords.forEach(item -> {
tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId());
});
}
@Async
protected void handleVirtualGroupRecord(List<CombinationRecordDO> virtualGroupHeadRecords) {
if (CollUtil.isEmpty(virtualGroupHeadRecords)) {
return;
}
// 1. 团员补齐
combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecords));
// 2. 更新拼团记录
updateBatchCombinationRecords(virtualGroupHeadRecords, CombinationRecordStatusEnum.SUCCESS);
}
private List<CombinationRecordDO> updateBatchCombinationRecords(List<CombinationRecordDO> headRecords, CombinationRecordStatusEnum status) {
// 1. 查询团成员
List<CombinationRecordDO> records = combinationRecordMapper.selectListByHeadIds(
convertSet(headRecords, CombinationRecordDO::getId));
if (CollUtil.isEmpty(records)) {
return null;
}
Map<Long, List<CombinationRecordDO>> recordsMap = convertMultiMap(records, CombinationRecordDO::getHeadId);
headRecords.forEach(item -> {
recordsMap.get(item.getId()).add(item); // 把团长加进团里
});
// 2.批量更新拼团记录 status 失败/成团时间
List<CombinationRecordDO> headsAndRecords = mergeValuesFromMap(recordsMap);
List<CombinationRecordDO> updateRecords = new ArrayList<>(headsAndRecords.size());
LocalDateTime now = LocalDateTime.now();
headsAndRecords.forEach(item -> {
CombinationRecordDO record = new CombinationRecordDO().setId(item.getId())
.setStatus(status.getStatus()).setEndTime(now);
if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
record.setUserCount(record.getUserSize());
}
updateRecords.add(record);
});
combinationRecordMapper.updateBatch(updateRecords);
return headsAndRecords;
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*
* @return 自己
*/
private CombinationRecordServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}

View File

@ -36,13 +36,22 @@ public interface SeckillActivityService {
void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
/**
* 更新秒杀库存
* 更新秒杀库存减少
*
* @param id 活动编号
* @param skuId sku 编号
* @param count 数量
* @param count 数量正数
*/
void updateSeckillStock(Long id, Long skuId, Integer count);
void updateSeckillStockDecr(Long id, Long skuId, Integer count);
/**
* 更新秒杀库存增加
*
* @param id 活动编号
* @param skuId sku 编号
* @param count 数量正数
*/
void updateSeckillStockIncr(Long id, Long skuId, Integer count);
/**
* 关闭秒杀活动
@ -113,8 +122,8 @@ public interface SeckillActivityService {
* 如果校验失败则抛出业务异常
*
* @param activityId 活动编号
* @param skuId SKU 编号
* @param count 数量
* @param skuId SKU 编号
* @param count 数量
* @return 秒杀信息
*/
SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.promotion.service.seckill;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -27,6 +29,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -154,7 +157,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillStock(Long id, Long skuId, Integer count) {
public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
// 1.1 校验活动库存是否充足
SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
if (count > seckillActivity.getTotalStock()) {
@ -167,18 +170,28 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
}
// 2.1 更新活动商品库存
int updateCount = seckillProductMapper.updateStock(product.getId(), count);
int updateCount = seckillProductMapper.updateStockDecr(product.getId(), count);
if (updateCount == 0) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
// 2.2 更新活动库存
updateCount = seckillActivityMapper.updateStock(seckillActivity.getId(), count);
updateCount = seckillActivityMapper.updateStockDecr(seckillActivity.getId(), count);
if (updateCount == 0) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(id, skuId);
// 更新活动商品库存
seckillProductMapper.updateStockIncr(product.getId(), count);
// 更新活动库存
seckillActivityMapper.updateStockIncr(id, count);
}
/**
* 更新秒杀商品
*
@ -312,7 +325,13 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
return seckillActivityMapper.selectListBySpuIds(spuIds, status);
// 1.查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
List<Map<String, Object>> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
return Collections.emptyList();
}
// 2.查询活动详情
return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
}
}

View File

@ -59,6 +59,11 @@
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -1,21 +1,25 @@
package cn.iocoder.yudao.module.statistics.controller.admin.member;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberTerminalStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*;
import cn.iocoder.yudao.module.statistics.service.member.MemberStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
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.RestController;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 会员统计")
@RestController
@RequestMapping("/statistics/member")
@ -23,36 +27,47 @@ import java.util.List;
@Slf4j
public class MemberStatisticsController {
// TODO @疯狂一个类似 getTradeTrendSummaryComparison 的接口
// TODO @疯狂一个类似 getTradeStatisticsList 的接口
@Resource
private MemberStatisticsService memberStatisticsService;
@GetMapping("/summary")
@Operation(summary = "获得会员统计")
public CommonResult<TradeStatisticsComparisonRespVO<MemberSummaryRespVO>> getMemberSummary() {
// TODO 疯狂目前先直接计算
return null;
@PreAuthorize("@ss.hasPermission('statistics:member:query')")
public CommonResult<MemberSummaryRespVO> getMemberSummary() {
return success(memberStatisticsService.getMemberSummary());
}
@GetMapping("/analyse")
@Operation(summary = "获得会员分析数据")
@PreAuthorize("@ss.hasPermission('statistics:member:query')")
public CommonResult<MemberAnalyseRespVO> getMemberAnalyse(MemberAnalyseReqVO reqVO) {
return success(memberStatisticsService.getMemberAnalyse(
ArrayUtil.get(reqVO.getTimes(), 0), ArrayUtil.get(reqVO.getTimes(), 1)));
}
@GetMapping("/get-area-statistics-list")
@Operation(summary = "按照省份,获得会员统计列表")
@PreAuthorize("@ss.hasPermission('statistics:member:query')")
public CommonResult<List<MemberAreaStatisticsRespVO>> getMemberAreaStatisticsList() {
// TODO 疯狂目前先直接计算进行统计后续再考虑优化
return null;
return success(memberStatisticsService.getMemberAreaStatisticsList());
}
@GetMapping("/get-sex-statistics-list")
@Operation(summary = "按照性别,获得会员统计列表")
@PreAuthorize("@ss.hasPermission('statistics:member:query')")
public CommonResult<List<MemberSexStatisticsRespVO>> getMemberSexStatisticsList() {
// TODO 疯狂目前先直接计算进行统计后续再考虑优化
return null;
return success(memberStatisticsService.getMemberSexStatisticsList());
}
@GetMapping("/get-terminal-statistics-list")
@Operation(summary = "按照终端,获得会员统计列表")
@PreAuthorize("@ss.hasPermission('statistics:member:query')")
public CommonResult<List<MemberTerminalStatisticsRespVO>> getMemberTerminalStatisticsList() {
// TODO 疯狂目前先直接计算进行统计后续再考虑优化
// TODO 疯狂这个可以晚点写因为 user = = 上还没记录 terminal
return null;
List<MemberTerminalStatisticsRespVO> list = convertList(TerminalEnum.values(),
t -> new MemberTerminalStatisticsRespVO()
.setTerminal(t.getTerminal()).setUserCount(t.getTerminal() * 100));
return success(list);
}
}

View File

@ -1,9 +0,0 @@
/**
* TODO
* 1. 会员总数据
* 2. 性别统计
* 3. 渠道统计
* 4. 地域统计
* 5. 会员概览
*/
package cn.iocoder.yudao.module.statistics.controller.admin.member;

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 会员分析对照数据 Response VO")
@Data
public class MemberAnalyseComparisonRespVO {
@Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer userCount;
@Schema(description = "活跃用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer activeUserCount;
@Schema(description = "充值会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "221")
private Integer rechargeUserCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 会员分析 Request VO")
@Data
public class MemberAnalyseReqVO {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "时间范围")
private LocalDateTime[] times;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 会员分析 Response VO")
@Data
public class MemberAnalyseRespVO {
@Schema(description = "访客数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer visitorCount;
@Schema(description = "下单用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderUserCount;
@Schema(description = "成交用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer payUserCount;
@Schema(description = "客单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer atv;
@Schema(description = "对照数据", requiredMode = Schema.RequiredMode.REQUIRED)
private TradeStatisticsComparisonRespVO<MemberAnalyseComparisonRespVO> comparison;
}

View File

@ -13,11 +13,4 @@ public class MemberSexStatisticsRespVO {
@Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer userCount;
@Schema(description = "订单创建数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderCreateCount;
@Schema(description = "订单支付数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
private Integer orderPayCount;
@Schema(description = "订单支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "622")
private Integer orderPayPrice;
}

View File

@ -15,6 +15,7 @@ public class MemberSummaryRespVO {
@Schema(description = "充值金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer rechargePrice;
@Schema(description = "支出金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer expensePrice;

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 终端性别统计 Response VO")
@Schema(description = "管理后台 - 会员终端统计 Response VO")
@Data
public class MemberTerminalStatisticsRespVO {
@ -13,11 +13,4 @@ public class MemberTerminalStatisticsRespVO {
@Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer userCount;
@Schema(description = "订单创建数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderCreateCount;
@Schema(description = "订单支付数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
private Integer orderPayCount;
@Schema(description = "订单支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "622")
private Integer orderPayPrice;
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.statistics.convert.member;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 会员统计 Convert
*
* @author owen
*/
@Mapper
public interface MemberStatisticsConvert {
MemberStatisticsConvert INSTANCE = Mappers.getMapper(MemberStatisticsConvert.class);
default List<MemberAreaStatisticsRespVO> convertList(List<Area> areaList,
Map<Integer, Integer> userCountMap,
Map<Integer, MemberAreaStatisticsRespVO> orderMap) {
return CollectionUtils.convertList(areaList, area -> {
MemberAreaStatisticsRespVO orderVo = Optional.ofNullable(orderMap.get(area.getId())).orElseGet(MemberAreaStatisticsRespVO::new);
return new MemberAreaStatisticsRespVO()
.setAreaId(area.getId()).setAreaName(area.getName())
.setUserCount(MapUtil.getInt(userCountMap, area.getId(), 0))
.setOrderCreateCount(ObjUtil.defaultIfNull(orderVo.getOrderCreateCount(), 0))
.setOrderPayCount(ObjUtil.defaultIfNull(orderVo.getOrderPayCount(), 0))
.setOrderPayPrice(ObjUtil.defaultIfNull(orderVo.getOrderPayPrice(), 0));
});
}
}

View File

@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.statistics.convert.trade;
import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryExcelVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -46,8 +46,8 @@ public interface TradeStatisticsConvert {
List<TradeTrendSummaryExcelVO> convertList02(List<TradeTrendSummaryRespVO> list);
TradeStatisticsDO convert(LocalDateTime time, TradeOrderSummaryRespDTO orderSummary,
AfterSaleSummaryRespDTO afterSaleSummary, Integer brokerageSettlementPrice,
WalletSummaryRespDTO walletSummary);
TradeStatisticsDO convert(LocalDateTime time, TradeOrderSummaryRespBO orderSummary,
AfterSaleSummaryRespBO afterSaleSummary, Integer brokerageSettlementPrice,
WalletSummaryRespBO walletSummary);
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.infra;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
/**
* API 访问日志的统计 Mapper
*
* @author owen
*/
@Mapper
public interface ApiAccessLogStatisticsMapper extends BaseMapperX<Object> {
Integer selectCountByIp(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectCountByUserId(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.member;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 会员信息的统计 Mapper
*
* @author owen
*/
@Mapper
public interface MemberStatisticsMapper extends BaseMapperX<Object> {
List<MemberAreaStatisticsRespVO> selectSummaryListByAreaId();
List<MemberSexStatisticsRespVO> selectSummaryListBySex();
Integer selectUserCount(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.pay;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
/**
* 支付钱包的统计 Mapper
*
* @author owen
*/
@Mapper
public interface PayWalletStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
WalletSummaryRespBO selectRechargeSummaryByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime,
@Param("payStatus") Boolean payStatus);
WalletSummaryRespBO selectRechargeSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime,
@Param("refundStatus") Integer refundStatus);
Integer selectPriceSummaryByBizTypeAndCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime,
@Param("bizType") Integer bizType);
// TODO @疯狂是不是搞个单独的 BO
MemberSummaryRespVO selectRechargeSummaryGroupByWalletId(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime,
@Param("payStatus") Boolean payStatus);
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
/**
* 售后订单的统计 Mapper
*
* @author owen
*/
@Mapper
public interface AfterSaleStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
AfterSaleSummaryRespBO selectSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
/**
* 订单分销的统计 Mapper
*
* @author owen
*/
@Mapper
public interface BrokerageStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 交易订单的统计 Mapper
*
* @author owen
*/
@Mapper
public interface TradeOrderStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
List<MemberAreaStatisticsRespVO> selectSummaryListByAreaId();
Integer selectCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectSummaryPriceByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectUserCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectUserCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -19,6 +19,7 @@ import java.util.List;
@Mapper
public interface TradeStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
// TODO @疯狂这个要不要也挪到 xml 保持统一
@Select("SELECT IFNULL(SUM(order_create_count), 0) AS count, IFNULL(SUM(order_pay_price), 0) AS summary " +
"FROM trade_statistics " +
"WHERE time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
@ -30,4 +31,8 @@ public interface TradeStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
List<TradeTrendSummaryRespVO> selectListByTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
Integer selectExpensePriceByTimeBetween(@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.statistics.service.infra;
import java.time.LocalDateTime;
/**
* API 访问日志的统计 Service 接口
*
* @author owen
*/
public interface ApiAccessLogStatisticsService {
/**
* 获取活跃用户数量
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 活跃用户数量
*/
Integer getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取访问用户数量
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 访问用户数量
*/
Integer getVisitorUserCount(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.statistics.service.infra;
import cn.iocoder.yudao.module.statistics.dal.mysql.infra.ApiAccessLogStatisticsMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* API 访问日志的统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class ApiAccessLogStatisticsServiceImpl implements ApiAccessLogStatisticsService {
@Resource
private ApiAccessLogStatisticsMapper apiAccessLogStatisticsMapper;
@Override
public Integer getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
return apiAccessLogStatisticsMapper.selectCountByUserId(beginTime, endTime);
}
@Override
public Integer getVisitorUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
return apiAccessLogStatisticsMapper.selectCountByIp(beginTime, endTime);
}
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.statistics.service.member;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAnalyseRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
import java.time.LocalDateTime;
import java.util.List;
/**
* 会员信息的统计 Service 接口
*
* @author owen
*/
public interface MemberStatisticsService {
/**
* 获取会员统计
*
* @return 会员统计
*/
MemberSummaryRespVO getMemberSummary();
/**
* 按照省份获得会员统计列表
*
* @return 会员统计列表
*/
List<MemberAreaStatisticsRespVO> getMemberAreaStatisticsList();
/**
* 按照性别获得会员统计列表
*
* @return 会员统计列表
*/
List<MemberSexStatisticsRespVO> getMemberSexStatisticsList();
/**
* 获取用户分析数据
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 用户分析数据
*/
MemberAnalyseRespVO getMemberAnalyse(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -0,0 +1,113 @@
package cn.iocoder.yudao.module.statistics.service.member;
import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import cn.iocoder.yudao.module.statistics.convert.member.MemberStatisticsConvert;
import cn.iocoder.yudao.module.statistics.dal.mysql.member.MemberStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.infra.ApiAccessLogStatisticsService;
import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService;
import cn.iocoder.yudao.module.statistics.service.trade.TradeOrderStatisticsService;
import cn.iocoder.yudao.module.statistics.service.trade.TradeStatisticsService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 会员信息的统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class MemberStatisticsServiceImpl implements MemberStatisticsService {
@Resource
private MemberStatisticsMapper memberStatisticsMapper;
@Resource
private PayWalletStatisticsService payWalletStatisticsService;
@Resource
private TradeStatisticsService tradeStatisticsService;
@Resource
private TradeOrderStatisticsService tradeOrderStatisticsService;
@Resource
private ApiAccessLogStatisticsService apiAccessLogStatisticsService;
@Override
public MemberSummaryRespVO getMemberSummary() {
MemberSummaryRespVO vo = payWalletStatisticsService.getUserRechargeSummary(null, null);
Integer expensePrice = tradeStatisticsService.getExpensePrice(null, null);
Integer userCount = memberStatisticsMapper.selectUserCount(null, null);
// 拼接数据
if (vo == null) {
vo = new MemberSummaryRespVO().setRechargeUserCount(0).setRechargePrice(0);
}
return vo.setUserCount(userCount).setExpensePrice(expensePrice);
}
@Override
public List<MemberAreaStatisticsRespVO> getMemberAreaStatisticsList() {
// 统计用户
Map<Integer, Integer> userCountMap = convertMap(memberStatisticsMapper.selectSummaryListByAreaId(),
vo -> AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE),
MemberAreaStatisticsRespVO::getUserCount, Integer::sum);
// 统计订单
Map<Integer, MemberAreaStatisticsRespVO> orderMap = convertMap(tradeOrderStatisticsService.getSummaryListByAreaId(),
vo -> AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE),
vo -> vo,
(a, b) -> new MemberAreaStatisticsRespVO()
.setOrderCreateCount(a.getOrderCreateCount() + b.getOrderCreateCount())
.setOrderPayCount(a.getOrderPayCount() + b.getOrderPayCount())
.setOrderPayPrice(a.getOrderPayPrice() + b.getOrderPayPrice()));
// 拼接数据
return MemberStatisticsConvert.INSTANCE.convertList(AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area), userCountMap, orderMap);
}
@Override
public List<MemberSexStatisticsRespVO> getMemberSexStatisticsList() {
return memberStatisticsMapper.selectSummaryListBySex();
}
@Override
public MemberAnalyseRespVO getMemberAnalyse(LocalDateTime beginTime, LocalDateTime endTime) {
// 对照数据
MemberAnalyseComparisonRespVO vo = getMemberAnalyseComparisonData(beginTime, endTime);
LocalDateTime referenceBeginTime = beginTime.minus(Duration.between(beginTime, endTime));
MemberAnalyseComparisonRespVO reference = getMemberAnalyseComparisonData(referenceBeginTime, beginTime);
Integer payUserCount = tradeOrderStatisticsService.getPayUserCount(beginTime, endTime);
// 计算客单价
int atv = 0;
if (payUserCount != null && payUserCount > 0) {
Integer payPrice = tradeOrderStatisticsService.getOrderPayPrice(beginTime, endTime);
atv = NumberUtil.div(payPrice, payUserCount).intValue();
}
return new MemberAnalyseRespVO()
.setVisitorCount(apiAccessLogStatisticsService.getVisitorUserCount(beginTime, endTime))
.setOrderUserCount(tradeOrderStatisticsService.getOrderUserCount(beginTime, endTime))
.setPayUserCount(payUserCount)
.setAtv(atv)
.setComparison(new TradeStatisticsComparisonRespVO<>(vo, reference));
}
private MemberAnalyseComparisonRespVO getMemberAnalyseComparisonData(LocalDateTime beginTime, LocalDateTime endTime) {
Integer rechargeUserCount = Optional.ofNullable(payWalletStatisticsService.getUserRechargeSummary(beginTime, endTime))
.map(MemberSummaryRespVO::getRechargeUserCount).orElse(0);
return new MemberAnalyseComparisonRespVO()
.setUserCount(memberStatisticsMapper.selectUserCount(beginTime, endTime))
.setActiveUserCount(apiAccessLogStatisticsService.getActiveUserCount(beginTime, endTime))
.setRechargeUserCount(rechargeUserCount);
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.statistics.service.pay;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
import java.time.LocalDateTime;
/**
* 钱包的统计 Service 接口
*
* @author owen
*/
public interface PayWalletStatisticsService {
/**
* 获取钱包统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 钱包统计
*/
WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取钱包充值统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 钱包充值统计
*/
MemberSummaryRespVO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.statistics.service.pay;
import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
import cn.iocoder.yudao.module.statistics.dal.mysql.pay.PayWalletStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 钱包的统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class PayWalletStatisticsServiceImpl implements PayWalletStatisticsService {
@Resource
private PayWalletStatisticsMapper payWalletStatisticsMapper;
@Override
public WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime) {
WalletSummaryRespBO paySummary = payWalletStatisticsMapper.selectRechargeSummaryByPayTimeBetween(
beginTime, endTime, true);
WalletSummaryRespBO refundSummary = payWalletStatisticsMapper.selectRechargeSummaryByRefundTimeBetween(
beginTime, endTime, PayRefundStatusEnum.SUCCESS.getStatus());
Integer walletPayPrice = payWalletStatisticsMapper.selectPriceSummaryByBizTypeAndCreateTimeBetween(
beginTime, endTime, PayWalletBizTypeEnum.PAYMENT.getType());
// 拼接
paySummary.setOrderWalletPayPrice(walletPayPrice)
.setRechargeRefundCount(refundSummary.getRechargeRefundCount())
.setRechargeRefundPrice(refundSummary.getRechargeRefundPrice());
return paySummary;
}
@Override
public MemberSummaryRespVO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return payWalletStatisticsMapper.selectRechargeSummaryGroupByWalletId(beginTime, endTime, true);
}
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
import java.time.LocalDateTime;
/**
* 售后统计 Service 接口
*
* @author owen
*/
public interface AfterSaleStatisticsService {
/**
* 获取售后单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 售后统计结果
*/
AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.iocoder.yudao.module.statistics.dal.mysql.trade.AfterSaleStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 售后统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class AfterSaleStatisticsServiceImpl implements AfterSaleStatisticsService {
@Resource
private AfterSaleStatisticsMapper afterSaleStatisticsMapper;
@Override
public AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return afterSaleStatisticsMapper.selectSummaryByRefundTimeBetween(beginTime, endTime);
}
}

View File

@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.trade.api.brokerage;
package cn.iocoder.yudao.module.statistics.service.trade;
import java.time.LocalDateTime;
/**
* 分销 API 接口
* 分销统计 Service 接口
*
* @author owen
*/
public interface TradeBrokerageApi {
public interface BrokerageStatisticsService {
/**
* 获取已结算的佣金金额

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.iocoder.yudao.module.statistics.dal.mysql.trade.BrokerageStatisticsMapper;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 分销统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class BrokerageStatisticsServiceImpl implements BrokerageStatisticsService {
@Resource
private BrokerageStatisticsMapper brokerageStatisticsMapper;
@Override
public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageStatisticsMapper.selectSummaryPriceByStatusAndUnfreezeTimeBetween(
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
beginTime, endTime);
}
}

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
import java.time.LocalDateTime;
import java.util.List;
/**
* 交易订单的统计 Service 接口
*
* @author owen
*/
public interface TradeOrderStatisticsService {
/**
* 获取订单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 订单统计结果
*/
TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取地区订单统计
*
* @return 订单统计结果
*/
List<MemberAreaStatisticsRespVO> getSummaryListByAreaId();
/**
* 获取下单用户数量
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 支付下单数量
*/
Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取支付用户数量
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 支付用户数量
*/
Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取支付金额
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 支付用户金额
*/
Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeOrderStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 交易订单统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class TradeOrderStatisticsServiceImpl implements TradeOrderStatisticsService {
@Resource
private TradeOrderStatisticsMapper tradeOrderStatisticsMapper;
@Override
public TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return new TradeOrderSummaryRespBO()
.setOrderCreateCount(tradeOrderStatisticsMapper.selectCountByCreateTimeBetween(beginTime, endTime))
.setOrderPayCount(tradeOrderStatisticsMapper.selectCountByPayTimeBetween(beginTime, endTime))
.setOrderPayPrice(tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime));
}
@Override
public List<MemberAreaStatisticsRespVO> getSummaryListByAreaId() {
return tradeOrderStatisticsMapper.selectSummaryListByAreaId();
}
@Override
public Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeOrderStatisticsMapper.selectUserCountByCreateTimeBetween(beginTime, endTime);
}
@Override
public Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeOrderStatisticsMapper.selectUserCountByPayTimeBetween(beginTime, endTime);
}
@Override
public Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime);
}
}

View File

@ -22,13 +22,20 @@ public interface TradeStatisticsService {
TradeStatisticsComparisonRespVO<TradeSummaryRespVO> getTradeSummaryComparison();
/**
* 获得交易状况统计
* 获得交易状况统计对照
*
* @return 统计数据对照
*/
TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO> getTradeTrendSummaryComparison(
LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获得交易状况统计
*
* @return 统计数据对照
*/
Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获得交易状况明细
*

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.statistics.service.trade;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
@ -11,11 +11,8 @@ import cn.iocoder.yudao.module.statistics.convert.trade.TradeStatisticsConvert;
import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO;
import cn.iocoder.yudao.module.trade.api.aftersale.TradeAfterSaleApi;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.api.brokerage.TradeBrokerageApi;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.validation.annotation.Validated;
@ -37,31 +34,26 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
@Resource
private TradeStatisticsMapper tradeStatisticsMapper;
// TODO @疯狂统计逻辑自己服务 mapper 去统计不要调用其它服务 API
// 主要的考虑点其它服务是在线的业务统计是离线业务尽量不占用他们的 db 资源
// 统计服务从建议使用从库或者从 mysql 抽取到单独的 clickhouse 或者其它的大数据组件
@Resource
private TradeOrderApi tradeOrderApi;
private TradeOrderStatisticsService tradeOrderStatisticsService;
@Resource
private TradeAfterSaleApi tradeAfterSaleApi;
private AfterSaleStatisticsService afterSaleStatisticsService;
@Resource
private TradeBrokerageApi tradeBrokerageApi;
private BrokerageStatisticsService brokerageStatisticsService;
@Resource
private PayWalletApi payWalletApi;
private PayWalletStatisticsService payWalletStatisticsService;
@Override
public TradeStatisticsComparisonRespVO<TradeSummaryRespVO> getTradeSummaryComparison() {
// 昨天的数据
// 1.1 昨天的数据
TradeSummaryRespBO yesterdayData = getTradeSummaryByDays(-1);
// 前天的数据用于对照昨天的数据
// 1.2 前天的数据用于对照昨天的数据
TradeSummaryRespBO beforeYesterdayData = getTradeSummaryByDays(-2);
// 本月数据;
// 2.1 本月数据
TradeSummaryRespBO monthData = getTradeSummaryByMonths(0);
// 上月数据用于对照本月的数据
// 2.2 上月数据用于对照本月的数据
TradeSummaryRespBO lastMonthData = getTradeSummaryByMonths(-1);
// 转换返回
return TradeStatisticsConvert.INSTANCE.convert(yesterdayData, beforeYesterdayData, monthData, lastMonthData);
}
@ -76,6 +68,11 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
return TradeStatisticsConvert.INSTANCE.convert(value, reference);
}
@Override
public Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeStatisticsMapper.selectExpensePriceByTimeBetween(beginTime, endTime);
}
@Override
public List<TradeTrendSummaryRespVO> getTradeStatisticsList(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeStatisticsMapper.selectListByTimeBetween(beginTime, endTime);
@ -83,31 +80,31 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
@Override
public String statisticsYesterdayTrade() {
// 处理统计参数
LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
// 统计
// 1.1 统计订单
StopWatch stopWatch = new StopWatch("交易统计");
stopWatch.start("统计订单");
TradeOrderSummaryRespDTO orderSummary = tradeOrderApi.getOrderSummary(beginTime, endTime);
TradeOrderSummaryRespBO orderSummary = tradeOrderStatisticsService.getOrderSummary(beginTime, endTime);
stopWatch.stop();
// 1.2 统计售后
stopWatch.start("统计售后");
AfterSaleSummaryRespDTO afterSaleSummary = tradeAfterSaleApi.getAfterSaleSummary(beginTime, endTime);
AfterSaleSummaryRespBO afterSaleSummary = afterSaleStatisticsService.getAfterSaleSummary(beginTime, endTime);
stopWatch.stop();
// 1.3 统计佣金
stopWatch.start("统计佣金");
Integer brokerageSettlementPrice = tradeBrokerageApi.getBrokerageSettlementPriceSummary(beginTime, endTime);
Integer brokerageSettlementPrice = brokerageStatisticsService.getBrokerageSettlementPriceSummary(beginTime, endTime);
stopWatch.stop();
// 1.4 统计充值
stopWatch.start("统计充值");
WalletSummaryRespBO walletSummary = payWalletStatisticsService.getWalletSummary(beginTime, endTime);
stopWatch.stop();
stopWatch.start("统计充值");
WalletSummaryRespDTO walletSummary = payWalletApi.getWalletSummary(beginTime, endTime);
stopWatch.stop();
// 插入数据
TradeStatisticsDO entity = TradeStatisticsConvert.INSTANCE.convert(yesterday, orderSummary, afterSaleSummary, brokerageSettlementPrice, walletSummary);
// 2. 插入数据
TradeStatisticsDO entity = TradeStatisticsConvert.INSTANCE.convert(yesterday, orderSummary, afterSaleSummary,
brokerageSettlementPrice, walletSummary);
tradeStatisticsMapper.insert(entity);
// 返回计时结果
return stopWatch.prettyPrint();
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.trade.api.aftersale.dto;
package cn.iocoder.yudao.module.statistics.service.trade.bo;
import lombok.Data;
@ -8,7 +8,7 @@ import lombok.Data;
* @author owen
*/
@Data
public class AfterSaleSummaryRespDTO {
public class AfterSaleSummaryRespBO {
/**
* 退款订单数

View File

@ -1,19 +1,19 @@
package cn.iocoder.yudao.module.trade.api.order.dto;
package cn.iocoder.yudao.module.statistics.service.trade.bo;
import lombok.Data;
/**
* 订单统计 Response DTO
* 订单统计 Response BO
*
* @author owen
*/
@Data
public class TradeOrderSummaryRespDTO {
public class TradeOrderSummaryRespBO {
/**
* 创建订单数
*/
private Long orderCreateCount;
private Integer orderCreateCount;
/**
* 支付订单商品数
*/

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.pay.api.wallet.dto;
package cn.iocoder.yudao.module.statistics.service.trade.bo;
import lombok.Data;
@ -8,7 +8,8 @@ import lombok.Data;
* @author owen
*/
@Data
public class WalletSummaryRespDTO {
public class WalletSummaryRespBO {
/**
* 总支付金额余额单位
*/

View File

@ -0,0 +1,22 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.infra.ApiAccessLogStatisticsMapper">
<select id="selectCountByIp" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM infra_api_access_log
WHERE create_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
GROUP BY user_ip
</select>
<select id="selectCountByUserId" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM infra_api_access_log
WHERE user_id > 0
AND create_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
GROUP BY user_id
</select>
</mapper>

View File

@ -0,0 +1,37 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.member.MemberStatisticsMapper">
<select id="selectSummaryListByAreaId"
resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO">
SELECT area_id, COUNT(1) AS userCount
FROM member_user
WHERE deleted = FALSE
GROUP BY area_id
<!-- TODO @疯狂order by 是不是交给内存哈 -->
ORDER BY userCount DESC
</select>
<select id="selectSummaryListBySex"
resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO">
SELECT sex, COUNT(1) AS userCount
FROM member_user
WHERE deleted = FALSE
GROUP BY sex
<!-- TODO @疯狂order by 是不是交给内存哈 -->
ORDER BY userCount DESC
</select>
<select id="selectUserCount" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM member_user
WHERE deleted = FALSE
<if test="beginTime != null">
AND create_time >= #{beginTime}
</if>
<if test="endTime != null">
AND create_time &lt;= #{endTime}
</if>
</select>
</mapper>

View File

@ -0,0 +1,48 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.pay.PayWalletStatisticsMapper">
<select id="selectRechargeSummaryByPayTimeBetween"
resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO">
SELECT COUNT(1) AS rechargePayCount,
SUM(pay_price) AS rechargePayPrice
FROM pay_wallet_recharge
WHERE pay_status = #{payStatus}
AND pay_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
<select id="selectRechargeSummaryByRefundTimeBetween"
resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO">
SELECT COUNT(1) AS rechargeRefundCount,
SUM(pay_price) AS rechargeRefundPrice
FROM pay_wallet_recharge
WHERE refund_status = #{refundStatus}
AND refund_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
<select id="selectPriceSummaryByBizTypeAndCreateTimeBetween" resultType="java.lang.Integer">
SELECT SUM(price)
FROM pay_wallet_transaction
WHERE biz_type = #{bizType}
AND create_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
<select id="selectRechargeSummaryGroupByWalletId"
resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO">
SELECT COUNT(1) AS rechargeUserCount,
SUM(pay_price) AS rechargePrice
FROM pay_wallet_recharge
WHERE pay_status = #{payStatus}
<if test="beginTime != null">
AND pay_time >= #{beginTime}
</if>
<if test="endTime != null">
AND pay_time &lt;= #{endTime}
</if>
AND deleted = FALSE
GROUP BY wallet_id
</select>
</mapper>

View File

@ -0,0 +1,14 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.trade.AfterSaleStatisticsMapper">
<select id="selectSummaryByRefundTimeBetween"
resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO">
SELECT COUNT(1) AS afterSaleCount,
SUM(refund_price) AS afterSaleRefundPrice
FROM trade_after_sale
WHERE refund_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
</mapper>

View File

@ -0,0 +1,14 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.trade.BrokerageStatisticsMapper">
<select id="selectSummaryPriceByStatusAndUnfreezeTimeBetween" resultType="java.lang.Integer">
SELECT SUM(price)
FROM trade_brokerage_record
WHERE biz_type = #{bizType}
AND status = #{status}
AND unfreeze_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
</mapper>

View File

@ -0,0 +1,64 @@
<?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.iocoder.yudao.module.statistics.dal.mysql.trade.TradeOrderStatisticsMapper">
<select id="selectSummaryListByAreaId"
resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO">
SELECT receiver_area_id AS areaId,
(SELECT COUNT(1)
FROM trade_order AS s
WHERE s.receiver_area_id = m.receiver_area_id) AS orderCreateCount,
(SELECT COUNT(1)
FROM trade_order AS s
WHERE s.receiver_area_id = m.receiver_area_id
AND s.pay_status = TRUE
AND s.deleted = FALSE) AS orderPayCount,
(SELECT SUM(s.pay_price)
FROM trade_order AS s
WHERE s.receiver_area_id = m.receiver_area_id
AND s.pay_status = TRUE
AND s.deleted = FALSE) AS orderPayPrice
FROM trade_order m
WHERE deleted = FALSE
GROUP BY receiver_area_id
</select>
<select id="selectUserCountByCreateTimeBetween" resultType="java.lang.Integer">
SELECT COUNT(DISTINCT(user_id))
FROM trade_order
WHERE deleted = FALSE
AND create_time BETWEEN #{beginTime} AND #{endTime}
</select>
<select id="selectUserCountByPayTimeBetween" resultType="java.lang.Integer">
SELECT COUNT(DISTINCT(user_id))
FROM trade_order
WHERE pay_time BETWEEN #{beginTime} AND #{endTime}
AND pay_status = TRUE
AND deleted = FALSE
</select>
<select id="selectCountByCreateTimeBetween" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM trade_order
WHERE create_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
<select id="selectCountByPayTimeBetween" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM trade_order
WHERE pay_status = TRUE
AND create_time BETWEEN #{beginTime} AND #{endTime}
AND deleted = FALSE
</select>
<select id="selectSummaryPriceByPayTimeBetween" resultType="java.lang.Integer">
SELECT SUM(pay_price)
FROM trade_order AS s
WHERE s.pay_status = TRUE
AND deleted = FALSE
AND create_time BETWEEN #{beginTime} AND #{endTime}
</select>
</mapper>

View File

@ -37,4 +37,17 @@
GROUP BY date
</select>
<select id="selectExpensePriceByTimeBetween" resultType="java.lang.Integer">
SELECT -- 支出金额 = 余额支付金额 + 支付佣金金额 + 商品退款金额
SUM(order_wallet_pay_price + brokerage_settlement_price + after_sale_refund_price) AS expensePrice
FROM trade_statistics
WHERE deleted = FALSE
<if test="beginTime != null">
AND time >= #{beginTime}
</if>
<if test="endTime != null">
AND time &lt;= #{endTime}
</if>
</select>
</mapper>

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.trade.api.aftersale;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import java.time.LocalDateTime;
/**
* 售后 API 接口
*
* @author owen
*/
public interface TradeAfterSaleApi {
/**
* 获取售后单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 售后统计结果
*/
AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.trade.api.order;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -30,25 +28,7 @@ public interface TradeOrderApi {
*/
TradeOrderRespDTO getOrder(Long id);
/**
* 获取订单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 订单统计结果
*/
TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 更新拼团相关信息到订单
*
* @param orderId 订单编号
* @param activityId 拼团活动编号
* @param combinationRecordId 拼团记录编号
* @param headId 团长编号
*/
void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId);
// TODO 芋艿需要优化下
/**
* 取消支付订单
*

View File

@ -33,7 +33,8 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态");
ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货");
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.module.trade.api.aftersale;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 售后 API 接口实现类
*
* @author owen
*/
@Service
@Validated
public class TradeAfterSaleApiImpl implements TradeAfterSaleApi {
@Resource
private AfterSaleService afterSaleService;
@Override
public AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return afterSaleService.getAfterSaleSummary(beginTime, endTime);
}
}

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.module.trade.api.brokerage;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 订单 API 接口实现类
*
* @author HUIHUI
*/
@Service
@Validated
public class TradeBrokerageApiImpl implements TradeBrokerageApi {
@Resource
private BrokerageRecordService brokerageRecordService;
@Override
public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageRecordService.getBrokerageSettlementPriceSummary(beginTime, endTime);
}
}

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.trade.api.order;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
@ -9,7 +8,6 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -22,10 +20,10 @@ import java.util.List;
@Validated
public class TradeOrderApiImpl implements TradeOrderApi {
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Override
public List<TradeOrderRespDTO> getOrderList(Collection<Long> ids) {
@ -37,16 +35,6 @@ public class TradeOrderApiImpl implements TradeOrderApi {
return TradeOrderConvert.INSTANCE.convert(tradeOrderQueryService.getOrder(id));
}
@Override
public TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeOrderQueryService.getOrderSummary(beginTime, endTime);
}
@Override
public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) {
tradeOrderUpdateService.updateOrderCombinationInfo(orderId, activityId, combinationRecordId, headId);
}
@Override
public void cancelPaidOrder(Long userId, Long orderId) {
tradeOrderUpdateService.cancelPaidOrder(userId, orderId);

View File

@ -1,19 +1,14 @@
package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yulichang.toolkit.MPJWrappers;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
@Mapper
@ -53,12 +48,4 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> {
.in(AfterSaleDO::getStatus, statuses));
}
default AfterSaleSummaryRespDTO selectSummaryByRefundTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<AfterSaleDO>lambdaJoin()
.selectCount(AfterSaleDO::getId, AfterSaleSummaryRespDTO::getAfterSaleCount)
.selectSum(AfterSaleDO::getRefundPrice, AfterSaleSummaryRespDTO::getAfterSaleRefundPrice)
.between(AfterSaleDO::getRefundTime, beginTime, endTime)), 0),
AfterSaleSummaryRespDTO.class);
}
}

View File

@ -1,8 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@ -111,13 +109,4 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
default Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(Integer bizType, Integer status,
LocalDateTime beginTime, LocalDateTime endTime) {
return Convert.toInt(CollUtil.getFirst(selectObjs(MPJWrappers.<BrokerageRecordDO>lambdaJoin()
.selectSum(BrokerageRecordDO::getPrice)
.eq(BrokerageRecordDO::getBizType, bizType)
.eq(BrokerageRecordDO::getStatus, status)
.between(BrokerageRecordDO::getUnfreezeTime, beginTime, endTime))), 0);
}
}

View File

@ -1,16 +1,12 @@
package cn.iocoder.yudao.module.trade.dal.mysql.order;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yulichang.toolkit.MPJWrappers;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@ -93,21 +89,21 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
.eq(TradeOrderDO::getSeckillActivityId, seckillActivityId));
}
default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin()
.selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount)
.selectSum(TradeOrderDO::getPayPrice, TradeOrderSummaryRespDTO::getOrderPayPrice)
.between(TradeOrderDO::getPayTime, beginTime, endTime)), 0),
TradeOrderSummaryRespDTO.class);
}
default Long selectCountByCreateTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
return selectCount(new LambdaQueryWrapperX<TradeOrderDO>()
.between(TradeOrderDO::getCreateTime, beginTime, endTime));
}
default TradeOrderDO selectOneByPickUpVerifyCode(String pickUpVerifyCode) {
return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
}
// TODO @hui999是不是只针对 combinationActivityId 的查询呀
default TradeOrderDO selectByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
.and(q -> q.eq(TradeOrderDO::getUserId, userId)
.eq(TradeOrderDO::getStatus, status))
.and(q -> q.eq(TradeOrderDO::getCombinationActivityId, activityId)
.or()
.eq(TradeOrderDO::getSeckillActivityId, activityId)
.or()
.eq(TradeOrderDO::getBargainActivityId, activityId))
);
}
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
@ -10,8 +9,6 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCre
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
import java.time.LocalDateTime;
/**
* 售后订单 Service 接口
*
@ -127,13 +124,4 @@ public interface AfterSaleService {
*/
Long getApplyingAfterSaleCount(Long userId);
/**
* 获取售后单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 售后统计结果
*/
AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
@ -411,9 +410,4 @@ public class AfterSaleServiceImpl implements AfterSaleService {
return tradeAfterSaleMapper.selectCountByUserIdAndStatus(userId, AfterSaleStatusEnum.APPLYING_STATUSES);
}
@Override
public AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return tradeAfterSaleMapper.selectSummaryByRefundTimeBetween(beginTime,endTime);
}
}

View File

@ -156,13 +156,4 @@ public interface BrokerageRecordService {
*/
AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long userId, Long spuId);
/**
* 获取已结算的佣金金额
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 已结算的佣金金额
*/
Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -356,13 +356,6 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
return respVO;
}
@Override
public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageRecordMapper.selectSummaryPriceByStatusAndUnfreezeTimeBetween(
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
beginTime, endTime);
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*

View File

@ -1,14 +1,12 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -40,6 +38,16 @@ public interface TradeOrderQueryService {
*/
TradeOrderDO getOrder(Long userId, Long id);
/**
* 获得指定用户指定活动指定状态的交易订单
*
* @param userId 用户编号
* @param activityId 活动编号
* @param status 订单状态
* @return 交易订单
*/
TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status);
/**
* 获得订单列表
*
@ -95,7 +103,7 @@ public interface TradeOrderQueryService {
/**
* 会员在指定秒杀活动下用户购买的商品数量
*
* @param userId 用户编号
* @param userId 用户编号
* @param activityId 活动编号
* @return 秒杀商品数量
*/
@ -138,13 +146,4 @@ public interface TradeOrderQueryService {
*/
List<TradeOrderItemDO> getOrderItemListByOrderId(Collection<Long> orderIds);
/**
* 获取订单统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 订单统计结果
*/
TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -7,7 +7,6 @@ import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@ -25,7 +24,6 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -72,6 +70,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
return order;
}
@Override
public TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
return tradeOrderMapper.selectByUserIdAndActivityIdAndStatus(userId, activityId, status);
}
@Override
public List<TradeOrderDO> getOrderList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
@ -183,13 +186,6 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
.setPhone(receiverMobile));
}
@Override
public TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
TradeOrderSummaryRespDTO dto = tradeOrderMapper.selectSummaryByPayTimeBetween(beginTime, endTime);
dto.setOrderCreateCount(tradeOrderMapper.selectCountByCreateTimeBetween(beginTime, endTime));
return dto;
}
// =================== Order Item ===================

View File

@ -14,23 +14,12 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@ -40,7 +29,6 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@ -48,15 +36,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
@ -68,13 +52,15 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@ -109,30 +95,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private DeliveryExpressService deliveryExpressService;
@Resource
private TradeMessageService tradeMessageService;
@Resource
private BrokerageUserService brokerageUserService;
@Resource
private BrokerageRecordService brokerageRecordService;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private PayOrderApi payOrderApi;
@Resource
private AddressApi addressApi;
@Resource
private CouponApi couponApi;
@Resource
private CombinationRecordApi combinationRecordApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberPointApi memberPointApi;
@Resource
private ProductCommentApi productCommentApi;
@Resource
@ -199,7 +167,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
List<TradeOrderItemDO> orderItems = buildTradeOrderItems(order, calculateRespBO);
// 2. 订单创建前的逻辑
beforeCreateTradeOrder(order, orderItems);
tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
// 3. 保存订单
tradeOrderMapper.insert(order);
@ -234,11 +202,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile());
order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码长度为 8
}
// 设置订单推广人
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
order.setBrokerageUserId(brokerageUser.getBindUserId());
}
return order;
}
@ -247,21 +210,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
}
/**
* 订单创建前执行前置逻辑
*
* @param order 订单
* @param orderItems 订单项
*/
private void beforeCreateTradeOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 1. 执行订单创建前置处理器
// TODO @puhui999这里有个纠结点handler 的定义是只处理指定类型的订单的拓展逻辑还是通用的 handler类似可以处理优惠劵等等
tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
// 2. 下单时扣减商品库存
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
}
/**
* 订单创建后执行后置逻辑
* <p>
@ -276,27 +224,16 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 1. 执行订单创建后置处理器
tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems));
// 2. 有使用优惠券时更新
// 不在前置扣减的原因是因为优惠劵要记录使用的订单号
if (order.getCouponId() != null) {
couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
.setOrderId(order.getId()));
}
// 3. 扣减积分抵扣
// 不在前置扣减的原因是因为积分扣减时需要记录关联业务
reduceUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
// 4. 删除购物车商品
// 2. 删除购物车商品
Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
if (CollUtil.isNotEmpty(cartIds)) {
cartService.deleteCart(order.getUserId(), cartIds);
}
// 5. 生成预支付
// 3. 生成预支付
createPayOrder(order, orderItems);
// 6. 插入订单日志
// 4. 插入订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
@ -330,18 +267,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 3订单支付成功后
// 3. 执行 TradeOrderHandler 的后置处理
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
// 4.1 增加用户积分赠送
addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId());
// 4.2 增加用户经验
getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
// 4.3 增加用户佣金
getSelf().addBrokerageAsync(order.getUserId(), order.getId());
// 5. 记录订单日志
// 4. 记录订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
}
@ -434,8 +364,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build());
// 4. 发送站内信
tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO().setOrderId(order.getId())
.setUserId(order.getUserId()).setMessage(null));
tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO()
.setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
}
/**
@ -448,18 +378,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
*/
private TradeOrderDO validateOrderDeliverable(Long id) {
TradeOrderDO order = validateOrderExists(id);
// 校验订单是否退款
// 1. 校验订单是否未发货
if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
}
// 订单类型拼团
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 校验订单拼团是否成功
if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
}
}
// 2. 执行 TradeOrderHandler 前置处理
tradeOrderHandlers.forEach(handler -> handler.beforeDeliveryOrder(order));
return order;
}
@ -616,30 +541,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
* @param cancelType 取消类型
*/
private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
Long id = order.getId();
// 1. 更新 TradeOrderDO 状态为已取消
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
.setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now()));
if (updateCount == 0) {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
}
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
// 3. 回滚库存
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
// 3.1 活动相关的回滚
tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, orderItems));
// 2. 执行 TradeOrderHandler 的后置处理
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
// 4. 回滚优惠券
if (order.getCouponId() != null && order.getCouponId() > 0) {
couponApi.returnUsedCoupon(order.getCouponId());
}
// 5. 回滚积分抵扣的
addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
// 6. 增加订单日志
// 3. 增加订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
}
@ -660,8 +574,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
// 2. 退还优惠券
couponApi.returnUsedCoupon(order.getCouponId());
// 2. 执行 TradeOrderHandler 的后置处理
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
}
@Override
@ -742,9 +657,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
// 校验交易订单
TradeOrderDO order = validateOrderExists(reqVO.getId());
// 发货后不允许修改
// TODO @puhui999只有待发货可以执行 update
if (TradeOrderStatusEnum.isDelivered(order.getStatus())) {
// 只有待发货状态才可以修改订单收货地址
if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {
throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
}
@ -790,13 +704,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
// 1. 更新订单项
// 1.1 更新订单项
updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
// 2.1 更新订单的退款金额积分
// 1.2 执行 TradeOrderHandler 的后置处理
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
tradeOrderHandlers.forEach(handler -> handler.afterCancelOrderItem(order, orderItem));
// 2.1 更新订单的退款金额积分
Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ?
@ -807,23 +723,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
// 2.2 如果全部退款则进行取消订单
getSelf().cancelOrderByAfterSale(order, orderRefundPrice);
// 3. 回滚库存
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
// 3.1 活动相关的回滚
tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, Collections.singletonList(orderItem)));
// 4.1 回滚积分扣减用户积分赠送的
reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
// 4.2 回滚积分增加用户积分返还抵扣
addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
// 5. 回滚经验扣减用户经验
getSelf().reduceUserExperienceAsync(order.getUserId(), refundPrice, orderItem.getAfterSaleId());
// 6. 回滚佣金更新分佣记录为已失效
getSelf().cancelBrokerageAsync(order.getUserId(), id);
}
@Override
@ -840,6 +739,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
if (updateCount <= 0) {
throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
}
}
/**
@ -923,11 +823,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelPaidOrder(Long userId, Long orderId) {
// TODO 芋艿这里实现要优化下
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
}
@ -940,7 +840,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT)
public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) {
// 1. 查询未评论的订单项
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(order.getId(), Boolean.FALSE);
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(
order.getId(), Boolean.FALSE);
if (CollUtil.isEmpty(orderItems)) {
return;
}
@ -983,73 +884,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// =================== 营销相关的操作 ===================
@Async
protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) {
int bizType = MemberExperienceBizTypeEnum.ORDER.getType();
memberLevelApi.addExperience(userId, payPrice, bizType, String.valueOf(orderId));
}
@Async
protected void reduceUserExperienceAsync(Long userId, Integer refundPrice, Long afterSaleId) {
int bizType = MemberExperienceBizTypeEnum.REFUND.getType();
memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
}
/**
* 添加用户积分
* <p>
* 目前是支付成功后就会创建积分记录
* <p>
* 业内还有两种做法可以根据自己的业务调整
* 1. 确认收货后才创建积分记录
* 2. 支付 or 下单成功时创建积分记录冻结确认收货解冻或者 n 天后解冻
*
* @param userId 用户编号
* @param point 增加积分数量
* @param bizType 业务编号
* @param bizId 业务编号
*/
protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
if (point != null && point > 0) {
memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
}
}
protected void reduceUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
if (point != null && point > 0) {
memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
}
}
/**
* 创建分销记录
* <p>
* 目前是支付成功后就会创建分销记录
* <p>
* 业内还有两种做法可以根据自己的业务调整
* 1. 确认收货后才创建分销记录
* 2. 支付 or 下单成功时创建分销记录冻结确认收货解冻或者 n 天后解冻
*
* @param userId 用户编号
* @param orderId 订单编号
*/
@Async
protected void addBrokerageAsync(Long userId, Long orderId) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
Assert.notNull(user);
// 每一个订单项都会去生成分销记录
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
List<BrokerageAddReqBO> addList = convertList(orderItems,
item -> TradeOrderConvert.INSTANCE.convert(user, item,
productSpuApi.getSpu(item.getSpuId()), productSkuApi.getSku(item.getSkuId())));
brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
}
@Async
protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*

View File

@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 砍价订单 handler 实现类
*
* @author HUIHUI
*/
@Component
public class TradeBargainHandler implements TradeOrderHandler {
@Resource
private BargainActivityApi bargainActivityApi;
@Resource
private BargainRecordApi bargainRecordApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 扣减砍价活动的库存
bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
-orderItems.get(0).getCount());
}
@Override
public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 记录砍价记录对应的订单编号
bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
}
@Override
public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// TODO 芋艿取消订单时需要增加库存
}
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 砍价订单的 {@link TradeOrderHandler} 实现类
*
* @author HUIHUI
*/
@Component
public class TradeBargainOrderHandler implements TradeOrderHandler {
@Resource
private BargainActivityApi bargainActivityApi;
@Resource
private BargainRecordApi bargainRecordApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
// 扣减砍价活动的库存
bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
-orderItems.get(0).getCount());
}
@Override
public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
// 记录砍价记录对应的订单编号
bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
// 售后的订单项已经在 afterCancelOrderItem 回滚库存所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
afterCancelOrderItem(order, orderItems.get(0));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
if (!TradeOrderTypeEnum.isBargain(order.getType())) {
return;
}
// 恢复增加砍价活动的库存
bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), orderItem.getCount());
}
}

View File

@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* 订单分销的 {@link TradeOrderHandler} 实现类
*
* @author 芋道源码
*/
@Component
public class TradeBrokerageOrderHandler implements TradeOrderHandler {
@Resource
private MemberUserApi memberUserApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private BrokerageRecordService brokerageRecordService;
@Resource
private BrokerageUserService brokerageUserService;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 设置订单推广人
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
order.setBrokerageUserId(brokerageUser.getBindUserId());
}
}
@Override
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (order.getBrokerageUserId() == null) {
return;
}
addBrokerage(order.getUserId(), orderItems);
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 如果是未支付的订单不会产生分销结果所以直接 return
if (!order.getPayStatus()) {
return;
}
if (order.getBrokerageUserId() == null) {
return;
}
// 售后的订单项已经在 afterCancelOrderItem 回滚库存所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
orderItems.forEach(orderItem -> afterCancelOrderItem(order, orderItem));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
if (order.getBrokerageUserId() == null) {
return;
}
cancelBrokerage(order.getId(), orderItem.getOrderId());
}
/**
* 创建分销记录
* <p>
* 目前是支付成功后就会创建分销记录
* <p>
* 业内还有两种做法可以根据自己的业务调整
* 1. 确认收货后才创建分销记录
* 2. 支付 or 下单成功时创建分销记录冻结确认收货解冻或者 n 天后解冻
*
* @param userId 用户编号
* @param orderItems 订单项
*/
protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
Assert.notNull(user);
ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
Assert.notNull(spu);
ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
// 每一个订单项都会去生成分销记录
List<BrokerageAddReqBO> addList = convertList(orderItems,
item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
}
protected void cancelBrokerage(Long userId, Long orderItemId) {
brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
}
}

View File

@ -1,61 +0,0 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 拼团订单 handler 接口实现类
*
* @author HUIHUI
*/
@Component
public class TradeCombinationHandler implements TradeOrderHandler {
@Resource
private CombinationRecordApi combinationRecordApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 如果不是拼团订单则结束
if (TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
// 校验是否满足拼团活动相关限制
TradeOrderItemDO item = orderItems.get(0);
combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
order.getCombinationHeadId(), item.getSkuId(), item.getCount());
// TODO @puhui999这里还要限制下是不是已经 createOrder就是还没支付的时候重复下单了需要校验下不然的话一个拼团可以下多个单子了
}
@Override
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 如果不是拼团订单则结束
if (TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
// 获取商品信息
TradeOrderItemDO item = orderItems.get(0);
// 创建拼团记录
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));
}
@Override
public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
}
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS;
/**
* 拼团订单的 {@link TradeOrderHandler} 实现类
*
* @author HUIHUI
*/
@Component
public class TradeCombinationOrderHandler implements TradeOrderHandler {
@Resource
private TradeOrderUpdateService orderUpdateService;
@Resource
private TradeOrderQueryService orderQueryService;
@Resource
private CombinationRecordApi combinationRecordApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 如果不是拼团订单则结束
if (!TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
// 1. 校验是否满足拼团活动相关限制
TradeOrderItemDO item = orderItems.get(0);
combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
order.getCombinationHeadId(), item.getSkuId(), item.getCount());
// 2. 校验该用户是否存在未支付的拼团活动订单避免一个拼团可以下多个单子了
// TODO @puhui999只校验未支付的拼团订单噢
TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus(
order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus());
if (activityOrder != null) {
throw exception(ORDER_CREATE_FAIL_EXIST_UNPAID);
}
}
@Override
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 1.如果不是拼团订单则结束
if (!TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
// 2. 创建拼团记录
TradeOrderItemDO item = orderItems.get(0);
KeyValue<Long, Long> recordIdAndHeadId = combinationRecordApi.createCombinationRecord(
TradeOrderConvert.INSTANCE.convert(order, item));
// 3. 更新拼团相关信息到订单
// TODO 芋艿只需要更新 record
orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(),
recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue());
}
@Override
public void beforeDeliveryOrder(TradeOrderDO order) {
if (!TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
// 校验订单拼团是否成功
if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
}
}
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 优惠劵的 {@link TradeOrderHandler} 实现类
*
* @author 芋道源码
*/
@Component
public class TradeCouponOrderHandler implements TradeOrderHandler {
@Resource
private CouponApi couponApi;
@Override
public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (order.getCouponId() == null || order.getCouponId() <= 0) {
return;
}
// 不在前置扣减的原因是因为优惠劵要记录使用的订单号
couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
.setOrderId(order.getId()));
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (order.getCouponId() == null || order.getCouponId() <= 0) {
return;
}
// 退回优惠劵
couponApi.returnUsedCoupon(order.getCouponId());
}
}

View File

@ -0,0 +1,120 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
/**
* 会员积分等级的 {@link TradeOrderHandler} 实现类
*
* @author owen
*/
@Component
public class TradeMemberPointOrderHandler implements TradeOrderHandler {
@Resource
private MemberPointApi memberPointApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private AfterSaleService afterSaleService;
@Override
public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 扣减用户积分订单抵扣不在前置扣减的原因是因为积分扣减时需要记录关联业务
reducePoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
}
@Override
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 增加用户积分订单赠送
addPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE,
order.getId());
// 增加用户经验
memberLevelApi.addExperience(order.getUserId(), order.getPayPrice(),
MemberExperienceBizTypeEnum.ORDER_GIVE.getType(), String.valueOf(order.getId()));
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 售后的订单项已经在 afterCancelOrderItem 回滚库存所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
// 增加回滚用户积分订单抵扣
Integer usePoint = getSumValue(orderItems, TradeOrderItemDO::getUsePoint, Integer::sum);
addPoint(order.getUserId(), usePoint, MemberPointBizTypeEnum.ORDER_USE_CANCEL,
order.getId());
// 如下的返还需要经过支持也就是经历 afterPayOrder 流程
if (!order.getPayStatus()) {
return;
}
// 扣减回滚积分订单赠送
Integer givePoint = getSumValue(orderItems, TradeOrderItemDO::getGivePoint, Integer::sum);
reducePoint(order.getUserId(), givePoint, MemberPointBizTypeEnum.ORDER_GIVE_CANCEL,
order.getId());
// 扣减回滚用户经验
int payPrice = order.getPayPrice() - order.getRefundPrice();
memberLevelApi.addExperience(order.getUserId(), payPrice,
MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL.getType(), String.valueOf(order.getId()));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
// 扣减回滚积分订单赠送
reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM,
orderItem.getId());
// 增加回滚积分订单抵扣
addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM,
orderItem.getId());
// 扣减回滚用户经验
AfterSaleDO afterSale = afterSaleService.getAfterSale(orderItem.getAfterSaleId());
memberLevelApi.reduceExperience(order.getUserId(), afterSale.getRefundPrice(),
MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL_ITEM.getType(), String.valueOf(orderItem.getId()));
}
/**
* 添加用户积分
* <p>
* 目前是支付成功后就会创建积分记录
* <p>
* 业内还有两种做法可以根据自己的业务调整
* 1. 确认收货后才创建积分记录
* 2. 支付 or 下单成功时创建积分记录冻结确认收货解冻或者 n 天后解冻
*
* @param userId 用户编号
* @param point 增加积分数量
* @param bizType 业务编号
* @param bizId 业务编号
*/
protected void addPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
if (point != null && point > 0) {
memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
}
}
protected void reducePoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
if (point != null && point > 0) {
memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
}
}
}

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
import java.util.List;
@ -35,16 +37,42 @@ public interface TradeOrderHandler {
* @param order 订单
* @param orderItems 订单项
*/
default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
}
default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
/**
* 订单取消
* 订单取消
*
* @param order 订单
* @param orderItems 订单项
*/
default void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
default void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
/**
* 订单项取消后
*
* @param order 订单
* @param orderItem 订单项
*/
default void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {}
/**
* 订单发货前
*
* @param order 订单
*/
default void beforeDeliveryOrder(TradeOrderDO order) {}
// ========== 公用方法 ==========
/**
* 过滤未售后的订单项列表
*
* @param orderItems 订单项列表
* @return 过滤后的订单项列表
*/
default List<TradeOrderItemDO> filterOrderItemListByNoneAfterSale(List<TradeOrderItemDO> orderItems) {
return CollectionUtils.filterList(orderItems,
item -> TradeOrderItemAfterSaleStatusEnum.isNone(item.getAfterSaleStatus()));
}
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static java.util.Collections.singletonList;
/**
* 商品 SKU 库存的 {@link TradeOrderHandler} 实现类
*
* @author 芋道源码
*/
@Component
public class TradeProductSkuOrderHandler implements TradeOrderHandler {
@Resource
private ProductSkuApi productSkuApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 售后的订单项已经在 afterCancelOrderItem 回滚库存所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(singletonList(orderItem)));
}
}

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 秒杀订单 handler 实现类
*
* @author HUIHUI
*/
@Component
public class TradeSeckillHandler implements TradeOrderHandler {
@Resource
private SeckillActivityApi seckillActivityApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isSeckill(order.getType())) {
return;
}
// 扣减秒杀活动的库存
seckillActivityApi.updateSeckillStock(order.getSeckillActivityId(),
orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
}
@Override
public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (TradeOrderTypeEnum.isSeckill(order.getType())) {
return;
}
}
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 秒杀订单的 {@link TradeOrderHandler} 实现类
*
* @author HUIHUI
*/
@Component
public class TradeSeckillOrderHandler implements TradeOrderHandler {
@Resource
private SeckillActivityApi seckillActivityApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
// 扣减秒杀活动的库存
seckillActivityApi.updateSeckillStockDecr(order.getSeckillActivityId(),
orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
// 售后的订单项已经在 afterCancelOrderItem 回滚库存所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
afterCancelOrderItem(order, orderItems.get(0));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
return;
}
// 恢复秒杀活动的库存
seckillActivityApi.updateSeckillStockIncr(order.getSeckillActivityId(),
orderItem.getSkuId(), orderItem.getCount());
}
}

View File

@ -28,4 +28,14 @@ public interface MemberLevelApi {
*/
void addExperience(Long userId, Integer experience, Integer bizType, String bizId);
/**
* 扣减会员经验
*
* @param userId 会员ID
* @param experience 经验
* @param bizType 业务类型 {@link MemberExperienceBizTypeEnum}
* @param bizId 业务编号
*/
void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId);
}

View File

@ -20,10 +20,11 @@ public enum MemberExperienceBizTypeEnum {
*/
ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true),
INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true),
ORDER(2, "下单奖励", "下单获得 {} 经验", true),
REFUND(3, "退单扣除", "退单获得 {} 经验", false),
SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true),
LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true),
ORDER_GIVE(11, "下单奖励", "下单获得 {} 经验", true),
ORDER_GIVE_CANCEL(12, "下单奖励(整单取消)", "取消订单获得 {} 经验", false), // ORDER_GIVE 的取消
ORDER_GIVE_CANCEL_ITEM(13, "下单奖励(单个退款)", "退款订单获得 {} 经验", false), // ORDER_GIVE 的取消
;
/**

View File

@ -18,11 +18,14 @@ public enum MemberPointBizTypeEnum implements IntArrayValuable {
SIGN(1, "签到", "签到获得 {} 积分", true),
ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时赠送积分
ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时退回积分
ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时扣减积分
AFTER_SALE_REFUND_USED(13, "订单退款", "订单退款,退还 {} 积分", true), // 售后订单成功时退回积分对应 ORDER_USE 操作
AFTER_SALE_DEDUCT_GIVE(14, "订单退款", "订单退款,扣除赠送的 {} 积分", false), // 售后订单成功时扣减积分对应 ORDER_GIVE 操作
ORDER_USE(11, "订单积分抵扣", "下单使用 {} 积分", false), // 下单时扣减积分
ORDER_USE_CANCEL(12, "订单积分抵扣(整单取消)", "订单取消,退还 {} 积分", true), // ORDER_USE 的取消
ORDER_USE_CANCEL_ITEM(13, "订单积分抵扣(单个退款)", "订单退款,退还 {} 积分", true), // ORDER_USE 的取消
ORDER_GIVE(21, "订单积分奖励", "下单获得 {} 积分", true), // 支付订单时赠送积分
ORDER_GIVE_CANCEL(22, "订单积分奖励(整单取消)", "订单取消,退还 {} 积分", false), // ORDER_GIVE 的取消
ORDER_GIVE_CANCEL_ITEM(23, "订单积分奖励(单个退款)", "订单退款,扣除赠送的 {} 积分", false) // ORDER_GIVE 的取消
;
/**

View File

@ -38,4 +38,9 @@ public class MemberLevelApiImpl implements MemberLevelApi {
memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId);
}
@Override
public void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId) {
addExperience(userId, -experience, bizType, bizId);
}
}

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.pay.api.wallet;
import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
import java.time.LocalDateTime;
/**
* 钱包 API 接口
*
* @author owen
*/
public interface PayWalletApi {
/**
* 获取钱包统计
*
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 钱包统计
*/
WalletSummaryRespDTO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime);
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.pay.api.wallet;
import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 钱包 API 接口实现类
*
* @author owen
*/
@Service
@Validated
public class PayWalletApiImpl implements PayWalletApi {
@Resource
private PayWalletRechargeService payWalletRechargeService;
@Resource
private PayWalletTransactionService payWalletTransactionService;
@Override
public WalletSummaryRespDTO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime) {
WalletSummaryRespDTO walletSummary = payWalletRechargeService.getWalletSummary(beginTime, endTime);
walletSummary.setOrderWalletPayPrice(payWalletTransactionService.getPriceSummary(PayWalletBizTypeEnum.PAYMENT, beginTime, endTime));
return walletSummary;
}
}

Some files were not shown because too many files have changed in this diff Show More