记得上下班打卡 | git大法好,push需谨慎

Commit c304cebb authored by wangyifan's avatar wangyifan

艺人专题:B端partner相关接口

parent 931332ff
-- 正在现场 v1.8.0 商城艺人专题
-- 关联开发文档:docu/zhengzai-v1.8.0-艺人专题-开发文档.md
-- 艺人关联申请表
CREATE TABLE `goblin_artist_auth_apply` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`apply_id` varchar(64) NOT NULL COMMENT '申请ID',
`store_id` varchar(64) NOT NULL COMMENT '申请店铺ID',
`artist_id` varchar(64) NOT NULL COMMENT '申请关联艺人ID,对应 kylin_artist.artist_id',
`attachment_urls` text NOT NULL COMMENT '申请资料图片URL列表,JSON数组',
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0待审核 1已通过 2已拒绝 3已撤销',
`reviewer_id` varchar(64) DEFAULT NULL COMMENT '审核人ID',
`reviewer_name` varchar(100) DEFAULT NULL COMMENT '审核人名称',
`review_time` datetime DEFAULT NULL COMMENT '审核时间',
`review_remark` varchar(500) DEFAULT NULL COMMENT '通过备注',
`reject_reason` varchar(500) DEFAULT NULL COMMENT '驳回原因',
`auth_expire_at` datetime DEFAULT NULL COMMENT '授权截止时间(通过时写入)',
`created_at` datetime NOT NULL COMMENT '申请时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_apply_id` (`apply_id`),
KEY `idx_store_status` (`store_id`, `status`),
KEY `idx_artist_status` (`artist_id`, `status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='艺人关联申请表';
-- 店铺-艺人授权关系表
CREATE TABLE `goblin_artist_auth` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`auth_id` varchar(64) NOT NULL COMMENT '授权ID',
`store_id` varchar(64) NOT NULL COMMENT '店铺ID',
`artist_id` varchar(64) NOT NULL COMMENT '艺人ID',
`apply_id` varchar(64) NOT NULL COMMENT '来源申请ID',
`expire_at` datetime NOT NULL COMMENT '授权截止时间',
`status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1有效 0失效',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_auth_id` (`auth_id`),
UNIQUE KEY `uk_store_artist` (`store_id`, `artist_id`),
KEY `idx_expire_at` (`expire_at`),
KEY `idx_store_status` (`store_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='店铺艺人授权关系表';
-- 艺人专题表
CREATE TABLE `goblin_artist_topic` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`topic_id` varchar(64) NOT NULL COMMENT '专题ID',
`store_id` varchar(64) NOT NULL COMMENT '所属店铺ID',
`topic_name` varchar(100) NOT NULL COMMENT '专题名称',
`artist_id` varchar(64) DEFAULT NULL COMMENT '关联艺人ID,可空',
`template_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1双列网格 2单列列表',
`banner_url` varchar(500) DEFAULT NULL COMMENT '专题banner',
`banner_height` int(11) NOT NULL DEFAULT 240 COMMENT 'banner高度px',
`on_sale_start` datetime DEFAULT NULL COMMENT '上架区间开始',
`on_sale_end` datetime DEFAULT NULL COMMENT '上架区间结束',
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0待上架 1已上架 2已下架',
`sort` int(11) NOT NULL DEFAULT 0 COMMENT 'Admin排序值,越小越靠前',
`recommend_position` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0无推荐 1~5推荐位',
`del_flg` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0正常 1删除',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_topic_id` (`topic_id`),
KEY `idx_store_status` (`store_id`, `status`, `del_flg`),
KEY `idx_sort` (`sort`, `created_at`),
KEY `idx_recommend` (`recommend_position`),
KEY `idx_artist_id` (`artist_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='艺人专题表';
-- 艺人专题商品关联表
CREATE TABLE `goblin_artist_topic_goods` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`relation_id` varchar(64) NOT NULL COMMENT '关联ID',
`topic_id` varchar(64) NOT NULL COMMENT '专题ID',
`spu_id` varchar(64) NOT NULL COMMENT '本店商品SPU ID',
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '专题内排序,越小越靠前',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_relation_id` (`relation_id`),
UNIQUE KEY `uk_topic_spu` (`topic_id`, `spu_id`),
KEY `idx_topic_sort` (`topic_id`, `sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='艺人专题商品关联表';
package com.liquidnet.service.goblin.constant;
/**
* 艺人专题相关状态常量
*/
public final class GoblinArtistConst {
private GoblinArtistConst() {
}
/** 申请:待审核 */
public static final int APPLY_PENDING = 0;
/** 申请:已通过 */
public static final int APPLY_APPROVED = 1;
/** 申请:已拒绝 */
public static final int APPLY_REJECTED = 2;
/** 申请:已撤销 */
public static final int APPLY_REVOKED = 3;
/** 授权:有效 */
public static final int AUTH_VALID = 1;
/** 授权:失效 */
public static final int AUTH_INVALID = 0;
/** 专题:待上架 */
public static final int TOPIC_PENDING = 0;
/** 专题:已上架 */
public static final int TOPIC_ONLINE = 1;
/** 专题:已下架 */
public static final int TOPIC_OFFLINE = 2;
/** 模版:双列 */
public static final int TEMPLATE_GRID = 1;
/** 模版:单列 */
public static final int TEMPLATE_LIST = 2;
/** 未删除 */
public static final int DEL_NO = 0;
/** 已删除 */
public static final int DEL_YES = 1;
public static final int DEFAULT_BANNER_HEIGHT = 240;
}
...@@ -471,4 +471,38 @@ public class GoblinRedisConst { ...@@ -471,4 +471,38 @@ public class GoblinRedisConst {
*/ */
public static final String SQB_SKU_PRICE = PREFIX.concat("sqb:sku:price:"); public static final String SQB_SKU_PRICE = PREFIX.concat("sqb:sku:price:");
/* ----------------------------------------------------------------- */
/* 艺人专题 */
/* ----------------------------------------------------------------- */
/**
* 艺人专题信息
* {goblin:artist:topic:{store_id}_{topic_id}, GoblinArtistTopicVo}
*/
public static final String ARTIST_TOPIC = PREFIX.concat("artist:topic:");
/**
* 店铺艺人专题列表
* {goblin:artist:topic:list:{store_id}, List<GoblinArtistTopicVo>}
*/
public static final String ARTIST_TOPIC_LIST = PREFIX.concat("artist:topic:list:");
/**
* 艺人授权关系
* {goblin:artist:auth:{store_id}_{artist_id}, GoblinArtistAuthVo}
*/
public static final String ARTIST_AUTH = PREFIX.concat("artist:auth:");
/**
* 店铺艺人授权列表
* {goblin:artist:auth:list:{store_id}, List<GoblinArtistAuthVo>}
*/
public static final String ARTIST_AUTH_LIST = PREFIX.concat("artist:auth:list:");
/**
* 艺人关联申请
* {goblin:artist:apply:{store_id}_{apply_id}, GoblinArtistAuthApplyVo}
*/
public static final String ARTIST_APPLY = PREFIX.concat("artist:apply:");
} }
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@ApiModel("商家端-撤销艺人关联申请入参")
@Data
public class GoblinStoreMgtArtistApplyRevokeParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "申请ID")
@NotBlank(message = "申请ID不能为空")
private String applyId;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;
@ApiModel("商家端-提交艺人关联申请入参")
@Data
public class GoblinStoreMgtArtistApplySubmitParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "艺人ID")
@NotBlank(message = "艺人ID不能为空")
private String artistId;
@ApiModelProperty(required = true, value = "申请资料图片URL列表")
@NotEmpty(message = "申请资料不能为空")
@Size(max = 20, message = "申请资料数量超限")
private List<String> attachmentUrls;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("商家端-创建艺人专题入参")
@Data
public class GoblinStoreMgtArtistTopicCreateParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "专题名称")
@NotBlank(message = "专题名称不能为空")
@Size(max = 100, message = "专题名称长度超限")
private String topicName;
@ApiModelProperty(value = "关联艺人ID,非必填")
private String artistId;
@ApiModelProperty(value = "模版类型 1双列 2单列")
@NotNull(message = "模版类型不能为空")
private Integer templateType;
@ApiModelProperty(value = "专题banner")
private String bannerUrl;
@ApiModelProperty(value = "banner高度")
private Integer bannerHeight;
@ApiModelProperty(value = "上架开始时间")
private LocalDateTime onSaleStart;
@ApiModelProperty(value = "上架结束时间")
private LocalDateTime onSaleEnd;
@ApiModelProperty(value = "专题商品")
@Valid
private List<GoblinStoreMgtArtistTopicGoodsItemParams> goods;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@ApiModel("商家端-删除艺人专题入参")
@Data
public class GoblinStoreMgtArtistTopicDeleteParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "专题ID")
@NotBlank(message = "专题ID不能为空")
private String topicId;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("商家端-专题商品项入参")
@Data
public class GoblinStoreMgtArtistTopicGoodsItemParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "商品SPU ID")
@NotBlank(message = "商品SPU不能为空")
private String spuId;
@ApiModelProperty(required = true, value = "排序,越小越靠前")
@NotNull(message = "商品排序不能为空")
private Integer sort;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("商家端-艺人专题上下架入参")
@Data
public class GoblinStoreMgtArtistTopicStatusParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "专题ID")
@NotBlank(message = "专题ID不能为空")
private String topicId;
@ApiModelProperty(required = true, value = "状态 0待上架 1已上架 2已下架")
@NotNull(message = "状态不能为空")
private Integer status;
}
package com.liquidnet.service.goblin.dto.manage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("商家端-编辑艺人专题入参")
@Data
public class GoblinStoreMgtArtistTopicUpdateParams implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(required = true, value = "专题ID")
@NotBlank(message = "专题ID不能为空")
private String topicId;
@ApiModelProperty(required = true, value = "专题名称")
@NotBlank(message = "专题名称不能为空")
@Size(max = 100, message = "专题名称长度超限")
private String topicName;
@ApiModelProperty(value = "关联艺人ID,非必填;传空字符串表示清空")
private String artistId;
@ApiModelProperty(value = "模版类型 1双列 2单列")
@NotNull(message = "模版类型不能为空")
private Integer templateType;
@ApiModelProperty(value = "专题banner")
private String bannerUrl;
@ApiModelProperty(value = "banner高度")
private Integer bannerHeight;
@ApiModelProperty(value = "上架开始时间")
private LocalDateTime onSaleStart;
@ApiModelProperty(value = "上架结束时间")
private LocalDateTime onSaleEnd;
@ApiModelProperty(value = "专题商品,全量替换")
@Valid
private List<GoblinStoreMgtArtistTopicGoodsItemParams> goods;
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("商家端-艺人关联申请列表项")
@Data
public class GoblinStoreMgtArtistApplyListItemVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("申请ID")
private String applyId;
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("艺人名称")
private String artistName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("申请附件")
private List<String> attachmentUrls;
@ApiModelProperty("申请时间")
private LocalDateTime applyTime;
@ApiModelProperty("审核状态 0待审核 1已通过 2已拒绝 3已撤销")
private Integer status;
@ApiModelProperty("审核时间")
private LocalDateTime reviewTime;
@ApiModelProperty("审核人")
private String reviewerName;
@ApiModelProperty("授权截止时间")
private LocalDateTime authExpireAt;
@ApiModelProperty("驳回原因")
private String rejectReason;
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ApiModel("商家端-已授权艺人列表出参")
@Data
public class GoblinStoreMgtArtistAuthListVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("列表")
private List<Item> list = new ArrayList<>();
@ApiModel("已授权艺人项")
@Data
public static class Item implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("艺人名称")
private String artistName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("授权截止时间")
private LocalDateTime expireAt;
}
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel("商家端-搜索艺人列表项")
@Data
public class GoblinStoreMgtArtistSearchItemVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("艺人名称")
private String artistName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("艺人类型")
private Integer artistType;
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ApiModel("商家端-艺人专题详情出参")
@Data
public class GoblinStoreMgtArtistTopicDetailVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("专题ID")
private String topicId;
@ApiModelProperty("专题名称")
private String topicName;
@ApiModelProperty("关联艺人ID")
private String artistId;
@ApiModelProperty("关联艺人名称")
private String artistName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("模版类型")
private Integer templateType;
@ApiModelProperty("banner")
private String bannerUrl;
@ApiModelProperty("banner高度")
private Integer bannerHeight;
@ApiModelProperty("上架开始")
private LocalDateTime onSaleStart;
@ApiModelProperty("上架结束")
private LocalDateTime onSaleEnd;
@ApiModelProperty("状态")
private Integer status;
@ApiModelProperty("商品列表")
private List<GoodsItem> goods = new ArrayList<>();
@ApiModel("专题详情商品项")
@Data
public static class GoodsItem implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("SPU ID")
private String spuId;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("封面")
private String coverPic;
@ApiModelProperty("售价")
private BigDecimal sellPrice;
@ApiModelProperty("排序")
private Integer sort;
}
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@ApiModel("商家端-专题可添加商品搜索项")
@Data
public class GoblinStoreMgtArtistTopicGoodsSearchItemVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("SPU ID")
private String spuId;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("封面")
private String coverPic;
@ApiModelProperty("售价")
private BigDecimal sellPrice;
}
package com.liquidnet.service.goblin.dto.manage.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ApiModel("商家端-艺人专题分页出参")
@Data
public class GoblinStoreMgtArtistTopicPageVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("总数(当前筛选)")
private long total;
@ApiModelProperty("已上架数量")
private long countOnline;
@ApiModelProperty("待上架数量")
private long countPending;
@ApiModelProperty("已下架数量")
private long countOffline;
@ApiModelProperty("列表")
private List<Item> list = new ArrayList<>();
@ApiModel("艺人专题列表项")
@Data
public static class Item implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("专题ID")
private String topicId;
@ApiModelProperty("专题名称")
private String topicName;
@ApiModelProperty("关联艺人ID")
private String artistId;
@ApiModelProperty("关联艺人名称")
private String artistName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("关联商品数量")
private Integer goodsCount;
@ApiModelProperty("上架开始时间")
private LocalDateTime onSaleStart;
@ApiModelProperty("上架结束时间")
private LocalDateTime onSaleEnd;
@ApiModelProperty("状态 0待上架 1已上架 2已下架")
private Integer status;
}
}
package com.liquidnet.service.goblin.dto.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class GoblinArtistAuthApplyVo implements Serializable {
private static final long serialVersionUID = 1L;
private String applyId;
private String storeId;
private String artistId;
private String attachmentUrls;
private Integer status;
private String reviewerId;
private String reviewerName;
private LocalDateTime reviewTime;
private String reviewRemark;
private String rejectReason;
private LocalDateTime authExpireAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.dto.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class GoblinArtistAuthVo implements Serializable {
private static final long serialVersionUID = 1L;
private String authId;
private String storeId;
private String artistId;
private String applyId;
private LocalDateTime expireAt;
private Integer status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.dto.vo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class GoblinArtistTopicVo implements Serializable {
private static final long serialVersionUID = 1L;
private String topicId;
private String storeId;
private String topicName;
private String artistId;
private Integer templateType;
private String bannerUrl;
private Integer bannerHeight;
private LocalDateTime onSaleStart;
private LocalDateTime onSaleEnd;
private Integer status;
private Integer sort;
private Integer recommendPosition;
private Integer delFlg;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private List<GoodsItem> goods = new ArrayList<>();
@Data
public static class GoodsItem implements Serializable {
private static final long serialVersionUID = 1L;
private String spuId;
private Integer sort;
private String name;
private String coverPic;
private BigDecimal sellPrice;
}
}
package com.liquidnet.service.goblin.service.manage;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplyRevokeParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplySubmitParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicCreateParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicDeleteParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicStatusParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicUpdateParams;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistApplyListItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistAuthListVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicDetailVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicGoodsSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicPageVo;
/**
* 商家端-艺人关联申请 / 艺人专题
* <p>店铺上下文:登录 UID → Redis 店铺信息取 storeId(对齐售后单管理)</p>
*/
public interface IGoblinStoreMgtArtistService {
/**
* 搜索可申请艺人(仅启用状态),支持关键词模糊匹配,DB 真分页
*/
ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> searchArtist(String keyword, int page, int size);
/**
* 提交艺人关联申请,校验艺人存在且启用、同店同艺人无待审核申请
*
* @return data = applyId
*/
ResponseDto<String> submitApply(GoblinStoreMgtArtistApplySubmitParams params);
/**
* 本店艺人关联申请列表,支持按状态筛选
*/
ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> applyList(Integer status, int page, int size);
/**
* 撤销艺人关联申请,仅待审核状态可撤销
*/
ResponseDto<Void> revokeApply(GoblinStoreMgtArtistApplyRevokeParams params);
/**
* 本店已授权艺人列表,过滤过期和失效授权
*/
ResponseDto<GoblinStoreMgtArtistAuthListVo> authList();
/**
* 本店艺人专题列表(未删除),响应含各状态数量统计
*/
ResponseDto<GoblinStoreMgtArtistTopicPageVo> topicList(Integer status, int page, int size);
/**
* 艺人专题详情,含内嵌商品列表(名称/图片/价格),艺人名联查 kylin_artist
*/
ResponseDto<GoblinStoreMgtArtistTopicDetailVo> topicDetail(String topicId);
/**
* 创建艺人专题,校验关联艺人有本店有效授权、商品属本店且未删除
*
* @return data = topicId
*/
ResponseDto<String> topicCreate(GoblinStoreMgtArtistTopicCreateParams params);
/**
* 编辑艺人专题,商品全量替换
*/
ResponseDto<Void> topicUpdate(GoblinStoreMgtArtistTopicUpdateParams params);
/**
* 删除艺人专题(逻辑删除 delFlg=1)
*/
ResponseDto<Void> topicDelete(GoblinStoreMgtArtistTopicDeleteParams params);
/**
* 专题上下架,0=待上架 1=已上架 2=已下架
*/
ResponseDto<Void> topicStatus(GoblinStoreMgtArtistTopicStatusParams params);
/**
* 搜索本店可添加商品(普通商品,非市场活动),支持关键词模糊匹配,DB 真分页
*/
ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> searchGoods(String keyword, int page, int size);
}
package com.liquidnet.service.goblin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 店铺-艺人授权关系
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GoblinArtistAuth implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String authId;
private String storeId;
private String artistId;
private String applyId;
private LocalDateTime expireAt;
/** 1有效 0失效 */
private Integer status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 艺人关联申请
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GoblinArtistAuthApply implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String applyId;
private String storeId;
private String artistId;
/** JSON 数组字符串 */
private String attachmentUrls;
/** 0待审核 1已通过 2已拒绝 3已撤销 */
private Integer status;
private String reviewerId;
private String reviewerName;
private LocalDateTime reviewTime;
private String reviewRemark;
private String rejectReason;
private LocalDateTime authExpireAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 艺人专题
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GoblinArtistTopic implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String topicId;
private String storeId;
private String topicName;
private String artistId;
/** 1双列 2单列 */
private Integer templateType;
private String bannerUrl;
private Integer bannerHeight;
private LocalDateTime onSaleStart;
private LocalDateTime onSaleEnd;
/** 0待上架 1已上架 2已下架 */
private Integer status;
private Integer sort;
private Integer recommendPosition;
/** 0正常 1删除 */
private Integer delFlg;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 艺人专题商品关联
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GoblinArtistTopicGoods implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String relationId;
private String topicId;
private String spuId;
private Integer sort;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
package com.liquidnet.service.goblin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.goblin.entity.GoblinArtistAuthApply;
public interface GoblinArtistAuthApplyMapper extends BaseMapper<GoblinArtistAuthApply> {
}
package com.liquidnet.service.goblin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.goblin.entity.GoblinArtistAuth;
public interface GoblinArtistAuthMapper extends BaseMapper<GoblinArtistAuth> {
}
package com.liquidnet.service.goblin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.goblin.entity.GoblinArtistTopicGoods;
public interface GoblinArtistTopicGoodsMapper extends BaseMapper<GoblinArtistTopicGoods> {
}
package com.liquidnet.service.goblin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.goblin.entity.GoblinArtistTopic;
public interface GoblinArtistTopicMapper extends BaseMapper<GoblinArtistTopic> {
}
package com.liquidnet.service.goblin.controller.manage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.manage.*;
import com.liquidnet.service.goblin.dto.manage.vo.*;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreMgtArtistService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
/**
* 商家端-艺人关联申请 / 艺人专题
* <p>店铺由登录 UID 解析,对齐售后单管理</p>
*/
@ApiSupport(order = 149020)
@Api(tags = "艺人管理")
@Slf4j
@Validated
@RestController
@RequestMapping("store/mgt/artist")
public class GoblinStoreMgtArtistController {
@Autowired
private IGoblinStoreMgtArtistService artistService;
@ApiOperationSupport(order = 1)
@ApiOperation("搜索可申请艺人")
@ApiImplicitParams({
@ApiImplicitParam(name = "keyword", value = "艺人名称关键词", dataType = "String"),
@ApiImplicitParam(name = "page", value = "页码", dataType = "int", required = true),
@ApiImplicitParam(name = "size", value = "每页条数", dataType = "int", required = true),
})
@GetMapping("list")
public ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> searchArtist(
@RequestParam(required = false) String keyword,
@Min(1) @RequestParam(defaultValue = "1") int page,
@Min(1) @RequestParam(defaultValue = "20") int size) {
return artistService.searchArtist(keyword, page, size);
}
@ApiOperationSupport(order = 2)
@ApiOperation("提交艺人关联申请")
@PostMapping("apply/submit")
public ResponseDto<String> submitApply(
@Valid @RequestBody GoblinStoreMgtArtistApplySubmitParams params) {
return artistService.submitApply(params);
}
@ApiOperationSupport(order = 3)
@ApiOperation("艺人关联申请列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "status", value = "状态 0待审核 1已通过 2已拒绝 3已撤销", dataType = "int"),
@ApiImplicitParam(name = "page", value = "页码", dataType = "int", required = true),
@ApiImplicitParam(name = "size", value = "每页条数", dataType = "int", required = true),
})
@GetMapping("apply/list")
public ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> applyList(
@RequestParam(required = false) Integer status,
@Min(1) @RequestParam(defaultValue = "1") int page,
@Min(1) @RequestParam(defaultValue = "20") int size) {
return artistService.applyList(status, page, size);
}
@ApiOperationSupport(order = 4)
@ApiOperation("撤销艺人关联申请")
@PostMapping("apply/revoke")
public ResponseDto<Void> revokeApply(
@Valid @RequestBody GoblinStoreMgtArtistApplyRevokeParams params) {
return artistService.revokeApply(params);
}
@ApiOperationSupport(order = 5)
@ApiOperation("本店已授权艺人列表")
@GetMapping("auth/list")
public ResponseDto<GoblinStoreMgtArtistAuthListVo> authList() {
return artistService.authList();
}
@ApiOperationSupport(order = 6)
@ApiOperation("艺人专题列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "status", value = "状态 0待上架 1已上架 2已下架", dataType = "int"),
@ApiImplicitParam(name = "page", value = "页码", dataType = "int", required = true),
@ApiImplicitParam(name = "size", value = "每页条数", dataType = "int", required = true),
})
@GetMapping("topic/list")
public ResponseDto<GoblinStoreMgtArtistTopicPageVo> topicList(
@RequestParam(required = false) Integer status,
@Min(1) @RequestParam(defaultValue = "1") int page,
@Min(1) @RequestParam(defaultValue = "20") int size) {
return artistService.topicList(status, page, size);
}
@ApiOperationSupport(order = 7)
@ApiOperation("艺人专题详情")
@GetMapping("topic/detail")
public ResponseDto<GoblinStoreMgtArtistTopicDetailVo> topicDetail(
@NotBlank(message = "专题ID不能为空") @RequestParam String topicId) {
return artistService.topicDetail(topicId);
}
@ApiOperationSupport(order = 8)
@ApiOperation("创建艺人专题")
@PostMapping("topic/create")
public ResponseDto<String> topicCreate(
@Valid @RequestBody GoblinStoreMgtArtistTopicCreateParams params) {
return artistService.topicCreate(params);
}
@ApiOperationSupport(order = 9)
@ApiOperation("编辑艺人专题")
@PostMapping("topic/update")
public ResponseDto<Void> topicUpdate(
@Valid @RequestBody GoblinStoreMgtArtistTopicUpdateParams params) {
return artistService.topicUpdate(params);
}
@ApiOperationSupport(order = 10)
@ApiOperation("删除艺人专题")
@PostMapping("topic/delete")
public ResponseDto<Void> topicDelete(
@Valid @RequestBody GoblinStoreMgtArtistTopicDeleteParams params) {
return artistService.topicDelete(params);
}
@ApiOperationSupport(order = 11)
@ApiOperation("艺人专题上下架")
@PostMapping("topic/status")
public ResponseDto<Void> topicStatus(
@Valid @RequestBody GoblinStoreMgtArtistTopicStatusParams params) {
return artistService.topicStatus(params);
}
@ApiOperationSupport(order = 12)
@ApiOperation("搜索本店可添加商品")
@ApiImplicitParams({
@ApiImplicitParam(name = "keyword", value = "商品名称关键词", dataType = "String"),
@ApiImplicitParam(name = "page", value = "页码", dataType = "int", required = true),
@ApiImplicitParam(name = "size", value = "每页条数", dataType = "int", required = true),
})
@GetMapping("topic/searchGoods")
public ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> searchGoods(
@RequestParam(required = false) String keyword,
@Min(1) @RequestParam(defaultValue = "1") int page,
@Min(1) @RequestParam(defaultValue = "20") int size) {
return artistService.searchGoods(keyword, page, size);
}
}
package com.liquidnet.service.goblin.service.impl.manage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.liquidnet.commons.lang.util.CollectionUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.goblin.constant.GoblinArtistConst;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplyRevokeParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplySubmitParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicCreateParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicDeleteParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicGoodsItemParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicStatusParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicUpdateParams;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistApplyListItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistAuthListVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicDetailVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicGoodsSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinArtistAuthApplyVo;
import com.liquidnet.service.goblin.dto.vo.GoblinArtistAuthVo;
import com.liquidnet.service.goblin.dto.vo.GoblinArtistTopicVo;
import com.liquidnet.service.goblin.dto.vo.GoblinGoodsInfoVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreInfoVo;
import com.liquidnet.service.goblin.entity.GoblinGoods;
import com.liquidnet.service.goblin.mapper.GoblinGoodsMapper;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreMgtArtistService;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import com.liquidnet.service.goblin.util.QueueUtils;
import com.liquidnet.service.kylin.entity.KylinArtist;
import com.liquidnet.service.kylin.mapper.KylinArtistMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
@Slf4j
public class GoblinStoreMgtArtistServiceImpl implements IGoblinStoreMgtArtistService {
private static final long TOPIC_TTL = 86400L;
@Autowired
private GoblinRedisUtils redisUtils;
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private QueueUtils queueUtils;
@Autowired
private GoblinGoodsMapper goodsMapper;
@Autowired
private KylinArtistMapper kylinArtistMapper;
private ResponseDto<GoblinStoreInfoVo> requireStore() {
String uid = com.liquidnet.commons.lang.util.CurrentUtil.getCurrentUid();
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
if (storeInfoVo == null || StringUtils.isBlank(storeInfoVo.getStoreId())) {
return ResponseDto.failure("无法查看");
}
return ResponseDto.success(storeInfoVo);
}
// ======================== 艺人搜索 / 申请 ========================
@Override
public ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> searchArtist(String keyword, int page, int size) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
LambdaQueryWrapper<KylinArtist> wrapper = new LambdaQueryWrapper<KylinArtist>()
.eq(KylinArtist::getStatus, 1)
.like(StringUtils.isNotBlank(keyword), KylinArtist::getArtistName, keyword)
.orderByDesc(KylinArtist::getSort)
.orderByDesc(KylinArtist::getCreatedAt);
long total = kylinArtistMapper.selectCount(wrapper);
wrapper.last("LIMIT " + ((page - 1) * size) + ", " + size);
List<KylinArtist> pageList = total > 0 ? kylinArtistMapper.selectList(wrapper) : Collections.emptyList();
PagedResult<GoblinStoreMgtArtistSearchItemVo> result = new PagedResult<>();
result.setCurrentPage(page);
result.setPageSize(size);
result.setTotal(total, size);
result.setList(pageList.stream().map(a -> {
GoblinStoreMgtArtistSearchItemVo item = new GoblinStoreMgtArtistSearchItemVo();
item.setArtistId(a.getArtistId());
item.setArtistName(a.getArtistName());
item.setAvatarUrl(a.getAvatarUrl());
item.setArtistType(a.getArtistType());
return item;
}).collect(Collectors.toList()));
return ResponseDto.success(result);
}
@Override
public ResponseDto<String> submitApply(GoblinStoreMgtArtistApplySubmitParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
KylinArtist artist = kylinArtistMapper.selectOne(new LambdaQueryWrapper<KylinArtist>()
.eq(KylinArtist::getArtistId, params.getArtistId())
.eq(KylinArtist::getStatus, 1)
.last("limit 1"));
if (artist == null) {
return ResponseDto.failure("艺人不存在或已禁用");
}
long pendingCount = mongoTemplate.count(new Query(Criteria.where("storeId").is(storeId)
.and("artistId").is(params.getArtistId())
.and("status").is(GoblinArtistConst.APPLY_PENDING)),
GoblinArtistAuthApplyVo.class);
if (pendingCount > 0) {
return ResponseDto.failure("该艺人已有待审核申请");
}
LocalDateTime now = LocalDateTime.now();
String applyId = IDGenerator.nextSnowId();
GoblinArtistAuthApplyVo applyVo = new GoblinArtistAuthApplyVo();
applyVo.setApplyId(applyId);
applyVo.setStoreId(storeId);
applyVo.setArtistId(params.getArtistId());
applyVo.setAttachmentUrls(JsonUtils.toJson(params.getAttachmentUrls()));
applyVo.setStatus(GoblinArtistConst.APPLY_PENDING);
applyVo.setCreatedAt(now);
applyVo.setUpdatedAt(now);
mongoTemplate.insert(applyVo, GoblinArtistAuthApplyVo.class.getSimpleName());
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_artist_auth_apply.insert"));
LinkedList<Object[]> applyObjs = CollectionUtil.linkedListObjectArr();
applyObjs.add(new Object[]{applyId, storeId, params.getArtistId(),
applyVo.getAttachmentUrls(), GoblinArtistConst.APPLY_PENDING, now, now});
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, applyObjs));
return ResponseDto.success(applyId);
}
@Override
public ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> applyList(Integer status, int page, int size) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
List<GoblinArtistAuthApplyVo> all = loadAppliesByStore(storeId);
if (status != null) {
all = all.stream().filter(a -> Objects.equals(a.getStatus(), status))
.collect(Collectors.toList());
}
all.sort((a, b) -> {
if (a.getCreatedAt() == null || b.getCreatedAt() == null) return 0;
return b.getCreatedAt().compareTo(a.getCreatedAt());
});
Map<String, KylinArtist> artistMap = loadArtistMap(all.stream()
.map(GoblinArtistAuthApplyVo::getArtistId).collect(Collectors.toSet()));
List<GoblinStoreMgtArtistApplyListItemVo> list = new ArrayList<>();
for (GoblinArtistAuthApplyVo apply : pageSlice(all, page, size)) {
GoblinStoreMgtArtistApplyListItemVo item = new GoblinStoreMgtArtistApplyListItemVo();
item.setApplyId(apply.getApplyId());
item.setArtistId(apply.getArtistId());
KylinArtist artist = artistMap.get(apply.getArtistId());
if (artist != null) {
item.setArtistName(artist.getArtistName());
item.setAvatarUrl(artist.getAvatarUrl());
}
item.setAttachmentUrls(parseUrlList(apply.getAttachmentUrls()));
item.setApplyTime(apply.getCreatedAt());
item.setStatus(apply.getStatus());
item.setReviewTime(apply.getReviewTime());
item.setReviewerName(apply.getReviewerName());
item.setAuthExpireAt(apply.getAuthExpireAt());
item.setRejectReason(apply.getRejectReason());
list.add(item);
}
PagedResult<GoblinStoreMgtArtistApplyListItemVo> result = new PagedResult<>();
result.setCurrentPage(page);
result.setPageSize(size);
result.setTotal(all.size(), size);
result.setList(list);
return ResponseDto.success(result);
}
@Override
public ResponseDto<Void> revokeApply(GoblinStoreMgtArtistApplyRevokeParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
Query query = new Query(Criteria.where("applyId").is(params.getApplyId())
.and("storeId").is(storeId));
GoblinArtistAuthApplyVo apply = mongoTemplate.findOne(query, GoblinArtistAuthApplyVo.class,
GoblinArtistAuthApplyVo.class.getSimpleName());
if (apply == null) {
return ResponseDto.failure("申请不存在");
}
if (!Objects.equals(apply.getStatus(), GoblinArtistConst.APPLY_PENDING)) {
return ResponseDto.failure("仅待审核申请可撤销");
}
LocalDateTime now = LocalDateTime.now();
mongoTemplate.updateFirst(query,
new Update().set("status", GoblinArtistConst.APPLY_REVOKED).set("updatedAt", now),
GoblinArtistAuthApplyVo.class.getSimpleName());
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_artist_auth_apply.revoke"));
LinkedList<Object[]> sqlObjs = CollectionUtil.linkedListObjectArr();
sqlObjs.add(new Object[]{now, params.getApplyId()});
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, sqlObjs));
return ResponseDto.success(null);
}
// ======================== 已授权艺人 ========================
@Override
public ResponseDto<GoblinStoreMgtArtistAuthListVo> authList() {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
List<GoblinArtistAuthVo> auths = loadAuthsByStore(storeId);
LocalDateTime now = LocalDateTime.now();
auths = auths.stream()
.filter(a -> Objects.equals(a.getStatus(), GoblinArtistConst.AUTH_VALID)
&& a.getExpireAt() != null && a.getExpireAt().isAfter(now))
.collect(Collectors.toList());
Map<String, KylinArtist> artistMap = loadArtistMap(auths.stream()
.map(GoblinArtistAuthVo::getArtistId).collect(Collectors.toSet()));
GoblinStoreMgtArtistAuthListVo result = new GoblinStoreMgtArtistAuthListVo();
for (GoblinArtistAuthVo auth : auths) {
GoblinStoreMgtArtistAuthListVo.Item item = new GoblinStoreMgtArtistAuthListVo.Item();
item.setArtistId(auth.getArtistId());
item.setExpireAt(auth.getExpireAt());
KylinArtist artist = artistMap.get(auth.getArtistId());
if (artist != null) {
item.setArtistName(artist.getArtistName());
item.setAvatarUrl(artist.getAvatarUrl());
}
result.getList().add(item);
}
return ResponseDto.success(result);
}
// ======================== 专题列表 / 详情 ========================
@Override
public ResponseDto<GoblinStoreMgtArtistTopicPageVo> topicList(Integer status, int page, int size) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
List<GoblinArtistTopicVo> all = loadTopicsByStore(storeId);
int countPending = 0, countOnline = 0, countOffline = 0;
for (GoblinArtistTopicVo t : all) {
if (Objects.equals(t.getStatus(), GoblinArtistConst.TOPIC_PENDING)) countPending++;
else if (Objects.equals(t.getStatus(), GoblinArtistConst.TOPIC_ONLINE)) countOnline++;
else if (Objects.equals(t.getStatus(), GoblinArtistConst.TOPIC_OFFLINE)) countOffline++;
}
if (status != null) {
final Integer fs = status;
all = all.stream().filter(t -> Objects.equals(t.getStatus(), fs))
.collect(Collectors.toList());
}
all.sort((a, b) -> {
int sortCmp = Integer.compare(
a.getSort() == null ? 0 : a.getSort(),
b.getSort() == null ? 0 : b.getSort());
if (sortCmp != 0) return sortCmp;
if (a.getCreatedAt() == null || b.getCreatedAt() == null) return 0;
return b.getCreatedAt().compareTo(a.getCreatedAt());
});
Map<String, KylinArtist> artistMap = loadArtistMap(all.stream()
.map(GoblinArtistTopicVo::getArtistId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet()));
GoblinStoreMgtArtistTopicPageVo result = new GoblinStoreMgtArtistTopicPageVo();
result.setTotal(all.size());
result.setCountPending(countPending);
result.setCountOnline(countOnline);
result.setCountOffline(countOffline);
for (GoblinArtistTopicVo topic : pageSlice(all, page, size)) {
GoblinStoreMgtArtistTopicPageVo.Item item = new GoblinStoreMgtArtistTopicPageVo.Item();
item.setTopicId(topic.getTopicId());
item.setTopicName(topic.getTopicName());
item.setArtistId(topic.getArtistId());
KylinArtist artist = artistMap.get(topic.getArtistId());
if (artist != null) {
item.setArtistName(artist.getArtistName());
item.setAvatarUrl(artist.getAvatarUrl());
}
item.setGoodsCount(topic.getGoods() != null ? topic.getGoods().size() : 0);
item.setOnSaleStart(topic.getOnSaleStart());
item.setOnSaleEnd(topic.getOnSaleEnd());
item.setStatus(topic.getStatus());
result.getList().add(item);
}
return ResponseDto.success(result);
}
@Override
public ResponseDto<GoblinStoreMgtArtistTopicDetailVo> topicDetail(String topicId) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
GoblinArtistTopicVo topic = loadTopic(storeId, topicId);
if (topic == null) {
return ResponseDto.failure("专题不存在");
}
GoblinStoreMgtArtistTopicDetailVo vo = new GoblinStoreMgtArtistTopicDetailVo();
vo.setTopicId(topic.getTopicId());
vo.setTopicName(topic.getTopicName());
vo.setArtistId(topic.getArtistId());
vo.setTemplateType(topic.getTemplateType());
vo.setBannerUrl(topic.getBannerUrl());
vo.setBannerHeight(topic.getBannerHeight());
vo.setOnSaleStart(topic.getOnSaleStart());
vo.setOnSaleEnd(topic.getOnSaleEnd());
vo.setStatus(topic.getStatus());
if (StringUtils.isNotBlank(topic.getArtistId())) {
KylinArtist artist = kylinArtistMapper.selectOne(new LambdaQueryWrapper<KylinArtist>()
.eq(KylinArtist::getArtistId, topic.getArtistId()).last("limit 1"));
if (artist != null) {
vo.setArtistName(artist.getArtistName());
vo.setAvatarUrl(artist.getAvatarUrl());
}
}
if (!CollectionUtils.isEmpty(topic.getGoods())) {
for (GoblinArtistTopicVo.GoodsItem goods : topic.getGoods()) {
GoblinStoreMgtArtistTopicDetailVo.GoodsItem item = new GoblinStoreMgtArtistTopicDetailVo.GoodsItem();
item.setSpuId(goods.getSpuId());
item.setSort(goods.getSort());
item.setName(goods.getName());
item.setCoverPic(goods.getCoverPic());
item.setSellPrice(goods.getSellPrice());
vo.getGoods().add(item);
}
}
return ResponseDto.success(vo);
}
// ======================== 专题 创建 / 编辑 / 删除 / 上下架 ========================
@Override
public ResponseDto<String> topicCreate(GoblinStoreMgtArtistTopicCreateParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
ResponseDto<Void> artistCheck = checkArtistAuthIfPresent(storeId, params.getArtistId());
if (!artistCheck.isSuccess()) {
return ResponseDto.failure(artistCheck.getMessage());
}
if (params.getTemplateType() == null
|| (params.getTemplateType() != GoblinArtistConst.TEMPLATE_GRID
&& params.getTemplateType() != GoblinArtistConst.TEMPLATE_LIST)) {
return ResponseDto.failure("模版类型不正确");
}
LocalDateTime now = LocalDateTime.now();
String topicId = IDGenerator.nextSnowId();
GoblinArtistTopicVo topicVo = new GoblinArtistTopicVo();
topicVo.setTopicId(topicId);
topicVo.setStoreId(storeId);
topicVo.setTopicName(params.getTopicName());
topicVo.setArtistId(StringUtils.trimToNull(params.getArtistId()));
topicVo.setTemplateType(params.getTemplateType());
topicVo.setBannerUrl(params.getBannerUrl());
topicVo.setBannerHeight(params.getBannerHeight() == null ? GoblinArtistConst.DEFAULT_BANNER_HEIGHT : params.getBannerHeight());
topicVo.setOnSaleStart(params.getOnSaleStart());
topicVo.setOnSaleEnd(params.getOnSaleEnd());
topicVo.setStatus(GoblinArtistConst.TOPIC_PENDING);
topicVo.setSort(0);
topicVo.setRecommendPosition(0);
topicVo.setDelFlg(GoblinArtistConst.DEL_NO);
topicVo.setCreatedAt(now);
topicVo.setUpdatedAt(now);
Map<String, GoblinGoods> goodsMap = loadGoodsMap(params.getGoods());
List<GoblinArtistTopicVo.GoodsItem> goodsItems = buildGoodsItems(params.getGoods(), goodsMap);
ResponseDto<Void> goodsCheck = checkGoodsBelong(storeId, goodsMap, params.getGoods());
if (!goodsCheck.isSuccess()) {
return ResponseDto.failure(goodsCheck.getMessage());
}
topicVo.setGoods(goodsItems);
mongoTemplate.insert(topicVo, GoblinArtistTopicVo.class.getSimpleName());
String topicKey = GoblinRedisConst.ARTIST_TOPIC + storeId + "_" + topicId;
redisUtils.set(topicKey, topicVo, TOPIC_TTL);
queueTopicInsertMysql(topicVo, now);
return ResponseDto.success(topicId);
}
@Override
public ResponseDto<Void> topicUpdate(GoblinStoreMgtArtistTopicUpdateParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
GoblinArtistTopicVo existing = loadTopic(storeId, params.getTopicId());
if (existing == null) {
return ResponseDto.failure("专题不存在");
}
ResponseDto<Void> artistCheck = checkArtistAuthIfPresent(storeId, params.getArtistId());
if (!artistCheck.isSuccess()) {
return ResponseDto.failure(artistCheck.getMessage());
}
if (params.getTemplateType() == null
|| (params.getTemplateType() != GoblinArtistConst.TEMPLATE_GRID
&& params.getTemplateType() != GoblinArtistConst.TEMPLATE_LIST)) {
return ResponseDto.failure("模版类型不正确");
}
LocalDateTime now = LocalDateTime.now();
Map<String, GoblinGoods> goodsMap = loadGoodsMap(params.getGoods());
ResponseDto<Void> goodsCheck = checkGoodsBelong(storeId, goodsMap, params.getGoods());
if (!goodsCheck.isSuccess()) {
return ResponseDto.failure(goodsCheck.getMessage());
}
Query query = new Query(Criteria.where("topicId").is(params.getTopicId()).and("storeId").is(storeId));
Update update = new Update();
update.set("topicName", params.getTopicName());
update.set("artistId", StringUtils.trimToNull(params.getArtistId()));
update.set("templateType", params.getTemplateType());
update.set("bannerUrl", params.getBannerUrl());
update.set("bannerHeight", params.getBannerHeight() == null ? GoblinArtistConst.DEFAULT_BANNER_HEIGHT : params.getBannerHeight());
update.set("onSaleStart", params.getOnSaleStart());
update.set("onSaleEnd", params.getOnSaleEnd());
update.set("goods", buildGoodsItems(params.getGoods(), goodsMap));
update.set("updatedAt", now);
mongoTemplate.updateFirst(query, update, GoblinArtistTopicVo.class.getSimpleName());
String topicKey = GoblinRedisConst.ARTIST_TOPIC + storeId + "_" + params.getTopicId();
redisUtils.del(topicKey);
queueTopicUpdateMysql(params.getTopicId(), params, now);
return ResponseDto.success(null);
}
@Override
public ResponseDto<Void> topicDelete(GoblinStoreMgtArtistTopicDeleteParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
GoblinArtistTopicVo existing = loadTopic(storeId, params.getTopicId());
if (existing == null) {
return ResponseDto.failure("专题不存在");
}
LocalDateTime now = LocalDateTime.now();
Query query = new Query(Criteria.where("topicId").is(params.getTopicId()).and("storeId").is(storeId));
mongoTemplate.updateFirst(query,
new Update().set("delFlg", GoblinArtistConst.DEL_YES).set("updatedAt", now),
GoblinArtistTopicVo.class.getSimpleName());
String topicKey = GoblinRedisConst.ARTIST_TOPIC + storeId + "_" + params.getTopicId();
redisUtils.del(topicKey);
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_artist_topic.delete"));
LinkedList<Object[]> sqlObjs = CollectionUtil.linkedListObjectArr();
sqlObjs.add(new Object[]{now, params.getTopicId()});
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, sqlObjs));
return ResponseDto.success(null);
}
@Override
public ResponseDto<Void> topicStatus(GoblinStoreMgtArtistTopicStatusParams params) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
GoblinArtistTopicVo existing = loadTopic(storeId, params.getTopicId());
if (existing == null) {
return ResponseDto.failure("专题不存在");
}
Integer status = params.getStatus();
if (status == null || (status != GoblinArtistConst.TOPIC_PENDING
&& status != GoblinArtistConst.TOPIC_ONLINE
&& status != GoblinArtistConst.TOPIC_OFFLINE)) {
return ResponseDto.failure("状态不正确");
}
LocalDateTime now = LocalDateTime.now();
Query query = new Query(Criteria.where("topicId").is(params.getTopicId()).and("storeId").is(storeId));
mongoTemplate.updateFirst(query,
new Update().set("status", status).set("updatedAt", now),
GoblinArtistTopicVo.class.getSimpleName());
String topicKey = GoblinRedisConst.ARTIST_TOPIC + storeId + "_" + params.getTopicId();
redisUtils.del(topicKey);
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_artist_topic.status"));
LinkedList<Object[]> sqlObjs = CollectionUtil.linkedListObjectArr();
sqlObjs.add(new Object[]{status, now, params.getTopicId()});
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, sqlObjs));
return ResponseDto.success(null);
}
// ======================== 搜索商品(不改 MySQL) ========================
@Override
public ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> searchGoods(String keyword, int page, int size) {
ResponseDto<GoblinStoreInfoVo> storeResp = requireStore();
if (!storeResp.isSuccess()) {
return ResponseDto.failure(storeResp.getMessage());
}
String storeId = storeResp.getData().getStoreId();
Criteria criteria = Criteria.where("storeId").is(storeId)
.and("delFlg").is("0")
.and("marketId").exists(false)
.orOperator(Criteria.where("spuType").exists(false), Criteria.where("spuType").is(0));
if (StringUtils.isNotBlank(keyword)) {
criteria.and("name").regex(Pattern.compile("^.*" + keyword + ".*$", Pattern.CASE_INSENSITIVE));
}
Query query = Query.query(criteria);
long total = mongoTemplate.count(query, GoblinGoodsInfoVo.class.getSimpleName());
PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo> result = new PagedResult<>();
result.setCurrentPage(page);
result.setPageSize(size);
result.setTotal(total, size);
if (total > 0) {
query.with(PageRequest.of(page - 1, size));
query.with(Sort.by(Sort.Order.desc("createdAt")));
result.setList(mongoTemplate.find(query, GoblinStoreMgtArtistTopicGoodsSearchItemVo.class,
GoblinGoodsInfoVo.class.getSimpleName()));
}
return ResponseDto.success(result);
}
// ======================== 私有:校验 ========================
private ResponseDto<Void> checkArtistAuthIfPresent(String storeId, String artistId) {
if (StringUtils.isBlank(artistId)) {
return ResponseDto.success(null);
}
LocalDateTime now = LocalDateTime.now();
List<GoblinArtistAuthVo> auths = loadAuthsByStore(storeId);
boolean valid = auths.stream().anyMatch(a ->
Objects.equals(a.getArtistId(), artistId)
&& Objects.equals(a.getStatus(), GoblinArtistConst.AUTH_VALID)
&& a.getExpireAt() != null && a.getExpireAt().isAfter(now));
if (!valid) {
return ResponseDto.failure("未获得该艺人有效授权");
}
return ResponseDto.success(null);
}
private ResponseDto<Void> checkGoodsBelong(String storeId, Map<String, GoblinGoods> goodsMap,
List<GoblinStoreMgtArtistTopicGoodsItemParams> paramsGoods) {
if (CollectionUtils.isEmpty(paramsGoods)) {
return ResponseDto.success(null);
}
for (GoblinStoreMgtArtistTopicGoodsItemParams item : paramsGoods) {
if (StringUtils.isBlank(item.getSpuId())) continue;
GoblinGoods goods = goodsMap.get(item.getSpuId());
if (goods == null || !Objects.equals(goods.getStoreId(), storeId)
|| !Objects.equals(goods.getDelFlg(), "0")) {
return ResponseDto.failure("存在非本店或已删除商品");
}
}
return ResponseDto.success(null);
}
// ======================== 私有:数据加载 ========================
private List<GoblinArtistAuthApplyVo> loadAppliesByStore(String storeId) {
List<GoblinArtistAuthApplyVo> applies = mongoTemplate.find(
new Query(Criteria.where("storeId").is(storeId)),
GoblinArtistAuthApplyVo.class, GoblinArtistAuthApplyVo.class.getSimpleName());
return applies == null ? Collections.emptyList() : applies;
}
private List<GoblinArtistAuthVo> loadAuthsByStore(String storeId) {
List<GoblinArtistAuthVo> auths = mongoTemplate.find(
new Query(Criteria.where("storeId").is(storeId)),
GoblinArtistAuthVo.class, GoblinArtistAuthVo.class.getSimpleName());
return auths == null ? Collections.emptyList() : auths;
}
private List<GoblinArtistTopicVo> loadTopicsByStore(String storeId) {
List<GoblinArtistTopicVo> topics = mongoTemplate.find(
new Query(Criteria.where("storeId").is(storeId).and("delFlg").is(GoblinArtistConst.DEL_NO)),
GoblinArtistTopicVo.class, GoblinArtistTopicVo.class.getSimpleName());
return topics == null ? Collections.emptyList() : topics;
}
private GoblinArtistTopicVo loadTopic(String storeId, String topicId) {
String redisKey = GoblinRedisConst.ARTIST_TOPIC + storeId + "_" + topicId;
Object cached = redisUtils.get(redisKey);
if (cached instanceof GoblinArtistTopicVo) {
GoblinArtistTopicVo topic = (GoblinArtistTopicVo) cached;
if (Objects.equals(topic.getDelFlg(), GoblinArtistConst.DEL_NO)) {
return topic;
}
return null;
}
GoblinArtistTopicVo topic = mongoTemplate.findOne(
new Query(Criteria.where("topicId").is(topicId).and("storeId").is(storeId)
.and("delFlg").is(GoblinArtistConst.DEL_NO)),
GoblinArtistTopicVo.class, GoblinArtistTopicVo.class.getSimpleName());
if (topic != null) {
redisUtils.set(redisKey, topic, TOPIC_TTL);
}
return topic;
}
// ======================== 私有:商品 / 艺人 Map ========================
private Map<String, GoblinGoods> loadGoodsMap(List<GoblinStoreMgtArtistTopicGoodsItemParams> goods) {
if (CollectionUtils.isEmpty(goods)) {
return Collections.emptyMap();
}
Set<String> spuIds = goods.stream().map(GoblinStoreMgtArtistTopicGoodsItemParams::getSpuId)
.filter(StringUtils::isNotBlank).collect(Collectors.toSet());
if (spuIds.isEmpty()) return Collections.emptyMap();
List<GoblinGoods> list = goodsMapper.selectList(new LambdaQueryWrapper<GoblinGoods>()
.in(GoblinGoods::getSpuId, spuIds));
Map<String, GoblinGoods> map = new HashMap<>();
for (GoblinGoods g : list) {
map.put(g.getSpuId(), g);
}
return map;
}
private List<GoblinArtistTopicVo.GoodsItem> buildGoodsItems(
List<GoblinStoreMgtArtistTopicGoodsItemParams> paramsGoods,
Map<String, GoblinGoods> goodsMap) {
List<GoblinArtistTopicVo.GoodsItem> items = new ArrayList<>();
if (CollectionUtils.isEmpty(paramsGoods)) {
return items;
}
int index = 0;
for (GoblinStoreMgtArtistTopicGoodsItemParams p : paramsGoods) {
if (StringUtils.isBlank(p.getSpuId())) continue;
GoblinArtistTopicVo.GoodsItem item = new GoblinArtistTopicVo.GoodsItem();
item.setSpuId(p.getSpuId());
item.setSort(p.getSort() == null ? index : p.getSort());
GoblinGoods goods = goodsMap.get(p.getSpuId());
if (goods != null) {
item.setName(goods.getName());
item.setCoverPic(goods.getCoverPic());
item.setSellPrice(goods.getSellPrice());
}
items.add(item);
index++;
}
return items;
}
private Map<String, KylinArtist> loadArtistMap(Set<String> artistIds) {
if (CollectionUtils.isEmpty(artistIds)) {
return Collections.emptyMap();
}
List<KylinArtist> list = kylinArtistMapper.selectList(new LambdaQueryWrapper<KylinArtist>()
.in(KylinArtist::getArtistId, artistIds));
Map<String, KylinArtist> map = new HashMap<>();
for (KylinArtist artist : list) {
map.put(artist.getArtistId(), artist);
}
return map;
}
// ======================== 私有:异步 MySQL ========================
private void queueTopicInsertMysql(GoblinArtistTopicVo topicVo, LocalDateTime now) {
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_artist_topic.insert"));
LinkedList<Object[]> topicObjs = CollectionUtil.linkedListObjectArr();
topicObjs.add(new Object[]{
topicVo.getTopicId(), topicVo.getStoreId(), topicVo.getTopicName(),
topicVo.getArtistId(), topicVo.getTemplateType(),
topicVo.getBannerUrl(), topicVo.getBannerHeight(),
topicVo.getOnSaleStart(), topicVo.getOnSaleEnd(),
topicVo.getStatus(), topicVo.getSort(), topicVo.getRecommendPosition(),
topicVo.getDelFlg(), topicVo.getCreatedAt(), topicVo.getUpdatedAt()
});
LinkedList<Object[]> goodsObjs = CollectionUtil.linkedListObjectArr();
if (!CollectionUtils.isEmpty(topicVo.getGoods())) {
sqls.add(SqlMapping.get("goblin_artist_topic_goods.insert"));
for (GoblinArtistTopicVo.GoodsItem goods : topicVo.getGoods()) {
goodsObjs.add(new Object[]{
IDGenerator.nextSnowId(), topicVo.getTopicId(), goods.getSpuId(),
goods.getSort(), now, now
});
}
}
LinkedList<Object[]>[] args;
if (goodsObjs.isEmpty()) {
args = new LinkedList[]{topicObjs};
} else {
args = new LinkedList[]{topicObjs, goodsObjs};
}
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, args));
}
private void queueTopicUpdateMysql(String topicId, GoblinStoreMgtArtistTopicUpdateParams params, LocalDateTime now) {
LinkedList<String> sqls = CollectionUtil.linkedListString();
LinkedList<Object[]>[] argsArr;
sqls.add(SqlMapping.get("goblin_artist_topic.update"));
LinkedList<Object[]> topicObjs = CollectionUtil.linkedListObjectArr();
topicObjs.add(new Object[]{
params.getTopicName(), StringUtils.trimToNull(params.getArtistId()),
params.getTemplateType(), params.getBannerUrl(),
params.getBannerHeight() == null ? GoblinArtistConst.DEFAULT_BANNER_HEIGHT : params.getBannerHeight(),
params.getOnSaleStart(), params.getOnSaleEnd(), now, topicId
});
sqls.add(SqlMapping.get("goblin_artist_topic_goods.delete_by_topic"));
LinkedList<Object[]> delObjs = CollectionUtil.linkedListObjectArr();
delObjs.add(new Object[]{topicId});
LinkedList<Object[]> goodsObjs = CollectionUtil.linkedListObjectArr();
if (!CollectionUtils.isEmpty(params.getGoods())) {
sqls.add(SqlMapping.get("goblin_artist_topic_goods.insert"));
for (GoblinStoreMgtArtistTopicGoodsItemParams goods : params.getGoods()) {
if (StringUtils.isBlank(goods.getSpuId())) continue;
goodsObjs.add(new Object[]{
IDGenerator.nextSnowId(), topicId, goods.getSpuId(),
goods.getSort() == null ? 0 : goods.getSort(), now, now
});
}
}
if (goodsObjs.isEmpty()) {
argsArr = new LinkedList[]{topicObjs, delObjs};
} else {
argsArr = new LinkedList[]{topicObjs, delObjs, goodsObjs};
}
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_STORE.getKey(),
SqlMapping.gets(sqls, argsArr));
}
// ======================== 私有:工具 ========================
private List<String> parseUrlList(String json) {
if (StringUtils.isBlank(json)) {
return Collections.emptyList();
}
List<String> list = JsonUtils.fromJson(json, new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {
});
return list == null ? Collections.emptyList() : list;
}
private <T> List<T> pageSlice(List<T> all, int page, int size) {
int safePage = Math.max(page, 1);
int safeSize = Math.max(size, 1);
int from = (safePage - 1) * safeSize;
if (from >= all.size()) {
return Collections.emptyList();
}
int to = Math.min(from + safeSize, all.size());
return all.subList(from, to);
}
}
...@@ -86,6 +86,10 @@ public class GoblinRedisUtils { ...@@ -86,6 +86,10 @@ public class GoblinRedisUtils {
return redisUtil.incr(key, delta); return redisUtil.incr(key, delta);
} }
public void set(String key, Object value, long seconds) {
redisUtil.set(key, value, seconds);
}
/* ---------------------------------------- 短信相关 ---------------------------------------- */ /* ---------------------------------------- 短信相关 ---------------------------------------- */
public boolean setSmsCodeByMobile(String mobile, String smsCode) { public boolean setSmsCodeByMobile(String mobile, String smsCode) {
......
...@@ -205,4 +205,20 @@ goblin_bracelet_order_insert = INSERT INTO `goblin_bracelet_order`(`order_id`, ` ...@@ -205,4 +205,20 @@ goblin_bracelet_order_insert = INSERT INTO `goblin_bracelet_order`(`order_id`, `
gpblin_bracelet_order_update= update goblin_bracelet_order set out_trans_id=?, end_time=?, acct_date=?, price_actual=?, time_pay=?, status=?, pay_status=?, updated_at=? where order_id=? gpblin_bracelet_order_update= update goblin_bracelet_order set out_trans_id=?, end_time=?, acct_date=?, price_actual=?, time_pay=?, status=?, pay_status=?, updated_at=? where order_id=?
goblin_bracelet_order_update_cardno=UPDATE goblin_bracelet_order SET cardno=?,updated_at=? WHERE order_id=? goblin_bracelet_order_update_cardno=UPDATE goblin_bracelet_order SET cardno=?,updated_at=? WHERE order_id=?
goblin_bracelet_order_update_fout_trade_no=UPDATE goblin_bracelet_order SET fout_trade_no=?,updated_at=? WHERE order_id=? goblin_bracelet_order_update_fout_trade_no=UPDATE goblin_bracelet_order SET fout_trade_no=?,updated_at=? WHERE order_id=?
goblin_bracelet_order_update_refund=UPDATE goblin_bracelet_order SET status=?,refund_status=?,refund_status_note=?,updated_at=? WHERE order_id=? goblin_bracelet_order_update_refund=UPDATE goblin_bracelet_order SET status=?,refund_status=?,refund_status_note=?,updated_at=? WHERE order_id=?
\ No newline at end of file #---- \u827A\u4EBA\u5173\u8054\u7533\u8BF7
goblin_artist_auth_apply.insert=INSERT INTO goblin_artist_auth_apply (apply_id,store_id,artist_id,attachment_urls,status,created_at,updated_at)VALUES(?,?,?,?,?,?,?)
goblin_artist_auth_apply.revoke=UPDATE goblin_artist_auth_apply SET status=3,updated_at=? WHERE apply_id=?
goblin_artist_auth_apply.approve=UPDATE goblin_artist_auth_apply SET status=1,reviewer_id=?,reviewer_name=?,review_time=?,review_remark=?,auth_expire_at=?,updated_at=? WHERE apply_id=?
goblin_artist_auth_apply.reject=UPDATE goblin_artist_auth_apply SET status=2,reviewer_id=?,reviewer_name=?,review_time=?,reject_reason=?,updated_at=? WHERE apply_id=?
#---- \u827A\u4EBA\u6388\u6743\u5173\u7CFB
goblin_artist_auth.insert=INSERT INTO goblin_artist_auth (auth_id,store_id,artist_id,apply_id,expire_at,status,created_at,updated_at)VALUES(?,?,?,?,?,?,?,?)
goblin_artist_auth.update_expire=UPDATE goblin_artist_auth SET expire_at=?,updated_at=? WHERE store_id=? AND artist_id=? AND status=1
#---- \u827A\u4EBA\u4E13\u9898
goblin_artist_topic.insert=INSERT INTO goblin_artist_topic (topic_id,store_id,topic_name,artist_id,template_type,banner_url,banner_height,on_sale_start,on_sale_end,status,sort,recommend_position,del_flg,created_at,updated_at)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
goblin_artist_topic.update=UPDATE goblin_artist_topic SET topic_name=?,artist_id=?,template_type=?,banner_url=?,banner_height=?,on_sale_start=?,on_sale_end=?,updated_at=? WHERE topic_id=?
goblin_artist_topic.delete=UPDATE goblin_artist_topic SET del_flg=1,updated_at=? WHERE topic_id=?
goblin_artist_topic.status=UPDATE goblin_artist_topic SET status=?,updated_at=? WHERE topic_id=?
#---- \u4E13\u9898\u5546\u54C1
goblin_artist_topic_goods.insert=INSERT INTO goblin_artist_topic_goods (relation_id,topic_id,spu_id,sort,created_at,updated_at)VALUES(?,?,?,?,?,?)
goblin_artist_topic_goods.delete_by_topic=DELETE FROM goblin_artist_topic_goods WHERE topic_id=?
\ No newline at end of file
package com.liquidnet.service.goblin.test;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.constant.GoblinArtistConst;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplyRevokeParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplySubmitParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicCreateParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicDeleteParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicGoodsItemParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicStatusParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicUpdateParams;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistApplyListItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistAuthListVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicDetailVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicGoodsSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicPageVo;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreMgtArtistService;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.ArrayList;
import java.util.List;
/**
* B 端艺人专题接口 集成测试
* <p>
* 覆盖 12 个接口的完整调用链路,验证 MongoDB 读写、Redis 缓存、MQ 异步 SQL 队列。
* </p>
*
* <h3>前置条件</h3>
* <ul>
* <li>需连接 dev 环境 MongoDB + Redis</li>
* <li>CurrentUtil 通过 MockHttpServletRequest 注入测试 UID</li>
* </ul>
*
* <h3>运行方式</h3>
* <pre>
* IDE VM 参数: -Dtest.uid=你的店铺UID
* 然后 run 整个类或单独 @Test 方法
* </pre>
*
* <h3>数据源验证矩阵</h3>
* <table>
* <tr><th>测试场景</th><th>读</th><th>写</th></tr>
* <tr><td>搜索艺人</td><td>MySQL (kylin_artist)</td><td>—</td></tr>
* <tr><td>提交/撤销申请</td><td>MongoDB + Kylin</td><td>MongoDB 同步 + MySQL MQ 异步</td></tr>
* <tr><td>申请/授权列表</td><td>MongoDB(不缓存)</td><td>—</td></tr>
* <tr><td>专题 CRUD</td><td>MongoDB + Redis 缓存</td><td>MongoDB 同步 + Redis 更新 + MySQL MQ 异步</td></tr>
* <tr><td>搜索商品</td><td>MongoDB (GoblinGoodsInfoVo)</td><td>—</td></tr>
* </table>
*
* @see IGoblinStoreMgtArtistService
* @see com.liquidnet.service.goblin.service.impl.manage.GoblinStoreMgtArtistServiceImpl
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class GoblinStoreMgtArtistServiceImplTest {
@Autowired
private IGoblinStoreMgtArtistService artistService;
@Autowired
private GoblinRedisUtils redisUtils;
/** 从 Redis 加载的测试店铺 ID,所有写操作作用于此店铺 */
private String storeId;
/**
* 初始化测试上下文:通过 MockHttpServletRequest 注入测试 UID,
* 使后续 service 调用中的 {@code CurrentUtil.getCurrentUid()} 能正确获取。
*
* @throws AssertionError 如果测试 UID 未在 Redis 中关联店铺
*/
@Before
public void setUp() {
String testUid = "937724050260131847782985";
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(CurrentUtil.TOKEN_SUB, testUid);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(testUid);
Assert.assertNotNull("请设置有效测试 UID(-Dtest.uid=xxx),并确保该 UID 已在 Redis 中关联店铺", storeInfoVo);
this.storeId = storeInfoVo.getStoreId();
log.info("测试店铺 storeId={}", storeId);
}
// ======================================== 4.1 艺人搜索(读 MySQL kylin_artist) ========================================
/**
* 搜索可申请艺人 — 无关键词,验证返回分页列表。
*
* <pre>
* 接口: GET /apply/searchArtist?page=1&size=10
* 验证: ResponseDto.isSuccess() == true, data.list 不为 null
* </pre>
*/
@Test
public void searchArtist_shouldReturnList() {
ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> resp = artistService.searchArtist(null, 1, 10);
Assert.assertTrue("searchArtist: " + resp.getMessage(), resp.isSuccess());
Assert.assertNotNull(resp.getData());
log.info("searchArtist total={} listSize={}",
resp.getData().getTotal(),
resp.getData().getList() == null ? 0 : resp.getData().getList().size());
}
/**
* 搜索可申请艺人 — 带关键词,验证返回结果匹配。
*
* <pre>
* 接口: GET /apply/searchArtist?keyword=测试&page=1&size=10
* 验证: ResponseDto.isSuccess() == true
* </pre>
*/
@Test
public void searchArtist_byKeyword() {
ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> resp = artistService.searchArtist("测试", 1, 10);
Assert.assertTrue("searchArtist keyword: " + resp.getMessage(), resp.isSuccess());
log.info("searchArtist keyword=测试 total={}", resp.getData().getTotal());
}
// ======================================== 4.1 申请 + 撤销(MongoDB 同步写 + MySQL MQ 异步) ========================================
/**
* 提交艺人关联申请 → 撤销申请 的完整链路。
*
* <pre>
* 提交: POST /apply/submit → MongoDB insert GoblinArtistAuthApplyVo → MQ SQL insert goblin_artist_auth_apply
* 撤销: POST /apply/revoke → MongoDB update status=3 → MQ SQL update goblin_artist_auth_apply
*
* 注意:
* 该艺人已有待审核申请时,submit 会返回 failure(符合业务规则),此时跳过撤销验证。
* 撤销仅对 status=0(待审核) 的申请有效。
* </pre>
*/
@Test
public void submitApply_and_revoke() {
ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> searchResp = artistService.searchArtist(null, 1, 1);
Assert.assertTrue("需有可用艺人: " + searchResp.getMessage(), searchResp.isSuccess());
if (searchResp.getData().getList().isEmpty()) {
log.warn("skip: 无可用艺人");
return;
}
String artistId = searchResp.getData().getList().get(0).getArtistId();
GoblinStoreMgtArtistApplySubmitParams submitParams = new GoblinStoreMgtArtistApplySubmitParams();
submitParams.setArtistId(artistId);
submitParams.setAttachmentUrls(new ArrayList<>());
submitParams.getAttachmentUrls().add("https://example.com/test.jpg");
ResponseDto<String> submitResp = artistService.submitApply(submitParams);
log.info("submitApply: success={} msg={} data={}",
submitResp.isSuccess(), submitResp.getMessage(), submitResp.getData());
if (submitResp.isSuccess()) {
Assert.assertNotNull(submitResp.getData());
GoblinStoreMgtArtistApplyRevokeParams revokeParams = new GoblinStoreMgtArtistApplyRevokeParams();
revokeParams.setApplyId(submitResp.getData());
ResponseDto<Void> revokeResp = artistService.revokeApply(revokeParams);
Assert.assertTrue("revoke: " + revokeResp.getMessage(), revokeResp.isSuccess());
}
}
// ======================================== 4.1 申请列表(读 MongoDB,不缓存) ========================================
/**
* 本店申请列表 — 全部状态。
*
* <pre>
* 接口: GET /apply/list?page=1&size=10
* 数据源: MongoDB → GoblinArtistAuthApplyVo collection(列表不缓存 Redis)
* 验证: 分页正常,列表不为 null
* </pre>
*/
@Test
public void applyList_shouldReturnPage() {
ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> resp = artistService.applyList(null, 1, 10);
Assert.assertTrue("applyList: " + resp.getMessage(), resp.isSuccess());
Assert.assertNotNull(resp.getData());
log.info("applyList total={}", resp.getData().getTotal());
}
/**
* 本店申请列表 — 按状态(待审核)筛选。
*
* <pre>
* 接口: GET /apply/list?status=0&page=1&size=10
* 验证: 列表中的申请 status 均 = 0
* </pre>
*/
@Test
public void applyList_filterByStatus() {
ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> resp = artistService.applyList(GoblinArtistConst.APPLY_PENDING, 1, 10);
Assert.assertTrue("applyList status=0: " + resp.getMessage(), resp.isSuccess());
log.info("applyList status=0 total={}", resp.getData().getTotal());
}
// ======================================== 4.2 已授权艺人(读 MongoDB,不缓存) ========================================
/**
* 本店有效授权列表 — 过滤过期和失效授权。
*
* <pre>
* 接口: GET /auth/list
* 数据源: MongoDB → GoblinArtistAuthVo collection(不缓存列表)
* 验证: 仅返回 status=1 且 expireAt > now 的授权
* </pre>
*/
@Test
public void authList_shouldReturnValidAuths() {
ResponseDto<GoblinStoreMgtArtistAuthListVo> resp = artistService.authList();
Assert.assertTrue("authList: " + resp.getMessage(), resp.isSuccess());
Assert.assertNotNull(resp.getData());
log.info("authList size={}",
resp.getData().getList() == null ? 0 : resp.getData().getList().size());
}
// ======================================== 4.3 专题 全流程(MongoDB + Redis 缓存 + MySQL MQ 异步) ========================================
/**
* 专题 CURD 完整生命周期测试。
*
* <pre>
* 步骤:
* 1. POST /topic/create → MongoDB insert + Redis set (TTL=86400) + MQ SQL insert topic + goods
* 2. GET /topic/detail → Redis 命中 → 返回内嵌 goods 列表
* 3. GET /topic/list → MongoDB 按 storeId 全量查询(不缓存列表)
* 4. POST /topic/status → MongoDB update status + Redis del 单条缓存 + MQ SQL update
* 5. POST /topic/update → MongoDB update + Redis del 单条缓存 + MQ SQL update topic + goods replace
* 6. POST /topic/status → 同上(下架)
* 7. POST /topic/delete → MongoDB update delFlg=1 + Redis del + MQ SQL update
* 8. GET /topic/detail → 验证逻辑删除后不可查
*
* 数据验证:
* 步骤 2: topicName 对齐、status=0(待上架)
* 步骤 3: countPending >= 1
* 步骤 5: templateType 从 GRID(1) 改为 LIST(2)
* 步骤 8: isSuccess() == false
* </pre>
*/
@Test
public void topicCreate_update_status_delete_flow() {
// 1. 创建 —— MongoDB insert → Redis set → MQ SQL insert
String topicName = "测试专题_" + System.currentTimeMillis();
GoblinStoreMgtArtistTopicCreateParams createParams = new GoblinStoreMgtArtistTopicCreateParams();
createParams.setTopicName(topicName);
createParams.setTemplateType(GoblinArtistConst.TEMPLATE_GRID);
createParams.setBannerHeight(240);
ResponseDto<String> createResp = artistService.topicCreate(createParams);
Assert.assertTrue("create: " + createResp.getMessage(), createResp.isSuccess());
Assert.assertNotNull(createResp.getData());
String topicId = createResp.getData();
log.info("created topicId={}", topicId);
// 2. 详情 —— Redis 命中(刚创建时已缓存),goods 已内嵌
ResponseDto<GoblinStoreMgtArtistTopicDetailVo> detail = artistService.topicDetail(topicId);
Assert.assertTrue("detail: " + detail.getMessage(), detail.isSuccess());
Assert.assertEquals(topicName, detail.getData().getTopicName());
Assert.assertEquals(Integer.valueOf(GoblinArtistConst.TOPIC_PENDING), detail.getData().getStatus());
// 3. 列表 —— MongoDB 全量查,不缓存,按 sort ASC + created_at DESC 排序
ResponseDto<GoblinStoreMgtArtistTopicPageVo> listResp = artistService.topicList(null, 1, 20);
Assert.assertTrue("list: " + listResp.getMessage(), listResp.isSuccess());
Assert.assertTrue("countPending >= 1", listResp.getData().getCountPending() >= 1);
// 4. 上架 —— MongoDB update + Redis del + MQ SQL update
GoblinStoreMgtArtistTopicStatusParams statusParams = new GoblinStoreMgtArtistTopicStatusParams();
statusParams.setTopicId(topicId);
statusParams.setStatus(GoblinArtistConst.TOPIC_ONLINE);
ResponseDto<Void> onlineResp = artistService.topicStatus(statusParams);
Assert.assertTrue("online: " + onlineResp.getMessage(), onlineResp.isSuccess());
// 5. 编辑 —— MongoDB update + Redis del + MQ SQL update topic + delete/insert goods
String editedName = "测试专题_编辑_" + System.currentTimeMillis();
GoblinStoreMgtArtistTopicUpdateParams updateParams = new GoblinStoreMgtArtistTopicUpdateParams();
updateParams.setTopicId(topicId);
updateParams.setTopicName(editedName);
updateParams.setTemplateType(GoblinArtistConst.TEMPLATE_LIST);
updateParams.setBannerHeight(300);
ResponseDto<Void> updateResp = artistService.topicUpdate(updateParams);
Assert.assertTrue("update: " + updateResp.getMessage(), updateResp.isSuccess());
// 编辑后重新查详情(从 MongoDB 回源,再缓存 Redis)验证字段变更
ResponseDto<GoblinStoreMgtArtistTopicDetailVo> afterEdit = artistService.topicDetail(topicId);
Assert.assertTrue("afterEdit detail", afterEdit.isSuccess());
Assert.assertEquals(Integer.valueOf(GoblinArtistConst.TEMPLATE_LIST), afterEdit.getData().getTemplateType());
// 6. 下架 —— MongoDB update + Redis del + MQ SQL update
statusParams.setStatus(GoblinArtistConst.TOPIC_OFFLINE);
ResponseDto<Void> offlineResp = artistService.topicStatus(statusParams);
Assert.assertTrue("offline: " + offlineResp.getMessage(), offlineResp.isSuccess());
// 7. 逻辑删除 —— MongoDB update delFlg=1 + Redis del + MQ SQL update
GoblinStoreMgtArtistTopicDeleteParams deleteParams = new GoblinStoreMgtArtistTopicDeleteParams();
deleteParams.setTopicId(topicId);
ResponseDto<Void> deleteResp = artistService.topicDelete(deleteParams);
Assert.assertTrue("delete: " + deleteResp.getMessage(), deleteResp.isSuccess());
// 8. 删后 detail 不可查(delFlg=1 被过滤)
ResponseDto<GoblinStoreMgtArtistTopicDetailVo> afterDelete = artistService.topicDetail(topicId);
Assert.assertFalse("afterDelete should fail", afterDelete.isSuccess());
}
/**
* 创建含商品专题 — 验证 goods 内嵌到 MongoDB 文档中。
*
* <pre>
* 流程:
* searchGoods → 取第一个可用商品的 spuId
* topicCreate (传 goods 参数) → MongoDB 内嵌 goods(name/coverPic/sellPrice 从 GoblinGoods 冗余)
* topicDetail → 验证返回的 goods 非空且 spuId 对齐
* 清理: topicDelete
*
* 关键验证:
* goods 为空时 storeTopic 商品数 = 0(非报错)
* goods 非空时 detail.goods.size() > 0 且 spuId 一致
* </pre>
*/
@Test
public void topicCreate_withGoods() {
// 搜索可添加商品 —— MongoDB GoblinGoodsInfoVo collection,PageRequest 真分页
ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> goodsResp = artistService.searchGoods(null, 1, 5);
Assert.assertTrue("searchGoods: " + goodsResp.getMessage(), goodsResp.isSuccess());
List<GoblinStoreMgtArtistTopicGoodsItemParams> goodsList = new ArrayList<>();
if (goodsResp.getData().getList() != null && !goodsResp.getData().getList().isEmpty()) {
GoblinStoreMgtArtistTopicGoodsItemParams item = new GoblinStoreMgtArtistTopicGoodsItemParams();
item.setSpuId(goodsResp.getData().getList().get(0).getSpuId());
item.setSort(0);
goodsList.add(item);
}
// 创建含商品专题 —— goods 内嵌写入 MongoDB GoblinArtistTopicVo
GoblinStoreMgtArtistTopicCreateParams createParams = new GoblinStoreMgtArtistTopicCreateParams();
createParams.setTopicName("带商品专题_" + System.currentTimeMillis());
createParams.setTemplateType(GoblinArtistConst.TEMPLATE_GRID);
createParams.setBannerHeight(240);
createParams.setGoods(goodsList);
ResponseDto<String> createResp = artistService.topicCreate(createParams);
Assert.assertTrue("create with goods: " + createResp.getMessage(), createResp.isSuccess());
String topicId = createResp.getData();
// 验证 goods 已内嵌在专题文档中
ResponseDto<GoblinStoreMgtArtistTopicDetailVo> detail = artistService.topicDetail(topicId);
Assert.assertTrue("detail with goods", detail.isSuccess());
if (!goodsList.isEmpty()) {
Assert.assertFalse("goods should not be empty", detail.getData().getGoods().isEmpty());
Assert.assertEquals(goodsList.get(0).getSpuId(), detail.getData().getGoods().get(0).getSpuId());
}
// 清理
GoblinStoreMgtArtistTopicDeleteParams deleteParams = new GoblinStoreMgtArtistTopicDeleteParams();
deleteParams.setTopicId(topicId);
artistService.topicDelete(deleteParams);
}
// ======================================== 4.3 搜索商品(读 MongoDB GoblinGoodsInfoVo) ========================================
/**
* 搜索本店可添加商品 — 无关键词,验证 MongoDB PageRequest 分页。
*
* <pre>
* 接口: GET /topic/searchGoods?page=1&size=10
* 数据源: MongoDB → GoblinGoodsInfoVo collection
* 过滤: storeId + delFlg=0 + marketId 不存在 + spuType=0 或不存
* 分页: PageRequest.of(page-1, size) — MongoDB 级真分页
* </pre>
*/
@Test
public void searchGoods_shouldReturnPage() {
ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods(null, 1, 10);
Assert.assertTrue("searchGoods: " + resp.getMessage(), resp.isSuccess());
Assert.assertNotNull(resp.getData());
log.info("searchGoods total={} listSize={}",
resp.getData().getTotal(),
resp.getData().getList() == null ? 0 : resp.getData().getList().size());
}
/**
* 搜索本店可添加商品 — 关键词正则匹配。
*
* <pre>
* 接口: GET /topic/searchGoods?keyword=测试&page=1&size=10
* 匹配: Pattern.compile("^.*测试.*$", CASE_INSENSITIVE) 对 name 字段正则
* </pre>
*/
@Test
public void searchGoods_byKeyword() {
ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods("测试", 1, 10);
Assert.assertTrue("searchGoods keyword: " + resp.getMessage(), resp.isSuccess());
log.info("searchGoods keyword=测试 total={}", resp.getData().getTotal());
}
/**
* 搜索本店可添加商品 — 翻页到第 2 页,验证 skip/limit 正确。
*
* <pre>
* 接口: GET /topic/searchGoods?page=2&size=5
* 验证: 第 2 页数据量 <= 5,且 total 与首页一致
* </pre>
*/
@Test
public void searchGoods_secondPage() {
ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods(null, 2, 5);
Assert.assertTrue("searchGoods page2: " + resp.getMessage(), resp.isSuccess());
log.info("searchGoods page=2 total={} listSize={}",
resp.getData().getTotal(),
resp.getData().getList() == null ? 0 : resp.getData().getList().size());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment