!685 完善遗留的问题

Merge pull request !685 from puhui999/feature/mall_product
This commit is contained in:
芋道源码 2023-10-21 11:31:56 +00:00 committed by Gitee
commit 3e570b8b89
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
40 changed files with 502 additions and 270 deletions

View File

@ -34,8 +34,9 @@ public interface ErrorCodeConstants {
// ========== 商品 SPU 1-008-005-000 ========== // ========== 商品 SPU 1-008-005-000 ==========
ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在"); ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下"); ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU【{}】不处于上架状态"); ErrorCode SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_005_002, "商品 SPU 保存失败,原因:优惠卷不存在");
ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态"); ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_003, "商品 SPU【{}】不处于上架状态");
ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_004, "商品 SPU 不处于回收站状态");
// ========== 商品 SKU 1-008-006-000 ========== // ========== 商品 SKU 1-008-006-000 ==========
ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在"); ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在");

View File

@ -28,6 +28,11 @@
<artifactId>yudao-module-member-api</artifactId> <artifactId>yudao-module-member-api</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-promotion-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>

View File

@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponTemplateApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -43,6 +45,9 @@ public class ProductSpuController {
@Resource @Resource
private ProductSkuService productSkuService; private ProductSkuService productSkuService;
@Resource
private CouponTemplateApi couponTemplateApi;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建商品 SPU") @Operation(summary = "创建商品 SPU")
@PreAuthorize("@ss.hasPermission('product:spu:create')") @PreAuthorize("@ss.hasPermission('product:spu:create')")
@ -87,7 +92,10 @@ public class ProductSpuController {
} }
// 查询商品 SKU // 查询商品 SKU
List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId()); List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespVO(spu, skus)); // 查询优惠卷
List<CouponTemplateRespDTO> couponTemplateList = couponTemplateApi.getCouponTemplateListByIds(
spu.getGiveCouponTemplateIds());
return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespVO(spu, skus, couponTemplateList));
} }
@GetMapping("/list-all-simple") @GetMapping("/list-all-simple")

View File

@ -96,19 +96,31 @@ public class ProductSpuBaseVO {
@NotNull(message = "商品赠送积分不能为空") @NotNull(message = "商品赠送积分不能为空")
private Integer giveIntegral; private Integer giveIntegral;
@Schema(description = "赠送的优惠劵编号的数组", example = "[1, 10]") // TODO 这块前端还未实现 @Schema(description = "赠送的优惠劵数组包含优惠券编号和名称")
private List<Long> giveCouponTemplateIds; private List<GiveCouponTemplate> giveCouponTemplates;
@Schema(description = "分销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @Schema(description = "分销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "商品分销类型不能为空") @NotNull(message = "商品分销类型不能为空")
private Boolean subCommissionType; private Boolean subCommissionType;
@Schema(description = "活动展示顺序", example = "[1, 3, 2, 4, 5]") // TODO 这块前端还未实现 @Schema(description = "活动展示顺序", example = "[1, 3, 2, 4, 5]")
private List<Integer> activityOrders; private List<Integer> activityOrders;
// ========== 统计相关字段 ========= // ========== 统计相关字段 =========
@Schema(description = "虚拟销量", example = "芋道") @Schema(description = "虚拟销量", example = "66")
private Integer virtualSalesCount; private Integer virtualSalesCount;
@Schema(description = "管理后台 - 商品 SPU 赠送的优惠卷")
@Data
public static class GiveCouponTemplate {
@Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
private String name;
}
} }

View File

