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

Commit f52ffd52 authored by 姜秀龙's avatar 姜秀龙

艺人合并需求-第二部分add

parent 488e9f3b
-- 艺人关联商品表
CREATE TABLE IF NOT EXISTS `kylin_artist_product` (
`mid` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`relation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联记录ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人ID',
`spu_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品SPU ID',
`sort` int DEFAULT 0 COMMENT '排序权重,越大越靠前',
`status` tinyint DEFAULT 1 COMMENT '1启用 0禁用',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_relation_id` (`relation_id`),
UNIQUE KEY `uk_artist_spu` (`artist_id`, `spu_id`),
KEY `idx_artist_id` (`artist_id`),
KEY `idx_spu_id` (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='艺人关联商品';
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("艺人关联商品保存参数")
public class ArtistProductSaveParam {
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("商品SPU ID列表")
private List<String> spuIds;
}
package com.liquidnet.service.kylin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 艺人关联商品搜索选项
*/
@Data
@ApiModel("艺人关联商品搜索选项")
public class ArtistGoodsOptionVo {
@ApiModelProperty("商品SPU ID")
private String spuId;
@ApiModelProperty("商品编码")
private String spuNo;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("封面图")
private String coverPic;
@ApiModelProperty("售价")
private BigDecimal sellPrice;
@ApiModelProperty("上架状态文案")
private String shelfStatusLabel;
@ApiModelProperty("是否可选择")
private Boolean selectable;
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
@ApiModel("C端艺人详情")
public class KylinArtistDetailFrontVo implements Serializable {
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("艺人名称")
private String artistName;
@ApiModelProperty("艺人类型 1音乐人 2艺术家 3厂牌 4品牌方")
private Integer artistType;
@ApiModelProperty("艺人类型名称")
private String artistTypeName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("艺人简介")
private String introduction;
@ApiModelProperty("艺人相册")
private List<String> albumImages;
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel("C端艺人关联演出")
public class KylinArtistPerformanceFrontVo implements Serializable {
@ApiModelProperty("演出ID")
private String performanceId;
@ApiModelProperty("演出名称")
private String title;
@ApiModelProperty("封面")
private String coverPic;
@ApiModelProperty("演出开始时间")
private String timeStart;
@ApiModelProperty("状态 6购买 8售罄 9未开始")
private Integer appStatus;
@ApiModelProperty("状态文案")
private String statusName;
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ApiModel("C端艺人关联商品")
public class KylinArtistProductFrontVo implements Serializable {
@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.kylin.service;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistDetailFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistPerformanceFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistProductFrontVo;
import java.util.List;
/**
* C端艺人详情
*/
public interface IKylinArtistFrontService {
KylinArtistDetailFrontVo getArtistDetail(String artistId);
List<String> getArtistAlbum(String artistId);
List<KylinArtistPerformanceFrontVo> getArtistPerformances(String artistId);
List<KylinArtistProductFrontVo> getArtistProducts(String artistId);
}
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('关联商品')"/>
<link rel="stylesheet" th:href="@{/ajax/libs/select2/select2.min.css}"/>
<style>
body { background: #fff; }
.page-wrap { padding: 16px; }
.section-title { font-weight: bold; margin: 16px 0 8px; }
.img-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
.select2-container { min-width: 380px; }
.add-row { margin-bottom: 16px; display: flex; align-items: center; gap: 10px; }
</style>
</head>
<body class="white-bg">
<div class="page-wrap">
<div class="section-title">
艺人:<span th:text="${artistName}">--</span>
<span style="font-size:12px;color:#999;margin-left:8px;" th:text="'ID: ' + ${artistId}"></span>
</div>
<div class="add-row">
<select id="goodsSelect" style="width:400px;">
<option value="">请输入商品名称搜索...</option>
</select>
<span style="color:#999;font-size:12px;">选中商品后自动添加到列表</span>
</div>
<div class="section-title">已关联商品</div>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>商品SPU ID</th>
<th>头图</th>
<th>商品名称</th>
<th>状态</th>
<th>售价</th>
<th>操作</th>
</tr>
</thead>
<tbody id="linkedProductsBody">
<tr id="linkedProductsEmptyRow">
<td colspan="6" style="text-align:center;color:#999;">暂无关联商品</td>
</tr>
</tbody>
</table>
</div>
<button type="button" class="btn btn-primary" onclick="saveProducts()">保存</button>
</div>
<th:block th:include="include :: footer"/>
<script th:src="@{/ajax/libs/select2/select2.min.js}"></script>
<script th:inline="javascript">
var artistId = /*[[${artistId}]]*/ '';
var prefix = ctx + 'kylin/artist';
var linkedProducts = [];
$(function () {
initSelect2();
loadLinkedProducts();
});
function initSelect2() {
$('#goodsSelect').select2({
allowClear: true,
placeholder: '请输入商品名称搜索...',
minimumInputLength: 0,
ajax: {
url: prefix + '/searchGoods',
dataType: 'json',
delay: 300,
data: function (params) { return { keyword: params.term || '' }; },
processResults: function (resp) {
var list = (resp && resp.data) ? resp.data : [];
return { results: list.map(function (item) {
var text = item.name || item.spuId;
if (item.spuNo) {
text += ' (' + item.spuNo + ')';
}
return { id: item.spuId, text: text, disabled: item.selectable === false, _raw: item };
})};
}
}
}).on('select2:select', function (e) {
var raw = e.params.data._raw;
if (raw && raw.selectable === false) {
$.modal.alertWarning('该商品不可关联');
$(this).val(null).trigger('change');
return;
}
if (raw) {
addLinkedProduct(raw);
}
$(this).val(null).trigger('change');
});
}
function loadLinkedProducts() {
$.get(prefix + '/products/list', { artistId: artistId }, function (resp) {
if (resp.code === 0 && resp.data) {
linkedProducts = resp.data.map(function (item) {
return {
spuId: item.spuId,
name: item.productName,
coverPic: item.imageUrl,
sellPrice: item.price ? item.price.replace('元', '') : null,
shelfStatusLabel: item.status === 1 ? '已上架' : '未上架'
};
});
renderLinkedProducts();
}
});
}
function renderLinkedProducts() {
var $body = $('#linkedProductsBody');
$body.empty();
if (!linkedProducts.length) {
$body.append('<tr><td colspan="6" style="text-align:center;color:#999;">暂无关联商品</td></tr>');
return;
}
linkedProducts.forEach(function (item, index) {
var priceText = item.sellPrice != null ? item.sellPrice + '元' : '--';
$body.append('<tr>' +
'<td>' + (item.spuId || '--') + '</td>' +
'<td>' + (item.coverPic ? '<img class="img-thumb" src="' + item.coverPic + '">' : '--') + '</td>' +
'<td>' + (item.name || '--') + '</td>' +
'<td>' + (item.shelfStatusLabel || '--') + '</td>' +
'<td>' + priceText + '</td>' +
'<td><button type="button" class="btn btn-danger btn-xs" onclick="removeLinkedProduct(' + index + ')">移除</button></td>' +
'</tr>');
});
}
function addLinkedProduct(raw) {
if (!raw || !raw.spuId) {
return;
}
if (linkedProducts.some(function (item) { return item.spuId === raw.spuId; })) {
$.modal.alertWarning('该商品已在列表中');
return;
}
linkedProducts.push({
spuId: raw.spuId,
name: raw.name,
coverPic: raw.coverPic,
sellPrice: raw.sellPrice,
shelfStatusLabel: raw.shelfStatusLabel
});
renderLinkedProducts();
}
function removeLinkedProduct(index) {
linkedProducts.splice(index, 1);
renderLinkedProducts();
}
function saveProducts() {
var spuIds = linkedProducts.map(function (item) { return item.spuId; });
$.ajax({
url: prefix + '/products/save',
type: 'post',
contentType: 'application/json',
data: JSON.stringify({ artistId: artistId, spuIds: spuIds }),
success: function (resp) {
if (resp.code === 0) {
$.modal.msgSuccess('保存成功');
loadLinkedProducts();
} else {
$.modal.alertError(resp.msg || '保存失败');
}
}
});
}
</script>
</body>
</html>
package com.liquidnet.service.kylin.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 KylinArtistProduct implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String relationId;
private String artistId;
private String spuId;
private Integer sort;
private Integer status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private static final KylinArtistProduct obj = new KylinArtistProduct();
public static KylinArtistProduct getNew() {
try {
return (KylinArtistProduct) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinArtistProduct();
}
}
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinArtistProduct;
import java.util.List;
public interface KylinArtistProductMapper extends BaseMapper<KylinArtistProduct> {
List<KylinArtistProduct> selectByArtistId(String artistId);
int deleteByArtistId(String artistId);
}
package com.liquidnet.service.kylin.controller;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistDetailFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistPerformanceFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistProductFrontVo;
import com.liquidnet.service.kylin.service.IKylinArtistFrontService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Api(tags = "前端-艺人")
@RestController
@RequestMapping("artist")
@Slf4j
public class KylinArtistController {
@Autowired
private IKylinArtistFrontService kylinArtistFrontService;
@GetMapping("detail/{artistId}")
@ApiOperation("艺人详情(简介)")
@ApiImplicitParam(type = "path", dataType = "String", name = "artistId", value = "艺人ID", required = true)
public ResponseDto<KylinArtistDetailFrontVo> detail(@PathVariable String artistId) {
KylinArtistDetailFrontVo vo = kylinArtistFrontService.getArtistDetail(artistId);
if (vo == null) {
return ResponseDto.failure("艺人不存在");
}
return ResponseDto.success(vo);
}
@GetMapping("{artistId}/album")
@ApiOperation("艺人相册")
@ApiImplicitParam(type = "path", dataType = "String", name = "artistId", value = "艺人ID", required = true)
public ResponseDto<List<String>> album(@PathVariable String artistId) {
return ResponseDto.success(kylinArtistFrontService.getArtistAlbum(artistId));
}
@GetMapping("{artistId}/performances")
@ApiOperation("艺人关联演出(未开售/已开售,不含已结束)")
@ApiImplicitParam(type = "path", dataType = "String", name = "artistId", value = "艺人ID", required = true)
public ResponseDto<List<KylinArtistPerformanceFrontVo>> performances(@PathVariable String artistId) {
return ResponseDto.success(kylinArtistFrontService.getArtistPerformances(artistId));
}
@GetMapping("{artistId}/products")
@ApiOperation("艺人关联商品")
@ApiImplicitParam(type = "path", dataType = "String", name = "artistId", value = "艺人ID", required = true)
public ResponseDto<List<KylinArtistProductFrontVo>> products(@PathVariable String artistId) {
return ResponseDto.success(kylinArtistFrontService.getArtistProducts(artistId));
}
}
package com.liquidnet.service.kylin.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.service.kylin.constant.KylinPerformanceStatusEnum;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistDetailFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistPerformanceFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistProductFrontVo;
import com.liquidnet.service.kylin.entity.KylinArtist;
import com.liquidnet.service.kylin.entity.KylinArtistAlbum;
import com.liquidnet.service.kylin.entity.KylinArtistProduct;
import com.liquidnet.service.kylin.mapper.KylinArtistAlbumMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistPerformanceMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistProductMapper;
import com.liquidnet.service.kylin.service.IKylinArtistFrontService;
import com.liquidnet.service.kylin.utils.DataUtils;
import com.liquidnet.service.kylin.utils.GoblinRedisUtils;
import com.liquidnet.service.goblin.dto.vo.GoblinGoodsInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class KylinArtistFrontServiceImpl implements IKylinArtistFrontService {
@Autowired
private KylinArtistMapper kylinArtistMapper;
@Autowired
private KylinArtistAlbumMapper kylinArtistAlbumMapper;
@Autowired
private KylinArtistPerformanceMapper kylinArtistPerformanceMapper;
@Autowired
private KylinArtistProductMapper kylinArtistProductMapper;
@Autowired
private DataUtils dataUtils;
@Autowired
private GoblinRedisUtils goblinRedisUtils;
@Autowired
private KylinPerformancesServiceImpl kylinPerformancesService;
@Override
public KylinArtistDetailFrontVo getArtistDetail(String artistId) {
String cacheKey = KylinRedisConst.ARTIST_DETAIL + artistId;
KylinArtistDetailFrontVo cached = dataUtils.getArtistFrontCache(cacheKey);
if (cached != null) {
return cached;
}
KylinArtist artist = loadArtistForFront(artistId);
if (artist == null) {
return null;
}
KylinArtistDetailFrontVo vo = new KylinArtistDetailFrontVo();
vo.setArtistId(artist.getArtistId());
vo.setArtistName(artist.getArtistName());
vo.setArtistType(artist.getArtistType());
vo.setArtistTypeName(resolveArtistTypeName(artist.getArtistType()));
vo.setAvatarUrl(artist.getAvatarUrl());
vo.setIntroduction(artist.getIntroduction());
dataUtils.setArtistFrontCache(cacheKey, vo);
return vo;
}
@Override
public List<String> getArtistAlbum(String artistId) {
if (loadArtistForFront(artistId) == null) {
return new ArrayList<>();
}
String cacheKey = KylinRedisConst.ARTIST_ALBUM + artistId;
List<String> cached = dataUtils.getArtistFrontCache(cacheKey);
if (cached != null) {
return cached;
}
List<KylinArtistAlbum> albumList = kylinArtistAlbumMapper.selectByArtistId(artistId);
List<String> imageUrls = new ArrayList<>();
if (!CollectionUtils.isEmpty(albumList)) {
imageUrls = albumList.stream().map(KylinArtistAlbum::getImageUrl).collect(Collectors.toList());
}
dataUtils.setArtistFrontCache(cacheKey, imageUrls);
return imageUrls;
}
@Override
public List<KylinArtistPerformanceFrontVo> getArtistPerformances(String artistId) {
if (loadArtistForFront(artistId) == null) {
return new ArrayList<>();
}
String cacheKey = KylinRedisConst.ARTIST_PERFORMANCES + artistId;
List<KylinArtistPerformanceDao> baseList = dataUtils.getArtistFrontCache(cacheKey);
if (baseList == null) {
baseList = loadPerformanceBaseList(artistId);
dataUtils.setArtistFrontCache(cacheKey, baseList);
}
return buildPerformanceFrontList(baseList);
}
@Override
public List<KylinArtistProductFrontVo> getArtistProducts(String artistId) {
if (loadArtistForFront(artistId) == null) {
return new ArrayList<>();
}
String cacheKey = KylinRedisConst.ARTIST_PRODUCTS + artistId;
List<KylinArtistProductFrontVo> cached = dataUtils.getArtistFrontCache(cacheKey);
if (cached != null) {
return cached;
}
List<KylinArtistProductFrontVo> list = loadProductFrontList(artistId);
dataUtils.setArtistFrontCache(cacheKey, list);
return list;
}
private List<KylinArtistPerformanceDao> loadPerformanceBaseList(String artistId) {
List<KylinArtistPerformanceDao> performanceList = kylinArtistPerformanceMapper.selectPerformanceDaoByArtistId(artistId);
if (CollectionUtils.isEmpty(performanceList)) {
return new ArrayList<>();
}
Map<String, KylinArtistPerformanceDao> dedup = new LinkedHashMap<>();
for (KylinArtistPerformanceDao item : performanceList) {
dedup.putIfAbsent(item.getPerformanceId(), item);
}
return new ArrayList<>(dedup.values());
}
private List<KylinArtistPerformanceFrontVo> buildPerformanceFrontList(List<KylinArtistPerformanceDao> baseList) {
List<KylinArtistPerformanceFrontVo> result = new ArrayList<>();
if (CollectionUtils.isEmpty(baseList)) {
return result;
}
for (KylinArtistPerformanceDao item : baseList) {
KylinPerformanceVo performanceVo = dataUtils.getPerformanceVo(item.getPerformanceId());
if (performanceVo == null) {
continue;
}
performanceVo = kylinPerformancesService.checkAppStatusInfo(performanceVo);
Integer appStatus = performanceVo.getAppStatus();
if (appStatus == null || appStatus == 10 || appStatus == 7 || appStatus == 3 || appStatus == 11) {
continue;
}
if (appStatus != 6 && appStatus != 9 && appStatus != 8) {
continue;
}
KylinArtistPerformanceFrontVo vo = new KylinArtistPerformanceFrontVo();
vo.setPerformanceId(item.getPerformanceId());
vo.setTitle(item.getTitle());
vo.setCoverPic(performanceVo.getImgPoster());
vo.setTimeStart(item.getTimeStart());
vo.setAppStatus(appStatus == 8 ? 6 : appStatus);
vo.setStatusName(KylinPerformanceStatusEnum.getName(appStatus));
result.add(vo);
}
return result;
}
private List<KylinArtistProductFrontVo> loadProductFrontList(String artistId) {
List<KylinArtistProduct> relations = kylinArtistProductMapper.selectByArtistId(artistId);
List<KylinArtistProductFrontVo> result = new ArrayList<>();
if (CollectionUtils.isEmpty(relations)) {
return result;
}
for (KylinArtistProduct relation : relations) {
GoblinGoodsInfoVo goods = goblinRedisUtils.getGoodsInfoVo(relation.getSpuId());
if (goods == null || !"0".equals(goods.getDelFlg()) || !"3".equals(goods.getShelvesStatus())) {
continue;
}
KylinArtistProductFrontVo vo = new KylinArtistProductFrontVo();
vo.setSpuId(goods.getSpuId());
vo.setName(goods.getName());
vo.setCoverPic(goods.getCoverPic());
vo.setSellPrice(goods.getSellPrice());
vo.setSort(relation.getSort());
result.add(vo);
}
return result;
}
/**
* 按 ID 取艺人(不按 status 过滤;列表/选艺人场景在各自入口筛 status=1)
*/
private KylinArtist loadArtistForFront(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return null;
}
return kylinArtistMapper.selectOne(
Wrappers.lambdaQuery(KylinArtist.class).eq(KylinArtist::getArtistId, artistId)
);
}
private String resolveArtistTypeName(Integer artistType) {
if (artistType == null) {
return "未知";
}
switch (artistType) {
case 1:
return "音乐人";
case 2:
return "艺术家";
case 3:
return "厂牌";
case 4:
return "品牌方";
default:
return "未知";
}
}
}
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