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

Commit 00f57842 authored by wangyifan's avatar wangyifan

草莓护照V1.1-home页数据过滤签证卡片类型 ;增加查询签证卡片接口

parent 5e155683
......@@ -27,7 +27,7 @@ public class AdamCaomeiPassportBadgeShelfItemVo {
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("类型 1护照 2演出 3特殊")
@ApiModelProperty("类型 1护照 2演出 3特殊(不含 4 签证页,签证页见签证卡片接口)")
private Integer type;
@ApiModelProperty("关联演出ID(演出纪念徽章、签证页)")
......
......@@ -13,9 +13,9 @@ public class AdamCaomeiPassportHomeVo {
@ApiModelProperty("个人信息卡片")
private AdamCaomeiPassportUserCardVo userCard;
@ApiModelProperty("已认领徽章(全部获得记录,用于网格墙)")
@ApiModelProperty("已认领徽章(用于网格墙;不含 type=4 签证页,签证页见签证卡片列表接口)")
private List<AdamCaomeiPassportUserClaimedBadgeVo> claimedBadges;
@ApiModelProperty("全部上架徽章(扁平列表;演出类含 performanceName,前端可按类型或按演出分组展示)")
@ApiModelProperty("全部上架徽章货架(不含 type=4 签证页;演出类含 performanceName)")
private List<AdamCaomeiPassportBadgeShelfItemVo> allBadges;
}
package com.liquidnet.service.adam.dto.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.liquidnet.commons.lang.util.DateUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* 草莓护照-签证页卡片(type=4,独立展示,不出现在首页徽章货架/徽章墙)
*/
@Data
@ApiModel("草莓护照-签证页卡片")
public class AdamCaomeiVisaCardVo {
@ApiModelProperty("签证页配置ID(徽章ID)")
private String badgeId;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("副标题")
private String subTitle;
@ApiModelProperty("图标")
private String icon;
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("关联音乐节/演出ID")
private String performanceId;
@ApiModelProperty("关联音乐节/演出名称")
private String performanceName;
@ApiModelProperty("是否已获得(本接口仅从缓存/库读取已发放记录,恒为 true)")
private boolean claimed;
@ApiModelProperty("获得时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date claimedAt;
}
......@@ -2,6 +2,7 @@ package com.liquidnet.service.adam.service;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportHomeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportUserClaimedBadgeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiVisaCardVo;
import com.liquidnet.service.base.ResponseDto;
import java.util.List;
......@@ -17,10 +18,16 @@ public interface IAdamCaomeiPassportUserService {
ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bindPassport(String passportNo);
/**
* 护照首页:个人信息、实名状态、已认领墙、按类型分组的全部上架徽章
* 护照首页:个人信息、实名状态、已认领墙、上架徽章货架;末尾自动发放满足条件的 type=4 签证页(响应中不含签证页)
*/
ResponseDto<AdamCaomeiPassportHomeVo> getPassportHome();
/**
* 签证页卡片列表(type=4):仅从用户已获徽章缓存/库中筛选 type=4。
* 需用户先访问护照首页 {@link #getPassportHome()} 触发静默发放后,本接口才有数据。
*/
ResponseDto<List<AdamCaomeiVisaCardVo>> listVisaCards();
/**
* 校验当前登录用户是否已绑定护照
*/
......
......@@ -5,6 +5,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.service.adam.dto.param.AdamCaomeiPassportNoParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportHomeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportUserClaimedBadgeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiVisaCardVo;
import com.liquidnet.service.adam.service.IAdamCaomeiPassportUserService;
import com.liquidnet.service.base.ResponseDto;
import io.swagger.annotations.Api;
......@@ -43,6 +44,13 @@ public class AdamCaomeiPassportUserController {
}
@ApiOperationSupport(order = 3)
@ApiOperation("签证页卡片列表:仅从用户已获徽章中筛选 type=4;须先访问护照首页 home 完成静默发放后再查看")
@GetMapping("visa/list")
public ResponseDto<List<AdamCaomeiVisaCardVo>> visaList() {
return adamCaomeiPassportUserService.listVisaCards();
}
@ApiOperationSupport(order = 4)
@ApiOperation("校验当前用户是否已绑定护照")
@GetMapping("/check")
public ResponseDto<Boolean> checkBound() {
......
......@@ -31,6 +31,12 @@ import java.util.stream.Collectors;
@Service
public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUserService {
/** 徽章类型:签证页(独立卡片区展示,不出现在首页徽章货架/徽章墙) */
private static final int BADGE_TYPE_VISA = 4;
/** 用户获得途径:购票自动发放(签证页在 home 末尾静默发放时使用) */
private static final int USER_BADGE_SOURCE_PAID_AUTO = 2;
@Autowired
private AdamCaomeiPassportMapper adamCaomeiPassportMapper;
@Autowired
......@@ -189,6 +195,7 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
uid, isRealNameVerified, StringUtils.isNotBlank(idCard));
// 5. TODO 优化点
// 身份证号已购买的演出ID
final List<String> paidPerformanceIds = StringUtils.isNotBlank(idCard)
? adamRdmService.getPaidPerformanceIdsByIdCard(idCard)
: new ArrayList<>();
......@@ -201,27 +208,40 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
}
ApplyBadgeStatus applyBadgeStatus = loadApplyBadgeStatus(uid, published);
// 6. 查询用户已认领的所有徽章记录 (用于展示徽章墙)
// 用户已获得记录(含 type=4;展示层再过滤)
List<AdamCaomeiPassportUserBadgeDto> rows = adamRdmService.getUserCaomeiBadgesByUid(uid);
if (rows == null) {
rows = new ArrayList<>();
} else {
rows = new ArrayList<>(rows);
}
// 末尾:实名且已购对应场次时,静默发放 type=4 签证页(写用户徽章 Redis + MQ 落库)
boolean visaGranted = syncVisaPageBadgesIfEligible(uid, published, rows, paidPerformanceIds);
if (visaGranted) {
List<AdamCaomeiPassportUserBadgeDto> refreshed = adamRdmService.getUserCaomeiBadgesByUid(uid);
rows = refreshed == null ? new ArrayList<>() : new ArrayList<>(refreshed);
log.info("[getPassportHome] 本次静默发放签证页后刷新用户徽章缓存, uid: {}", uid);
}
Map<String, String> claimedPerformanceTitleById = buildClaimedPerformanceTitleMap(rows);
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows, claimedPerformanceTitleById);
// 首页徽章墙:排除 type=4
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows, claimedPerformanceTitleById).stream()
.filter(v -> !isVisaBadgeType(v.getType()))
.collect(Collectors.toList());
home.setClaimedBadges(claimed);
log.info("[getPassportHome] 用户已认领的徽章数量, uid: {}, 数量: {}", uid, claimed.size());
log.info("[getPassportHome] 用户已认领的徽章数量(不含签证页), uid: {}, 数量: {}", uid, claimed.size());
// 转换为 Map 方便后续匹配货架上的徽章是否已认领
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap = toClaimedBadgeMap(rows);
log.info("[getPassportHome] 系统已上架的徽章数量, uid: {}, 数量: {}", uid, published.size());
// 演出纪念徽章:批量查演出名称,供前端按演出分组展示
Map<String, String> performanceTitleById = buildPerformanceTitleMap(published);
// 首页货架:排除 type=4,仅 type=2 需要演出名称
List<AdamCaomeiBadge> shelfPublished = filterShelfPublished(published);
Map<String, String> performanceTitleById = buildPerformanceTitleMap(shelfPublished);
// 8. 组装全部上架徽章列表 (扁平结构,前端按 type / 演出名称 筛选分组展示)
List<AdamCaomeiPassportBadgeShelfItemVo> allBadges = toShelfItems(
published,
shelfPublished,
claimedMap,
paidPerformanceIds,
applyBadgeStatus,
......@@ -233,6 +253,41 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
return ResponseDto.success(home);
}
@Override
public ResponseDto<List<AdamCaomeiVisaCardVo>> listVisaCards() {
String uid = CurrentUtil.getCurrentUid();
AdamCaomeiPassport bound = findBoundPassportForUser(uid);
if (bound == null) {
log.error("[listVisaCards] 用户未绑定护照, uid: {}", uid);
return ResponseDto.failure(ErrorMapping.get("10604"));
}
// 仅读用户已获徽章列表(Redis 缓存 miss 时回源 DB);须先访问 home 触发 type=4 静默发放后才有数据
List<AdamCaomeiPassportUserBadgeDto> rows = adamRdmService.getUserCaomeiBadgesByUid(uid);
if (rows == null) {
rows = Collections.emptyList();
}
List<AdamCaomeiPassportUserBadgeDto> visaRows = rows.stream()
.filter(r -> r != null && isVisaBadgeType(r.getType()))
.collect(Collectors.toList());
if (visaRows.isEmpty()) {
return ResponseDto.success(Collections.emptyList());
}
Map<String, String> performanceTitleById = buildPerformanceTitleMapByPerfIds(
visaRows.stream()
.map(AdamCaomeiPassportUserBadgeDto::getPerformanceId)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList())
);
List<AdamCaomeiVisaCardVo> cards = visaRows.stream()
.map(r -> toVisaCardVoFromUserBadge(r, performanceTitleById))
.collect(Collectors.toList());
return ResponseDto.success(cards);
}
@Override
public ResponseDto<Boolean> checkPassportBound() {
String uid = CurrentUtil.getCurrentUid();
......@@ -372,6 +427,118 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
return map;
}
/**
* 按演出 ID 批量解析名称(Kylin 演出缓存),供签证卡片等展示。
*/
private Map<String, String> buildPerformanceTitleMapByPerfIds(List<String> perfIds) {
if (perfIds == null || perfIds.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> map = new HashMap<>(perfIds.size() * 2);
for (String perfId : perfIds) {
if (StringUtils.isBlank(perfId)) {
continue;
}
String title = adamRdmService.getPerformanceTitleById(perfId);
if (StringUtils.isNotBlank(title)) {
map.put(perfId, title);
}
}
return map;
}
/**
* 将用户已获徽章 DTO 转为签证页卡片 VO(仅已发放记录调用)。
*/
private static AdamCaomeiVisaCardVo toVisaCardVoFromUserBadge(AdamCaomeiPassportUserBadgeDto r,
Map<String, String> performanceTitleById) {
AdamCaomeiVisaCardVo vo = new AdamCaomeiVisaCardVo();
vo.setBadgeId(r.getBadgeId());
vo.setName(StringUtils.defaultString(r.getBadgeName()));
vo.setSubTitle(StringUtils.defaultString(r.getSubTitle()));
vo.setIcon(StringUtils.defaultString(r.getIcon()));
vo.setShareText(StringUtils.defaultString(r.getShareText()));
String perfId = StringUtils.trimToEmpty(r.getPerformanceId());
vo.setPerformanceId(perfId);
String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
vo.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
vo.setClaimed(true);
vo.setClaimedAt(r.getClaimedAt());
return vo;
}
/**
* home 末尾:对已上架 type=4、用户已购对应场次且尚未获得的签证页执行静默发放。
*
* @return 是否本次有发放(用于刷新用户徽章缓存后再组装首页响应)
*/
private boolean syncVisaPageBadgesIfEligible(String uid,
List<AdamCaomeiBadge> published,
List<AdamCaomeiPassportUserBadgeDto> badgeVos,
List<String> paidPerformanceIds) {
if (StringUtils.isBlank(uid) || published == null || published.isEmpty()) {
return false;
}
if (paidPerformanceIds == null || paidPerformanceIds.isEmpty()) {
return false;
}
Set<String> paidSet = new HashSet<>(paidPerformanceIds);
Set<String> claimedBadgeIds = badgeVos.stream()
.map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
List<AdamCaomeiBadge> toGrant = published.stream()
.filter(b -> b != null && isVisaBadgeType(b.getType()))
.filter(b -> StringUtils.isNotBlank(b.getPerformanceId()))
.filter(b -> paidSet.contains(StringUtils.trimToEmpty(b.getPerformanceId())))
.filter(b -> StringUtils.isNotBlank(b.getBadgeId()) && !claimedBadgeIds.contains(b.getBadgeId()))
.collect(Collectors.toList());
if (toGrant.isEmpty()) {
return false;
}
Date grantAt = DateUtil.now();
List<AdamCaomeiPassportUserBadgeDto> appendVos = new ArrayList<>(toGrant.size());
for (AdamCaomeiBadge badge : toGrant) {
AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
dto.setBadgeId(badge.getBadgeId());
dto.setBadgeName(StringUtils.defaultString(badge.getName()));
dto.setSubTitle(StringUtils.defaultString(badge.getSubTitle()));
dto.setIcon(StringUtils.defaultString(badge.getIcon()));
dto.setShareText(StringUtils.defaultString(badge.getShareText()));
dto.setType(badge.getType());
dto.setPerformanceId(StringUtils.trimToEmpty(badge.getPerformanceId()));
dto.setClaimedAt(grantAt);
dto.setSource(USER_BADGE_SOURCE_PAID_AUTO);
appendVos.add(dto);
}
adamRdmService.addUserCaomeiBadgeDtosByUid(uid, badgeVos, appendVos);
LinkedList<Object[]> paramsList = new LinkedList<>();
for (AdamCaomeiBadge badge : toGrant) {
paramsList.add(new Object[]{uid, badge.getBadgeId(), USER_BADGE_SOURCE_PAID_AUTO, grantAt});
}
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_user_badge.add", paramsList)
);
log.info("[syncVisaPageBadgesIfEligible] 静默发放签证页, uid: {}, badgeIds: {}",
uid, toGrant.stream().map(AdamCaomeiBadge::getBadgeId).collect(Collectors.toList()));
return true;
}
/** 首页徽章货架/墙:排除签证页配置 */
private static List<AdamCaomeiBadge> filterShelfPublished(List<AdamCaomeiBadge> published) {
return published.stream()
.filter(b -> b != null && !isVisaBadgeType(b.getType()))
.collect(Collectors.toList());
}
private static boolean isVisaBadgeType(Integer type) {
return type != null && type == BADGE_TYPE_VISA;
}
private static List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
......@@ -436,6 +603,9 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
} else if (type == 3) {
// 特殊徽章:不可自助领取,需要提示用户
v.setClaimable(false);
} else if (type == BADGE_TYPE_VISA) {
// 签证页不在首页货架展示;若配置误入货架列表,不可点击认领(由 home 静默发放)
v.setClaimable(false);
}
} else {
// 已认领的徽章,可认领状态置为 false
......
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