@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.enums.DictTypeConstants; import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Named; import org.mapstruct.Named;
@ -100,10 +101,14 @@ public interface ProductSpuConvert {
List<AppProductSpuDetailRespVO.Sku> convertListForGetSpuDetail(List<ProductSkuDO> skus); List<AppProductSpuDetailRespVO.Sku> convertListForGetSpuDetail(List<ProductSkuDO> skus);
default ProductSpuDetailRespVO convertForSpuDetailRespVO(ProductSpuDO spu, List<ProductSkuDO> skus) { List<ProductSpuDetailRespVO.GiveCouponTemplate> convertList04(List<CouponTemplateRespDTO> couponTemplateList);
ProductSpuDetailRespVO detailRespVO = convert03(spu);
detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skus)); default ProductSpuDetailRespVO convertForSpuDetailRespVO(ProductSpuDO spu, List<ProductSkuDO> skus,
return detailRespVO; List<CouponTemplateRespDTO> couponTemplateList) {
ProductSpuDetailRespVO respVO = convert03(spu);
respVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skus));
respVO.setGiveCouponTemplates(convertList04(couponTemplateList));
return respVO;
} }
default List<ProductSpuDetailRespVO> convertForSpuDetailRespListVO(List<ProductSpuDO> spus, List<ProductSkuDO> skus) { default List<ProductSpuDetailRespVO> convertForSpuDetailRespListVO(List<ProductSpuDO> spus, List<ProductSkuDO> skus) {

View File

@ -194,7 +194,7 @@ public class ProductSpuDO extends BaseDO {
* 对应 PromotionTypeEnum 枚举 * 对应 PromotionTypeEnum 枚举
*/ */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private List<Integer> activityOrders; private List<Integer> activityOrders; // TODO @芋艿 活动顺序字段长度需要增加
// ========== 统计相关字段 ========= // ========== 统计相关字段 =========

View File

@ -16,8 +16,10 @@ import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.product.service.brand.ProductBrandService; import cn.iocoder.yudao.module.product.service.brand.ProductBrandService;
import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponTemplateApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -26,10 +28,10 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMinValue; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.CATEGORY_LEVEL; import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.CATEGORY_LEVEL;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
@ -52,9 +54,10 @@ public class ProductSpuServiceImpl implements ProductSpuService {
private ProductBrandService brandService; private ProductBrandService brandService;
@Resource @Resource
private ProductCategoryService categoryService; private ProductCategoryService categoryService;
@Resource @Resource
@Lazy // 循环依赖避免报错 @Lazy
private ProductPropertyValueService productPropertyValueService; private CouponTemplateApi couponTemplateApi;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -62,6 +65,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 校验分类品牌 // 校验分类品牌
validateCategory(createReqVO.getCategoryId()); validateCategory(createReqVO.getCategoryId());
brandService.validateProductBrand(createReqVO.getBrandId()); brandService.validateProductBrand(createReqVO.getBrandId());
// 校验优惠券
Set<Long> giveCouponTemplateIds = convertSet(createReqVO.getGiveCouponTemplates(), ProductSpuCreateReqVO.GiveCouponTemplate::getId);
validateCouponTemplate(giveCouponTemplateIds);
// 校验 SKU // 校验 SKU
List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = createReqVO.getSkus(); List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = createReqVO.getSkus();
productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType()); productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType());
@ -69,6 +75,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO); ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO);
// 初始化 SPU SKU 相关属性 // 初始化 SPU SKU 相关属性
initSpuFromSkus(spu, skuSaveReqList); initSpuFromSkus(spu, skuSaveReqList);
// 设置优惠券
spu.setGiveCouponTemplateIds(CollUtil.newArrayList(giveCouponTemplateIds));
// 插入 SPU // 插入 SPU
productSpuMapper.insert(spu); productSpuMapper.insert(spu);
// 插入 SKU // 插入 SKU
@ -85,6 +93,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 校验分类品牌 // 校验分类品牌
validateCategory(updateReqVO.getCategoryId()); validateCategory(updateReqVO.getCategoryId());
brandService.validateProductBrand(updateReqVO.getBrandId()); brandService.validateProductBrand(updateReqVO.getBrandId());
// 校验优惠券
Set<Long> giveCouponTemplateIds = convertSet(updateReqVO.getGiveCouponTemplates(), ProductSpuUpdateReqVO.GiveCouponTemplate::getId);
validateCouponTemplate(giveCouponTemplateIds);
// 校验SKU // 校验SKU
List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = updateReqVO.getSkus(); List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = updateReqVO.getSkus();
productSkuService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType()); productSkuService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType());
@ -92,6 +103,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 更新 SPU // 更新 SPU
ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO); ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO);
initSpuFromSkus(updateObj, skuSaveReqList); initSpuFromSkus(updateObj, skuSaveReqList);
// 设置优惠券
updateObj.setGiveCouponTemplateIds(CollUtil.newArrayList(giveCouponTemplateIds));
productSpuMapper.updateById(updateObj); productSpuMapper.updateById(updateObj);
// 批量更新 SKU // 批量更新 SKU
productSkuService.updateSkuList(updateObj.getId(), updateReqVO.getSkus()); productSkuService.updateSkuList(updateObj.getId(), updateReqVO.getSkus());
@ -125,6 +138,10 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 默认商品浏览量 // 默认商品浏览量
spu.setBrowseCount(0); spu.setBrowseCount(0);
} }
// 如果活动顺序为空则默认初始化
if (CollUtil.isEmpty(spu.getActivityOrders())) {
spu.setActivityOrders(Arrays.stream(PromotionTypeEnum.ARRAYS).boxed().collect(Collectors.toList()));
}
} }
/** /**
@ -140,6 +157,13 @@ public class ProductSpuServiceImpl implements ProductSpuService {
} }
} }
private void validateCouponTemplate(Collection<Long> ids) {
List<CouponTemplateRespDTO> couponTemplateList = couponTemplateApi.getCouponTemplateListByIds(ids);
if (couponTemplateList.size() != ids.size()) {
throw exception(SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS);
}
}
@Override @Override
public List<ProductSpuDO> validateSpuList(Collection<Long> ids) { public List<ProductSpuDO> validateSpuList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.promotion.api.coupon;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import java.util.Collection;
import java.util.List;
/**
* 优惠劵模版 API 接口
*
* @author HUIHUI
*/
public interface CouponTemplateApi {
/**
* 获得优惠券模版的精简信息列表
*
* @param ids 优惠券模版编号
* @return 优惠券模版的精简信息列表
*/
List<CouponTemplateRespDTO> getCouponTemplateListByIds(Collection<Long> ids);
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.promotion.api.coupon.dto;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import lombok.Data;
/**
* 优惠券模版 Response DTO
*
* @author HUIHUI
*/
@Data
public class CouponTemplateRespDTO {
/**
* 模板编号自增唯一
*/
private Long id;
/**
* 优惠劵名
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.promotion.enums.banner;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import cn.iocoder.yudao.module.promotion.enums.bargain.BargainRecordStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* Banner Position 枚举
*
* @author HUIHUI
*/
@AllArgsConstructor
@Getter
public enum BannerPositionEnum implements IntArrayValuable {
HOME_POSITION(1, "首页"),
SECKILL_POSITION(2, "秒杀活动页"),
COMBINATION_POSITION(3, "砍价活动页"),
DISCOUNT_POSITION(4, "限时折扣页"),
REWARD_POSITION(5, "满减送页");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BargainRecordStatusEnum::getStatus).toArray();
/**
*
*/
private final Integer position;
/**
* 名字
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.promotion.api.coupon;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 优惠劵模版 API 接口实现类
*
* @author HUIHUI
*/
@Service
public class CouponTemplateApiImpl implements CouponTemplateApi {
@Resource
private CouponTemplateService couponTemplateService;
@Override
public List<CouponTemplateRespDTO> getCouponTemplateListByIds(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) { // 防御一下
return Collections.emptyList();
}
return CouponTemplateConvert.INSTANCE.convertList(couponTemplateService.getCouponTemplateListByIds(ids));
}
}

View File

@ -27,6 +27,10 @@ public class BannerBaseVO {
@NotNull(message = "图片地址不能为空") @NotNull(message = "图片地址不能为空")
private String picUrl; private String picUrl;
@Schema(description = "position", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "position 不能为空")
private Integer position;
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "排序不能为空") @NotNull(message = "排序不能为空")
private Integer sort; private Integer sort;

View File

@ -25,7 +25,6 @@ public class BannerPageReqVO extends PageParam {
@Schema(description = "标题") @Schema(description = "标题")
private String title; private String title;
@Schema(description = "状态") @Schema(description = "状态")
@InEnum(CommonStatusEnum.class) @InEnum(CommonStatusEnum.class)
private Integer status; private Integer status;

View File

@ -56,13 +56,11 @@ public class AppArticleController {
return success(ArticleConvert.INSTANCE.convert01(articleService.getArticle(id))); return success(ArticleConvert.INSTANCE.convert01(articleService.getArticle(id)));
} }
// TODO @puhui999add-browse-count 前端 uniapp 也要接下就是打开文章的时候调用下这个接口 @PutMapping("/add-browse-count")
@PutMapping("/add-browseCount")
@Operation(summary = "增加文章浏览量") @Operation(summary = "增加文章浏览量")
@Parameter(name = "id", description = "文章编号", example = "1024") @Parameter(name = "id", description = "文章编号", example = "1024")
public CommonResult<Boolean> addBrowseCount(@RequestParam("id") Long id) { public CommonResult<Boolean> addBrowseCount(@RequestParam("id") Long id) {
// TODO @puhui999addArticleBrowseCount articleService.addArticleBrowseCount(id);
articleService.addBrowseCount(id);
return success(true); return success(true);
} }

View File

@ -2,18 +2,23 @@ package cn.iocoder.yudao.module.promotion.controller.app.banner;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.app.banner.vo.AppBannerRespVO; import cn.iocoder.yudao.module.promotion.controller.app.banner.vo.AppBannerRespVO;
import cn.iocoder.yudao.module.promotion.convert.banner.BannerConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import cn.iocoder.yudao.module.promotion.service.banner.BannerService;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList; import javax.annotation.Resource;
import java.time.Duration;
import java.util.List; import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
@RestController @RestController
@RequestMapping("/promotion/banner") @RequestMapping("/promotion/banner")
@ -21,22 +26,39 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Validated @Validated
public class AppBannerController { public class AppBannerController {
@Resource
private BannerService bannerService;
/**
* {@link AppBannerRespVO} 缓存通过它异步刷新 {@link #getBannerList0(Integer)} 所要的首页数据
*/
private final LoadingCache<Integer, List<AppBannerRespVO>> bannerListCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
new CacheLoader<Integer, List<AppBannerRespVO>>() {
@Override
public List<AppBannerRespVO> load(Integer position) {
return getBannerList0(position);
}
});
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "获得 banner 列表") @Operation(summary = "获得 banner 列表")
// todo @芋艿swagger 注解待补全 @Parameter(name = "position", description = "Banner position", example = "1")
// TODO @芋艿可以增加缓存提升性能
// TODO @芋艿position = 1 首页position = 10 拼团活动页
public CommonResult<List<AppBannerRespVO>> getBannerList(@RequestParam("position") Integer position) { public CommonResult<List<AppBannerRespVO>> getBannerList(@RequestParam("position") Integer position) {
List<AppBannerRespVO> bannerList = new ArrayList<>(); return success(bannerListCache.getUnchecked(position));
AppBannerRespVO banner1 = new AppBannerRespVO(); }
banner1.setUrl("https://www.example.com/link1");
banner1.setPicUrl("https://api.java.crmeb.net/crmebimage/public/content/2022/08/04/0f78716213f64bfa83f191d51a832cbf73f6axavoy.jpg"); private List<AppBannerRespVO> getBannerList0(Integer position) {
bannerList.add(banner1); List<BannerDO> bannerList = bannerService.getBannerListByPosition(position);
AppBannerRespVO banner2 = new AppBannerRespVO(); return BannerConvert.INSTANCE.convertList01(bannerList);
banner2.setUrl("https://www.example.com/link2"); }
banner2.setPicUrl("https://api.java.crmeb.net/crmebimage/public/content/2023/01/11/be09e755268b43ee90b0db3a3e1b7132r7a6t2wvsm.jpg");
bannerList.add(banner2); @PutMapping("/add-browse-count")
return success(bannerList); @Operation(summary = "增加 Banner 点击量")
@Parameter(name = "id", description = "Banner 编号", example = "1024")
public CommonResult<Boolean> addBrowseCount(@RequestParam("id") Long id) {
bannerService.addBannerBrowseCount(id);
return success(true);
} }
} }

View File

@ -9,6 +9,13 @@ import javax.validation.constraints.NotNull;
@Data @Data
public class AppBannerRespVO { public class AppBannerRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Long id;
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "标题不能为空")
private String title;
@Schema(description = "跳转链接", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "跳转链接", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "跳转链接不能为空") @NotNull(message = "跳转链接不能为空")
private String url; private String url;

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.banner.vo.AppBannerRespVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -25,4 +26,6 @@ public interface BannerConvert {
BannerDO convert(BannerUpdateReqVO updateReqVO); BannerDO convert(BannerUpdateReqVO updateReqVO);
List<AppBannerRespVO> convertList01(List<BannerDO> bannerList);
} }

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.convert.coupon;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO;
@ -58,4 +59,6 @@ public interface CouponTemplateConvert {
} }
} }
List<CouponTemplateRespDTO> convertList(List<CouponTemplateDO> list);
} }

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.banner; package cn.iocoder.yudao.module.promotion.dal.dataobject.banner;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.promotion.enums.banner.BannerPositionEnum;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
@ -40,14 +42,25 @@ public class BannerDO extends BaseDO {
private Integer sort; private Integer sort;
/** /**
* 状态 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} * 状态 {@link CommonStatusEnum}
*/ */
private Integer status; private Integer status;
/**
* 定位 {@link BannerPositionEnum}
*/
private Integer position;
/** /**
* 备注 * 备注
*/ */
private String memo; private String memo;
// TODO 芋艿 点击次数&& 其他数据相关 /**
* 点击次数
*/
private Integer browseCount;
// TODO 芋艿 其他数据相关
} }

View File

@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/** /**
* Banner Mapper * Banner Mapper
* *
@ -23,4 +26,14 @@ public interface BannerMapper extends BaseMapperX<BannerDO> {
.orderByDesc(BannerDO::getSort)); .orderByDesc(BannerDO::getSort));
} }
default void updateBrowseCount(Long id) {
update(null, new LambdaUpdateWrapper<BannerDO>()
.eq(BannerDO::getId, id)
.setSql("browse_count = browse_count + 1"));
}
default List<BannerDO> selectBannerListByPosition(Integer position) {
return selectList(new LambdaQueryWrapperX<BannerDO>().eq(BannerDO::getPosition, position));
}
} }

View File

@ -86,7 +86,6 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
.last("LIMIT " + count)); .last("LIMIT " + count));
} }
// TODO @puhui999是不是返回 BargainActivityDO 更干净哈分组后返回 DO 的话需要联表查询
/** /**
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号 * 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号
* *
@ -102,7 +101,6 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
.groupBy("spu_id")); .groupBy("spu_id"));
} }
// TODO @puhui999是不是只要 endTime 小于就可以啦
/** /**
* 获取指定活动编号的活动列表且 * 获取指定活动编号的活动列表且
* 开始时间和结束时间小于给定时间 dateTime 的活动列表 * 开始时间和结束时间小于给定时间 dateTime 的活动列表
@ -115,7 +113,7 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
return selectList(new LambdaQueryWrapperX<BargainActivityDO>() return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
.in(BargainActivityDO::getId, ids) .in(BargainActivityDO::getId, ids)
.lt(BargainActivityDO::getStartTime, dateTime) .lt(BargainActivityDO::getStartTime, dateTime)
.lt(BargainActivityDO::getEndTime, dateTime) .gt(BargainActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByDesc(BargainActivityDO::getCreateTime)); .orderByDesc(BargainActivityDO::getCreateTime));
} }

View File

@ -71,7 +71,7 @@ public interface CombinationActivityMapper extends BaseMapperX<CombinationActivi
return selectList(new LambdaQueryWrapperX<CombinationActivityDO>() return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
.in(CombinationActivityDO::getId, ids) .in(CombinationActivityDO::getId, ids)
.lt(CombinationActivityDO::getStartTime, dateTime) .lt(CombinationActivityDO::getStartTime, dateTime)
.lt(CombinationActivityDO::getEndTime, dateTime) .gt(CombinationActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByDesc(CombinationActivityDO::getCreateTime)); .orderByDesc(CombinationActivityDO::getCreateTime));
} }

View File

@ -8,10 +8,11 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -39,7 +40,11 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
.orderByDesc(CouponTemplateDO::getId)); .orderByDesc(CouponTemplateDO::getId));
} }
void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount); default void updateTakeCount(Long id, Integer incrCount) {
update(null, new LambdaUpdateWrapper<CouponTemplateDO>()
.eq(CouponTemplateDO::getId, id)
.setSql("take_count = take_count + " + incrCount));
}
default List<CouponTemplateDO> selectListByTakeType(Integer takeType) { default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
return selectList(CouponTemplateDO::getTakeType, takeType); return selectList(CouponTemplateDO::getTakeType, takeType);
@ -70,4 +75,8 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
return canTakeConsumer; return canTakeConsumer;
} }
default List<CouponTemplateDO> selectListByIds(Collection<Long> ids) {
return selectList(new LambdaQueryWrapperX<CouponTemplateDO>().in(CouponTemplateDO::getId, ids));
}
} }

View File

@ -103,7 +103,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
return selectList(new LambdaQueryWrapperX<SeckillActivityDO>() return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
.in(SeckillActivityDO::getId, ids) .in(SeckillActivityDO::getId, ids)
.lt(SeckillActivityDO::getStartTime, dateTime) .lt(SeckillActivityDO::getStartTime, dateTime)
.lt(SeckillActivityDO::getEndTime, dateTime) .gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByDesc(SeckillActivityDO::getCreateTime)); .orderByDesc(SeckillActivityDO::getCreateTime));
} }

View File

@ -93,6 +93,6 @@ public interface ArticleService {
* *
* @param id 文章编号 * @param id 文章编号
*/ */
void addBrowseCount(Long id); void addArticleBrowseCount(Long id);
} }

View File

@ -111,7 +111,7 @@ public class ArticleServiceImpl implements ArticleService {
} }
@Override @Override
public void addBrowseCount(Long id) { public void addArticleBrowseCount(Long id) {
// 校验文章是否存在 // 校验文章是否存在
validateArticleExists(id); validateArticleExists(id);
// 增加浏览次数 // 增加浏览次数

View File

@ -46,12 +46,6 @@ public interface BannerService {
*/ */
BannerDO getBanner(Long id); BannerDO getBanner(Long id);
/**
* 获得所有 Banner列表
* @return Banner列表
*/
List<BannerDO> getBannerList();
/** /**
* 获得 Banner 分页 * 获得 Banner 分页
* *
@ -60,4 +54,19 @@ public interface BannerService {
*/ */
PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO); PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO);
/**
* 增加 Banner 点击量
*
* @param id Banner编号
*/
void addBannerBrowseCount(Long id);
/**
* 获得 Banner 列表
*
* @param position 定位
* @return Banner 列表
*/
List<BannerDO> getBannerListByPosition(Integer position);
} }

View File

@ -65,14 +65,22 @@ public class BannerServiceImpl implements BannerService {
return bannerMapper.selectById(id); return bannerMapper.selectById(id);
} }
@Override
public List<BannerDO> getBannerList() {
return bannerMapper.selectList();
}
@Override @Override
public PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO) { public PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO) {
return bannerMapper.selectPage(pageReqVO); return bannerMapper.selectPage(pageReqVO);
} }
@Override
public void addBannerBrowseCount(Long id) {
// 校验 Banner 是否存在
validateBannerExists(id);
// 增加点击次数
bannerMapper.updateBrowseCount(id);
}
@Override
public List<BannerDO> getBannerListByPosition(Integer position) {
return bannerMapper.selectBannerListByPosition(position);
}
} }

View File

@ -369,8 +369,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
keyValue.setValue(keyValue.getValue() + 1); keyValue.setValue(keyValue.getValue() + 1);
} }
} catch (Exception ignored) { // 处理异常继续循环 } catch (Exception ignored) { // 处理异常继续循环
// TODO @puhui999拼团过期 or 虚拟成团 可以改成 expireCombinationRecord因为找方法更容易一些 log.error("[expireCombinationRecord][record({}) 处理异常请进行处理record 数据是:{}]",
log.error("[拼团过期 or 虚拟成团][record({}) 处理异常请进行处理record 数据是:{}]",
record.getId(), JsonUtils.toJsonString(record)); record.getId(), JsonUtils.toJsonString(record));
} }
} }

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
@ -91,4 +92,12 @@ public interface CouponTemplateService {
List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope, List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
Long productScopeValue, Integer count); Long productScopeValue, Integer count);
/**
* 获得优惠券模版列表
*
* @param ids 优惠券模版编号
* @return 优惠券模版列表
*/
List<CouponTemplateDO> getCouponTemplateListByIds(Collection<Long> ids);
} }

View File

@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -126,4 +127,9 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count); return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count);
} }
@Override
public List<CouponTemplateDO> getCouponTemplateListByIds(Collection<Long> ids) {
return couponTemplateMapper.selectListByIds(ids);
}
} }

View File

@ -1,11 +0,0 @@
<?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.promotion.dal.mysql.coupon.CouponTemplateMapper">
<update id="updateTakeCount">
UPDATE promotion_coupon_template
SET take_count = take_count + #{incrCount}
WHERE id = #{id}
</update>
</mapper>

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.member.controller.app.signin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
// TODO @xiaqingsign-in
@Tag(name = "签到APP - 签到")
@RestController
@RequestMapping("/member/signin")
public class AppMemberSignInController {
@Resource
private MemberSignInRecordService signInRecordService;
// TODO @xiaqing泛型
// TODO @xiaqing合并到 AppMemberSignInRecordController getSignInRecordSummary 里哈
@Operation(summary = "个人签到信息")
@GetMapping("/get-summary")
public CommonResult getUserSummary() {
return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
}
}

View File

@ -31,22 +31,11 @@ public class AppMemberSignInRecordController {
@Resource @Resource
private MemberSignInRecordService signInRecordService; private MemberSignInRecordService signInRecordService;
// TODO 芋艿临时 mock => UserSignController.getUserInfo
@GetMapping("/get-summary") @GetMapping("/get-summary")
@Operation(summary = "获得个人签到统计") @Operation(summary = "获得个人签到统计")
@PreAuthenticated @PreAuthenticated
public CommonResult<AppMemberSignInRecordSummaryRespVO> getSignInRecordSummary() { public CommonResult<AppMemberSignInRecordSummaryRespVO> getSignInRecordSummary() {
AppMemberSignInRecordSummaryRespVO respVO = new AppMemberSignInRecordSummaryRespVO(); return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
if (false) {
respVO.setTotalDay(100);
respVO.setContinuousDay(5);
respVO.setTodaySignIn(true);
} else {
respVO.setTotalDay(100);
respVO.setContinuousDay(10);
respVO.setTodaySignIn(false);
}
return success(respVO);
} }
@PostMapping("/create") @PostMapping("/create")

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.member.controller.app.signin.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户签到积分 Response VO")
@Data
public class AppMemberSignInRecordRespVO {
@Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer day;
@Schema(description = "签到的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer point;
@Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer experience;
@Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.member.controller.app.signin.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户签到统计信息 Response VO")
@Data
public class AppMemberSignInSummaryRespVO {
@Schema(description = "持续签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer continuousDay;
@Schema(description = "总签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer totalDay;
@Schema(description = "当天是否签到", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
private Boolean todaySignIn ;
}

View File

@ -1,14 +1,19 @@
package cn.iocoder.yudao.module.member.convert.signin; package cn.iocoder.yudao.module.member.convert.signin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO;
import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO; import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -32,10 +37,37 @@ public interface MemberSignInRecordConvert {
memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname()))); memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname())));
return voPageResult; return voPageResult;
} }
PageResult<MemberSignInRecordRespVO> convertPage(PageResult<MemberSignInRecordDO> pageResult); PageResult<MemberSignInRecordRespVO> convertPage(PageResult<MemberSignInRecordDO> pageResult);
PageResult<AppMemberSignInRecordRespVO> convertPage02(PageResult<MemberSignInRecordDO> pageResult); PageResult<AppMemberSignInRecordRespVO> convertPage02(PageResult<MemberSignInRecordDO> pageResult);
AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO); AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO);
default MemberSignInRecordDO convert(Long userId, MemberSignInRecordDO firstRecord, List<MemberSignInConfigDO> signInConfigs) {
// 1. 计算今天是第几天签到
long day = ChronoUnit.DAYS.between(firstRecord.getCreateTime(), LocalDateTime.now());
// 2. 初始化签到信息
MemberSignInRecordDO signInRecord = new MemberSignInRecordDO().setUserId(userId)
.setDay(Integer.parseInt(Long.toString(day))) // 设置签到天数
.setPoint(0) // 设置签到积分默认为 0
.setExperience(0); // 设置签到经验默认为 0
// 3. 获取签到对应的积分数
MemberSignInConfigDO lastConfig = signInConfigs.get(signInConfigs.size() - 1); // 最大签到天数
if (day > lastConfig.getDay()) { // 超出范围按第一天的经验计算
signInRecord.setPoint(signInConfigs.get(0).getPoint());
signInRecord.setExperience(signInConfigs.get(0).getExperience());
return signInRecord;
}
MemberSignInConfigDO signInConfig = CollUtil.findOne(signInConfigs, config -> ObjUtil.equal(config.getDay(), day));
if (signInConfig == null) {
return signInRecord;
}
signInRecord.setPoint(signInConfig.getPoint());
signInRecord.setExperience(signInConfig.getExperience());
return signInRecord;
}
} }

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@ -35,7 +36,43 @@ public interface MemberSignInRecordMapper extends BaseMapperX<MemberSignInRecord
} }
//获取用户的签到记录列表信息,根据签到时间倒序 /**
* 获取用户最近的签到记录信息,根据签到时间倒序
*
* @param userId 用户编号
* @return 签到记录列表
*/
default MemberSignInRecordDO selectLastRecordByUserIdDesc(Long userId) {
return selectOne(new QueryWrapper<MemberSignInRecordDO>()
.eq("user_id", userId)
.orderByDesc("create_time")
.last("limit 1"));
}
/**
* 获取用户最早的签到记录信息,根据签到时间倒序
*
* @param userId 用户编号
* @return 签到记录列表
*/
default MemberSignInRecordDO selectLastRecordByUserIdAsc(Long userId) {
return selectOne(new QueryWrapper<MemberSignInRecordDO>()
.eq("user_id", userId)
.orderByAsc("create_time")
.last("limit 1"));
}
default Long selectCountByUserId(Long userId) {
return selectCount(new LambdaQueryWrapperX<MemberSignInRecordDO>()
.eq(MemberSignInRecordDO::getUserId, userId));
}
/**
* 获取用户的签到记录列表信息,根据签到时间倒序
*
* @param userId 用户编号
* @return 签到记录信息
*/
default List<MemberSignInRecordDO> selectListByUserId(Long userId) { default List<MemberSignInRecordDO> selectListByUserId(Long userId) {
return selectList(new LambdaQueryWrapperX<MemberSignInRecordDO>() return selectList(new LambdaQueryWrapperX<MemberSignInRecordDO>()
.eq(MemberSignInRecordDO::getUserId, userId) .eq(MemberSignInRecordDO::getUserId, userId)

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.member.service.signin;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO; import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordSummaryRespVO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
/** /**
@ -44,7 +44,7 @@ public interface MemberSignInRecordService {
* @param userId 用户编号 * @param userId 用户编号
* @return 个人签到统计信息 * @return 个人签到统计信息
*/ */
AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId); AppMemberSignInRecordSummaryRespVO getSignInRecordSummary(Long userId);
} }

View File

@ -1,19 +1,20 @@
package cn.iocoder.yudao.module.member.service.signin; package cn.iocoder.yudao.module.member.service.signin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi; 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.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO; import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordSummaryRespVO;
import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper; import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum; import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.level.MemberLevelService; import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
@ -21,17 +22,17 @@ import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_RECORD_TODAY_EXISTS;
/** /**
* 签到记录 Service 实现类 * 签到记录 Service 实现类
@ -45,7 +46,7 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
@Resource @Resource
private MemberSignInRecordMapper signInRecordMapper; private MemberSignInRecordMapper signInRecordMapper;
@Resource @Resource
private MemberSignInConfigMapper signInConfigMapper; private MemberSignInConfigService signInConfigService;
@Resource @Resource
private MemberPointRecordService pointRecordService; private MemberPointRecordService pointRecordService;
@Resource @Resource
@ -55,50 +56,64 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
private MemberUserApi memberUserApi; private MemberUserApi memberUserApi;
@Override @Override
public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) { public AppMemberSignInRecordSummaryRespVO getSignInRecordSummary(Long userId) {
AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO(); // 1. 初始化默认返回信息
AppMemberSignInRecordSummaryRespVO vo = new AppMemberSignInRecordSummaryRespVO();
vo.setTotalDay(0); vo.setTotalDay(0);
vo.setContinuousDay(0); vo.setContinuousDay(0);
vo.setTodaySignIn(false); vo.setTodaySignIn(false);
//获取用户签到的记录按照天数倒序获取
List<MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
// TODO @xiaqingif 空的时候直接 return这样括号少逻辑更简洁
if (!CollectionUtils.isEmpty(signInRecordDOList)) {
//设置总签到天数
vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing是不是不用读取 signInRecordDOList 所有的而是 count下然后另外再读取一条最后一条
//判断当天是否有签到复用校验方法
// TODO @xiaqing不要用异常实现逻辑还是判断哈
try {
validSignDay(signInRecordDOList.get(0));
vo.setTodaySignIn(false);
} catch (Exception e) {
vo.setTodaySignIn(true);
}
//如果当天签到了则说明连续签到天数有意义否则直接用默认值0
if (vo.getTodaySignIn()) {
//下方计算连续签到从2天开始此处直接设置一天连续签到
vo.setContinuousDay(1);
//判断连续签到天数
// TODO @xiaqing这里逻辑想想怎么在简化下可读性可以在提升下哈
for (int i = 1; i < signInRecordDOList.size(); i++) {
//前一天减1等于当前天数则说明连续继续循环
LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
LocalDate pre = signInRecordDOList.get(i - 1).getCreateTime().toLocalDate();
if (1 == daysBetween(cur, pre)) {
vo.setContinuousDay(i + 1);
} else {
break;
}
}
}
// 2. 获取用户签到的记录数
Long signCount = signInRecordMapper.selectCountByUserId(userId);
if (ObjUtil.equal(signCount, 0L)) {
return vo;
} }
vo.setTotalDay(signCount.intValue()); // 设置总签到天数
// 3. 校验当天是否有签到
MemberSignInRecordDO signInRecord = signInRecordMapper.selectLastRecordByUserIdDesc(userId);
if (signInRecord == null) {
return vo;
}
vo.setTodaySignIn(DateUtils.isToday(signInRecord.getCreateTime()));
// 4. 校验今天是否签到没有签到则直接返回
if (!vo.getTodaySignIn()) {
return vo;
}
// 4.1. 判断连续签到天数
List<MemberSignInRecordDO> signInRecords = signInRecordMapper.selectListByUserId(userId);
vo.setContinuousDay(calculateConsecutiveDays(signInRecords));
return vo; return vo;
} }
private long daysBetween(LocalDate date1, LocalDate date2) { /**
return ChronoUnit.DAYS.between(date1, date2); * 计算连续签到天数
*
* @param signInRecords 签到记录列表
* @return int 连续签到天数
*/
public int calculateConsecutiveDays(List<MemberSignInRecordDO> signInRecords) {
int consecutiveDays = 1; // 初始连续天数为1
LocalDate previousDate = null;
for (MemberSignInRecordDO record : signInRecords) {
LocalDate currentDate = record.getCreateTime().toLocalDate();
if (previousDate != null) {
// 检查相邻两个日期是否连续
if (currentDate.minusDays(1).isEqual(previousDate)) {
consecutiveDays++;
} else {
// 如果日期不连续停止遍历
break;
}
}
previousDate = currentDate;
}
return consecutiveDays;
} }
@Override @Override
@ -108,7 +123,7 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
if (StringUtils.isNotBlank(pageReqVO.getNickname())) { if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
// 如果查询用户结果为空直接返回无需继续查询 // 如果查询用户结果为空直接返回无需继续查询
if (CollectionUtils.isEmpty(users)) { if (CollUtil.isEmpty(users)) {
return PageResult.empty(); return PageResult.empty();
} }
userIds = convertSet(users, MemberUserRespDTO::getId); userIds = convertSet(users, MemberUserRespDTO::getId);
@ -125,73 +140,40 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public MemberSignInRecordDO createSignRecord(Long userId) { public MemberSignInRecordDO createSignRecord(Long userId) {
// 获取当前用户签到的最大天数 // 1. 获取当前用户最近的签到
// TODO @xiaqingdb 操作dou封装到 mapper MemberSignInRecordDO lastRecord = signInRecordMapper.selectLastRecordByUserIdDesc(userId);
// TODO @xiaqingmaxSignDay是不是变量叫 lastRecord 会更容易理解哈 // 1.1. 判断是否重复签到
MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX<MemberSignInRecordDO>() validateSigned(lastRecord);
.eq(MemberSignInRecordDO::getUserId, userId)
.orderByDesc(MemberSignInRecordDO::getDay)
.last("limit 1"));
// 判断是否重复签到
validSignDay(maxSignDay);
// 1. 查询出当前签到的天数 // 2. 获取当前用户最早的一次前端记录用于计算今天是第几天签到
MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing应该使用 record 变量会更合适 MemberSignInRecordDO firstRecord = signInRecordMapper.selectLastRecordByUserIdAsc(userId);
sign.setDay(1); // 设置签到初始化天数 // 2.1. 获取所有的签到规则
sign.setPoint(0); // 设置签到积分默认为 0 List<MemberSignInConfigDO> signInConfigs = signInConfigService.getSignInConfigList(CommonStatusEnum.ENABLE.getStatus());
sign.setExperience(0); // 设置签到经验默认为 0 signInConfigs.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
// 如果不为空则修改当前签到对应的天数 // 2.2. 组合数据
// TODO @xiaqing应该是要判断连续哈就是昨天 MemberSignInRecordDO record = MemberSignInRecordConvert.INSTANCE.convert(userId, firstRecord, signInConfigs);
if (maxSignDay != null) {
sign.setDay(maxSignDay.getDay() + 1);
}
// 2. 获取签到对应的积分数
// 获取所有的签到规则按照天数排序只获取启用的 TODO @xiaqing不要使用 signInConfigMapper 直接查询而是要通过 SigninConfigService
List<MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX<MemberSignInConfigDO>()
.eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
.orderByAsc(MemberSignInConfigDO::getDay));
// 如果签到的天数大于最大启用的规则天数直接给最大签到的积分数
// TODO @xiaqing超过最大配置的天数应该直接重置到第一天哈
MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
if (sign.getDay() > lastConfig.getDay()) {
sign.setPoint(lastConfig.getPoint());
sign.setExperience(lastConfig.getExperience());
} else {
configDOList.forEach(el -> {
// 循环匹配对应天数设置对应积分数
// TODO @xiaqing使用 equals另外这种不应该去遍历比较从可读性来说应该 CollUtil.findOne()
if (el.getDay() == sign.getDay()) {
sign.setPoint(el.getPoint());
sign.setExperience(el.getExperience());
}
});
}
// 3. 插入签到记录 // 3. 插入签到记录
signInRecordMapper.insert(sign); signInRecordMapper.insert(record);
// 4. 增加积分 // 4. 增加积分
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) { if (!ObjectUtils.equalsAny(record.getPoint(), null, 0)) {
pointRecordService.createPointRecord(userId, sign.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(sign.getId())); pointRecordService.createPointRecord(userId, record.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(record.getId()));
} }
// 5. 增加经验 // 5. 增加经验
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) { if (!ObjectUtils.equalsAny(record.getExperience(), null, 0)) {
memberLevelService.addExperience(userId, sign.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(sign.getId())); memberLevelService.addExperience(userId, record.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(record.getId()));
} }
return sign; return record;
} }
// TODO @xiaqing校验使用 validate 动词哈可以改成 validateSigned private void validateSigned(MemberSignInRecordDO signInRecordDO) {
private void validSignDay(MemberSignInRecordDO signInRecordDO) { if (signInRecordDO == null) {
// TODO @xiaqing代码格式if () {} 要有括号哈
if (signInRecordDO == null)
return; return;
// TODO @xiaqing可以直接使用 DateUtils.isToday() }
LocalDate today = LocalDate.now(); if (DateUtils.isToday(signInRecordDO.getCreateTime())) {
if (today.equals(signInRecordDO.getCreateTime().toLocalDate())) { throw exception(SIGN_IN_RECORD_TODAY_EXISTS);
throw exception(ErrorCodeConstants.SIGN_IN_RECORD_TODAY_EXISTS);
} }
} }