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

Commit 8077bd58 authored by wangyifan's avatar wangyifan

Merge branch 'dev-caomeihuzhao-V1.1' into 'master'

Dev caomeihuzhao v1.1

See merge request !423
parents f2af79bb 964a3cfe
-- ============================================================
-- 草莓护照 / 徽章 v1.1 数据库变更
-- 说明:新增徽章类型「签证页」(type=4),仅更新字段注释,无结构变更
-- 适用:已在 v1.0 建表的环境(adam_caomei_badge)
-- 日期:2026-05-13
-- ============================================================
-- 1. 徽章类型:补充 4-签证页
ALTER TABLE `adam_caomei_badge`
MODIFY COLUMN `type` tinyint(4) NOT NULL COMMENT '徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章, 4-签证页';
-- 2. 关联演出:签证页同样需绑定音乐节/演出
ALTER TABLE `adam_caomei_badge`
MODIFY COLUMN `performance_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '关联演出ID (演出纪念徽章、签证页必填,其他类型为空)';
......@@ -26,10 +26,10 @@ public class AdamCaomeiBadgeParam {
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章, 4-签证页")
private Integer type;
@ApiModelProperty(value = "关联演出ID (仅演出纪念徽章必填,其他类型为空)")
@ApiModelProperty(value = "关联演出ID (演出纪念徽章、签证页必填,其他类型为空)")
private String performanceId;
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
......
......@@ -14,7 +14,7 @@ public class AdamCaomeiBadgeSearchParam {
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章, 4-签证页")
private Integer type;
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
......
......@@ -28,7 +28,7 @@ public class AdamCaomeiBadgeVo {
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章, 4-签证页")
private Integer type;
@ApiModelProperty(value = "关联演出ID")
......
......@@ -27,10 +27,10 @@ public class AdamCaomeiPassportBadgeShelfItemVo {
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("类型 1护照 2演出 3特殊")
@ApiModelProperty("类型 1护照 2演出 3特殊(不含 4 签证页,签证页见 home.visaBadges)")
private Integer type;
@ApiModelProperty("关联演出ID(演出纪念徽章)")
@ApiModelProperty("关联演出ID(演出纪念徽章、签证页)")
private String performanceId;
@ApiModelProperty("关联演出名称(type=2 时用于按演出分组展示;无数据时可能为演出ID)")
......
......@@ -13,9 +13,12 @@ public class AdamCaomeiPassportHomeVo {
@ApiModelProperty("个人信息卡片")
private AdamCaomeiPassportUserCardVo userCard;
@ApiModelProperty("已认领徽章(全部获得记录,用于网格墙)")
@ApiModelProperty("已认领徽章(用于网格墙;不含 type=4 签证页)")
private List<AdamCaomeiPassportUserClaimedBadgeVo> claimedBadges;
@ApiModelProperty("全部上架徽章(扁平列表;演出类含 performanceName,前端可按类型或按演出分组展示)")
@ApiModelProperty("签证页卡片(type=4;home 内静默发放后返回,含 performanceName)")
private List<AdamCaomeiPassportUserClaimedBadgeVo> visaBadges;
@ApiModelProperty("全部上架徽章货架(不含 type=4 签证页;演出类含 performanceName)")
private List<AdamCaomeiPassportBadgeShelfItemVo> allBadges;
}
......@@ -9,7 +9,7 @@ import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-已认领徽章(墙)")
@ApiModel("草莓护照-用户已获徽章(徽章墙、签证页卡片等共用)")
public class AdamCaomeiPassportUserClaimedBadgeVo {
@ApiModelProperty("徽章ID")
......@@ -27,10 +27,13 @@ public class AdamCaomeiPassportUserClaimedBadgeVo {
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("类型 1护照类型徽章 2演出类型徽章 3特殊徽章")
@ApiModelProperty("类型 1护照类型徽章 2演出类型徽章 3特殊徽章 4签证页")
private Integer type;
@ApiModelProperty("关联演出名称(仅 type=2 有值)")
@ApiModelProperty("关联音乐节/演出ID(type=2、type=4 有值)")
private String performanceId;
@ApiModelProperty("关联音乐节/演出名称(type=2、type=4 有值)")
private String performanceName;
@ApiModelProperty("获得时间")
......
......@@ -17,7 +17,8 @@ public interface IAdamCaomeiPassportUserService {
ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bindPassport(String passportNo);
/**
* 护照首页:个人信息、实名状态、已认领墙、按类型分组的全部上架徽章
* 护照首页:个人信息、实名状态、已认领墙、签证页(type=4)、上架徽章货架;
* 末尾自动发放满足条件的 type=4 签证页,并在 {@link AdamCaomeiPassportHomeVo#getVisaBadges()} 返回。
*/
ResponseDto<AdamCaomeiPassportHomeVo> getPassportHome();
......
......@@ -94,11 +94,11 @@ public class AdamCaomeiBadgeController extends BaseController {
badge.setCreatedAt(new Date());
badge.setUpdatedAt(new Date());
// 演出类型校验
if (badge.getType() != null && badge.getType() == 2) {
// 演出纪念徽章、签证页须关联音乐节
if (badgeRequiresPerformance(badge.getType())) {
String pid = StringUtils.trimToEmpty(badge.getPerformanceId());
if (StringUtils.isBlank(pid)) {
return error("演出纪念徽章必须关联演出");
return error(badgePerformanceRequiredMessage(badge.getType()));
}
badge.setPerformanceId(pid);
if (!adamCaomeiBadgeAdminService.kylinPerformanceExists(pid)) {
......@@ -184,10 +184,10 @@ public class AdamCaomeiBadgeController extends BaseController {
badge.setUpdatedAt(new java.util.Date());
// 徽章类型与「已发布不可改」一致:编辑时不允许变更类型
badge.setType(oldBadge.getType());
if (badge.getType() != null && badge.getType() == 2) {
if (badgeRequiresPerformance(badge.getType())) {
String pid = StringUtils.trimToEmpty(badge.getPerformanceId());
if (StringUtils.isBlank(pid)) {
return error("演出纪念徽章必须关联演出");
return error(badgePerformanceRequiredMessage(badge.getType()));
}
badge.setPerformanceId(pid);
if (!adamCaomeiBadgeAdminService.kylinPerformanceExists(pid)) {
......@@ -217,6 +217,17 @@ public class AdamCaomeiBadgeController extends BaseController {
return toAjax(adamCaomeiBadgeAdminService.updateById(updateBadge));
}
private static boolean badgeRequiresPerformance(Integer type) {
return type != null && (type == 2 || type == 4);
}
private static String badgePerformanceRequiredMessage(Integer type) {
if (type != null && type == 4) {
return "签证页必须关联音乐节";
}
return "演出纪念徽章必须关联演出";
}
/**
* @return 校验通过返回 null,否则返回错误 AjaxResult
*/
......
......@@ -39,6 +39,7 @@
<option value="">请选择</option>
<option value="1">护照纪念徽章</option>
<option value="2">演出纪念徽章</option>
<option value="4">签证页</option>
<!-- <option value="3">特殊徽章</option> -->
</select>
</div>
......@@ -59,7 +60,7 @@
<label class="col-sm-3 control-label is-required">关联演出:</label>
<div class="col-sm-8">
<input name="performanceId" id="performanceId" class="form-control" type="text" placeholder="请输入演出ID">
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 演出纪念徽章必填</span>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 演出纪念徽章、签证页必填</span>
</div>
</div>
</form>
......@@ -71,7 +72,7 @@
var platformUrl = /*[[${platformUrl}]]*/ '';
function typeChange(val) {
if (val == 2) {
if (val == 2 || val == 4) {
$("#ticketTimesDiv").show();
$("#performanceId").prop("required", true);
} else {
......
......@@ -32,10 +32,11 @@
<div class="col-sm-8">
<div class="form-control-static" th:if="*{type == 1}">护照纪念徽章</div>
<div class="form-control-static" th:if="*{type == 2}">演出纪念徽章</div>
<div class="form-control-static" th:if="*{type == 3}">特殊徽章</div>
<div class="form-control-static" th:if="*{type == 4}">签证页</div>
<!-- <div class="form-control-static" th:if="*{type == 3}">特殊徽章</div>-->
</div>
</div>
<div class="form-group" th:style="${badge.type == 2 ? 'display:block;' : 'display:none;'}">
<div class="form-group" th:style="${badge.type == 2 or badge.type == 4 ? 'display:block;' : 'display:none;'}">
<label class="col-sm-3 control-label">关联演出:</label>
<div class="col-sm-8">
<div class="form-control-static" th:text="*{performanceId}"></div>
......
......@@ -47,6 +47,7 @@
<select class="form-control m-b" disabled>
<option th:selected="${badge.type == 1}" value="1">护照纪念徽章</option>
<option th:selected="${badge.type == 2}" value="2">演出纪念徽章</option>
<option th:selected="${badge.type == 4}" value="4">签证页</option>
</select>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 徽章类型保存后不可修改</span>
</div>
......@@ -63,11 +64,11 @@
<textarea name="shareText" th:field="*{shareText}" class="form-control" rows="3" maxlength="255" placeholder="请输入徽章分享文案"></textarea>
</div>
</div>
<div class="form-group" id="ticketTimesDiv" th:style="${badge.type == 2 ? 'display:block;' : 'display:none;'}">
<div class="form-group" id="ticketTimesDiv" th:style="${badge.type == 2 or badge.type == 4 ? 'display:block;' : 'display:none;'}">
<label class="col-sm-3 control-label is-required">关联演出:</label>
<div class="col-sm-8">
<input name="performanceId" id="performanceId" th:field="*{performanceId}" class="form-control" type="text" placeholder="请输入演出ID" th:required="${badge.type == 2}">
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 演出纪念徽章必填</span>
<input name="performanceId" id="performanceId" th:field="*{performanceId}" class="form-control" type="text" placeholder="请输入演出ID" th:required="${badge.type == 2 or badge.type == 4}">
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 演出纪念徽章、签证页必填</span>
</div>
</div>
</form>
......@@ -79,7 +80,7 @@
var platformUrl = /*[[${platformUrl}]]*/ '';
function typeChange(val) {
if (val == 2 || val == '2') {
if (val == 2 || val == '2' || val == 4 || val == '4') {
$("#ticketTimesDiv").show();
$("#performanceId").prop("required", true);
} else {
......
......@@ -18,6 +18,7 @@
<option value="">所有</option>
<option value="1">护照纪念徽章</option>
<option value="2">演出纪念徽章</option>
<option value="4">签证页</option>
<!-- <option value="3">特殊徽章</option> -->
</select>
</li>
......@@ -106,6 +107,7 @@
if (value == 1) return '<span class="badge badge-info">护照纪念</span>';
if (value == 2) return '<span class="badge badge-primary">演出纪念</span>';
if (value == 3) return '<span class="badge badge-warning">特殊徽章</span>';
if (value == 4) return '<span class="badge badge-success">签证页</span>';
return value;
}
},
......
package com.liquidnet.service.adam.dto;
import lombok.Data;
@Data
public class AdamCaomeiPerformanceIdTitleDto {
private String performanceId;
private String title;
}
......@@ -44,12 +44,12 @@ public class AdamCaomeiBadge implements Serializable {
private String icon;
/**
* 徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章
* 徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章, 4-签证页
*/
private Integer type;
/**
* 关联演出ID (仅演出纪念徽章必填,其他类型为空)
* 关联演出ID (演出纪念徽章、签证页必填,其他类型为空)
*/
private String performanceId;
......
......@@ -3,6 +3,7 @@ package com.liquidnet.service.adam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeClaimCountDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeClaimUserDto;
import com.liquidnet.service.adam.dto.AdamCaomeiPerformanceIdTitleDto;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
......@@ -107,4 +108,16 @@ public interface AdamCaomeiBadgeMapper extends BaseMapper<AdamCaomeiBadge> {
@Select("select title from kylin_performances where performances_id = #{performanceId}")
String selectKylinPerformanceTitleById(@Param("performanceId") String performanceId);
@Select({
"<script>",
"select performances_id as performanceId, title",
"from kylin_performances",
"where performances_id in",
"<foreach collection='performanceIds' item='performanceId' open='(' separator=',' close=')'>",
"#{performanceId}",
"</foreach>",
"</script>"
})
List<AdamCaomeiPerformanceIdTitleDto> selectKylinPerformanceTitlesByIds(@Param("performanceIds") List<String> performanceIds);
}
......@@ -36,7 +36,7 @@ public class AdamCaomeiPassportUserController {
}
@ApiOperationSupport(order = 2)
@ApiOperation("护照首页聚合数据")
@ApiOperation("护照首页聚合数据(含签证页 type=4 字段 visaBadges,访问时自动静默发放)")
@GetMapping("home")
public ResponseDto<AdamCaomeiPassportHomeVo> home() {
return adamCaomeiPassportUserService.getPassportHome();
......
......@@ -12,6 +12,7 @@ import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.commons.lang.util.SensitizeUtil;
import com.liquidnet.service.adam.constant.AdamRedisConst;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.AdamCaomeiPerformanceIdTitleDto;
import com.liquidnet.service.adam.dto.AdamUserInfoDto;
import com.liquidnet.service.adam.dto.vo.*;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
......@@ -807,21 +808,62 @@ public class AdamRdmService {
* 读取不到时返回 null。
*/
public String getPerformanceTitleById(String performanceId) {
try {
KylinPerformanceVo vo = getKylinPerformanceVoById(performanceId);
if (vo != null) {
return vo.getTitle();
if (StringUtils.isEmpty(performanceId)) {
return null;
}
String id = performanceId.trim();
Map<String, String> map = getPerformanceTitleMapByIds(Collections.singletonList(id));
return map.get(id);
}
/**
* 批量解析演出 ID → 标题:先走 Kylin Redis,缓存未命中的 ID 合并为一次 DB IN 查询。
*/
public Map<String, String> getPerformanceTitleMapByIds(Collection<String> performanceIds) {
if (CollectionUtils.isEmpty(performanceIds)) {
return Collections.emptyMap();
}
List<String> ids = performanceIds.stream()
.filter(id -> !StringUtils.isEmpty(id))
.map(String::trim)
.distinct()
.collect(Collectors.toList());
if (ids.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>(ids.size() * 2);
List<String> missingFromCache = new ArrayList<>();
for (String id : ids) {
try {
KylinPerformanceVo vo = getKylinPerformanceVoById(id);
if (vo != null) {
String title = vo.getTitle();
if (!StringUtils.isEmpty(title)) {
result.put(id, title);
}
} else {
missingFromCache.add(id);
}
} catch (Exception e) {
log.warn("[getPerformanceTitleMapByIds] 读取演出缓存失败, performanceId: {}", id, e);
missingFromCache.add(id);
}
// 从数据库查询
String title = adamCaomeiBadgeMapper.selectKylinPerformanceTitleById(performanceId);
if (!StringUtils.isEmpty(title)) {
return title;
}
if (!missingFromCache.isEmpty()) {
List<AdamCaomeiPerformanceIdTitleDto> rows =
adamCaomeiBadgeMapper.selectKylinPerformanceTitlesByIds(missingFromCache);
if (!CollectionUtils.isEmpty(rows)) {
for (AdamCaomeiPerformanceIdTitleDto row : rows) {
if (row == null || StringUtils.isEmpty(row.getPerformanceId()) || StringUtils.isEmpty(row.getTitle())) {
continue;
}
result.put(row.getPerformanceId().trim(), row.getTitle());
}
}
return null;
} catch (Exception e) {
log.warn("[getPerformanceTitleById] 读取演出缓存失败, performanceId: {}", performanceId, e);
return null;
}
return result;
}
/**
......
package com.liquidnet.service.adam.service.caomei;
import java.util.Collections;
import java.util.Set;
/**
* 用户补签审核状态快照(按「徽章 ID」与「场次 performanceId」两个维度聚合)。
* <p>
* 由 {@link CaomeiBadgeEligibilityService#loadApplyBadgeStatus} 从 DB 补签表加载,
* 供护照首页货架({@code claimable} / {@code applyPending})与演出徽章认领前置判断共用,
* 避免 home 展示与 POST claim 规则不一致。
*/
public final class CaomeiBadgeApplyStatus {
/** 审核已通过(auditStatus=1)的补签申请对应的 badgeId */
private final Set<String> passedApplyBadgeIds;
/** 待审核(auditStatus=0)的补签申请对应的 badgeId */
private final Set<String> pendingApplyBadgeIds;
/** 审核已通过补签所关联的场次 ID(含从 badge 配置兜底解析的 performanceId) */
private final Set<String> passedApplyPerformanceIds;
/** 待审核补签所关联的场次 ID */
private final Set<String> pendingApplyPerformanceIds;
/**
* @param passedApplyBadgeIds 已通过补签的徽章 ID 集合,可为 null(视为空集)
* @param pendingApplyBadgeIds 待审核补签的徽章 ID 集合,可为 null
* @param passedApplyPerformanceIds 已通过补签的场次 ID 集合,可为 null
* @param pendingApplyPerformanceIds 待审核补签的场次 ID 集合,可为 null
*/
public CaomeiBadgeApplyStatus(Set<String> passedApplyBadgeIds,
Set<String> pendingApplyBadgeIds,
Set<String> passedApplyPerformanceIds,
Set<String> pendingApplyPerformanceIds) {
this.passedApplyBadgeIds = passedApplyBadgeIds == null ? Collections.emptySet() : passedApplyBadgeIds;
this.pendingApplyBadgeIds = pendingApplyBadgeIds == null ? Collections.emptySet() : pendingApplyBadgeIds;
this.passedApplyPerformanceIds = passedApplyPerformanceIds == null ? Collections.emptySet() : passedApplyPerformanceIds;
this.pendingApplyPerformanceIds = pendingApplyPerformanceIds == null ? Collections.emptySet() : pendingApplyPerformanceIds;
}
public Set<String> getPassedApplyBadgeIds() {
return passedApplyBadgeIds;
}
public Set<String> getPendingApplyBadgeIds() {
return pendingApplyBadgeIds;
}
public Set<String> getPassedApplyPerformanceIds() {
return passedApplyPerformanceIds;
}
public Set<String> getPendingApplyPerformanceIds() {
return pendingApplyPerformanceIds;
}
}
package com.liquidnet.service.adam.service.caomei;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.service.adam.dto.vo.AdamRealInfoVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.adam.service.AdamRdmService;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 草莓徽章「领取资格」判断:护照首页货架展示(是否可点领取、是否补签待审)
* 与 {@code POST caomei/badge/claim} 演出徽章校验共用同一套规则。
* <p>
* 不负责写 Redis/MQ;发放见 {@link CaomeiBadgeGrantService}。
*/
@Service
public class CaomeiBadgeEligibilityService {
/** 徽章类型:签证页(仅 home 静默发放,不可 claim) */
private static final int BADGE_TYPE_VISA = 4;
@Autowired
private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
@Autowired
private AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
@Autowired
private AdamRdmService adamRdmService;
/**
* 加载用户补签状态,用于 home 货架 {@code allBadges} 的 claimable / applyPending。
* <p>
* 只查 auditStatus 为 0(待审)、1(通过)的记录;驳回(2) 不参与。
* 补签记录若未填 performanceId,会用 {@code published} 里 badgeId→performanceId 兜底,
* 以便按「场次」聚合待审/通过状态。
*
* @param uid 当前用户 ID
* @param published 已上架徽章全量列表(来自 published Redis),用于 performanceId 兜底
*/
public CaomeiBadgeApplyStatus loadApplyBadgeStatus(String uid, List<AdamCaomeiBadge> published) {
// 徽章ID 演出ID Map
Map<String, String> badgeIdToPerformanceId = new HashMap<>();
if (published != null) {
for (AdamCaomeiBadge b : published) {
if (b != null && StringUtils.isNotBlank(b.getBadgeId())) {
badgeIdToPerformanceId.put(b.getBadgeId(), StringUtils.trimToEmpty(b.getPerformanceId()));
}
}
}
// 查询用户提交的补签申请(0 待审核、1 已通过)
List<AdamCaomeiBadgeApplyRecord> applyRecords = badgeApplyRecordMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.in(AdamCaomeiBadgeApplyRecord::getAuditStatus, 0, 1)
);
Set<String> passedApplyBadgeIds = new HashSet<>(); // 补签通过的徽章ID
Set<String> pendingApplyBadgeIds = new HashSet<>(); // 补签中的徽章ID
Set<String> passedApplyPerformanceIds = new HashSet<>(); // 补签通过的演出ID
Set<String> pendingApplyPerformanceIds = new HashSet<>(); // 补签中的演出ID
if (applyRecords != null) {
for (AdamCaomeiBadgeApplyRecord r : applyRecords) {
if (r == null || StringUtils.isBlank(r.getBadgeId()) || r.getAuditStatus() == null) {
continue;
}
String perf = StringUtils.trimToEmpty(r.getPerformanceId());
if (StringUtils.isBlank(perf)) {
perf = StringUtils.trimToEmpty(badgeIdToPerformanceId.get(r.getBadgeId()));
}
if (r.getAuditStatus() == 1) {
passedApplyBadgeIds.add(r.getBadgeId());
if (StringUtils.isNotBlank(perf)) {
passedApplyPerformanceIds.add(perf);
}
} else if (r.getAuditStatus() == 0) {
pendingApplyBadgeIds.add(r.getBadgeId());
if (StringUtils.isNotBlank(perf)) {
pendingApplyPerformanceIds.add(perf);
}
}
}
}
return new CaomeiBadgeApplyStatus(
passedApplyBadgeIds,
pendingApplyBadgeIds,
passedApplyPerformanceIds,
pendingApplyPerformanceIds
);
}
/**
* 为本次 claim 请求中的 type=2 徽章构建校验上下文(实名、购票场次、补签通过、同场 badge 映射)。
* <p>
* 须在 {@link #canClaimPerformanceBadge} / {@link #resolveClaimSourceForType2} 之前调用一次。
*
* @param uid 用户 ID
* @param type2BadgesInRequest 本次请求要认领的徽章列表(可含 type=1,内部只取 type=2 的 performanceId)
* @return {@code realNameOk=false} 表示未实名,不可领任何 type=2
*/
public Type2ClaimContext buildType2ClaimContext(String uid, List<AdamCaomeiBadge> type2BadgesInRequest) {
AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
if (real == null || real.getState() == null || real.getState() != 1 || StringUtils.isBlank(real.getIdCard())) {
return Type2ClaimContext.notEligible();
}
List<String> paidPerformanceIds = adamRdmService.getPaidPerformanceIdsByIdCard(real.getIdCard());
Set<String> paidPerformanceSet = paidPerformanceIds == null
? Collections.emptySet()
: new HashSet<>(paidPerformanceIds);
List<AdamCaomeiBadgeApplyRecord> passedApplyRecords = badgeApplyRecordMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 1)
);
Set<String> passedApplyBadgeIds = new HashSet<>();
Set<String> passedApplyPerformanceIds = new HashSet<>();
if (passedApplyRecords != null) {
for (AdamCaomeiBadgeApplyRecord r : passedApplyRecords) {
if (r == null) {
continue;
}
if (StringUtils.isNotBlank(r.getBadgeId())) {
passedApplyBadgeIds.add(r.getBadgeId());
}
if (StringUtils.isNotBlank(r.getPerformanceId())) {
passedApplyPerformanceIds.add(r.getPerformanceId());
}
}
}
Map<String, Set<String>> perfAllBadgeIds = Collections.emptyMap();
if (type2BadgesInRequest != null && !type2BadgesInRequest.isEmpty()) {
Set<String> targetPerfIds = type2BadgesInRequest.stream()
.filter(b -> b != null && b.getType() != null && b.getType() == 2)
.map(AdamCaomeiBadge::getPerformanceId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
if (!targetPerfIds.isEmpty()) {
List<AdamCaomeiBadge> perfBadges = adamCaomeiBadgeMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadge.class)
.eq(AdamCaomeiBadge::getType, 2)
.in(AdamCaomeiBadge::getPerformanceId, targetPerfIds)
);
perfAllBadgeIds = perfBadges == null ? Collections.emptyMap() : perfBadges.stream()
.filter(b -> StringUtils.isNotBlank(b.getPerformanceId()) && StringUtils.isNotBlank(b.getBadgeId()))
.collect(Collectors.groupingBy(AdamCaomeiBadge::getPerformanceId,
Collectors.mapping(AdamCaomeiBadge::getBadgeId, Collectors.toSet())));
}
}
return new Type2ClaimContext(true, paidPerformanceSet, passedApplyBadgeIds, passedApplyPerformanceIds, perfAllBadgeIds);
}
/**
* 判断单枚 type=2 演出纪念徽章是否允许认领。
* <p>
* 满足其一即可:身份证下有该场次购票;该场次补签已通过;同场次任一 type=2 徽章补签已通过。
*
* @param badge 待认领配置(须 type=2)
* @param ctx {@link #buildType2ClaimContext} 的返回值
*/
public boolean canClaimPerformanceBadge(AdamCaomeiBadge badge, Type2ClaimContext ctx) {
if (badge == null || ctx == null || !ctx.isRealNameOk()) {
return false;
}
String perfId = StringUtils.trimToEmpty(badge.getPerformanceId());
boolean hasPaidRecord = StringUtils.isNotBlank(perfId) && ctx.getPaidPerformanceSet().contains(perfId);
if (hasPaidRecord) {
return true;
}
if (StringUtils.isBlank(perfId)) {
return false;
}
if (ctx.getPassedApplyPerformanceIds().contains(perfId)) {
return true;
}
Set<String> badgeIdSet = ctx.getPerfAllBadgeIds().getOrDefault(perfId, Collections.emptySet());
return badgeIdSet.stream().anyMatch(id -> ctx.getPassedApplyBadgeIds().contains(id));
}
/**
* 解析 type=2 认领落库时的 source:有购票记录为 2,否则视为补签通过为 3。
*
* @param badge 当前认领的 type=2 徽章
* @param ctx 已构建的 Type2 上下文
*/
public int resolveClaimSourceForType2(AdamCaomeiBadge badge, Type2ClaimContext ctx) {
String perfId = StringUtils.trimToEmpty(badge.getPerformanceId());
boolean hasPaid = StringUtils.isNotBlank(perfId) && ctx.getPaidPerformanceSet().contains(perfId);
return hasPaid ? 2 : 3;
}
/**
* 计算首页货架单枚徽章的交互状态(未领取时是否显示可领、是否补签待审)。
* <p>
* type=1:已绑护照即可 claimable(用于后上架补领);type=2:购票或补签通过;
* type=3/4:不可自助领取(4 由 home 静默发,不在货架展示)。
*
* @param badge 货架上的徽章配置
* @param claimed 用户是否已在 user 缓存/库中拥有该 badgeId
* @param paidPerformanceIds 实名身份证下的已购场次 ID 列表(home 已查)
* @param applyStatus {@link #loadApplyBadgeStatus} 结果
*/
public ShelfInteractState resolveShelfInteract(AdamCaomeiBadge badge,
boolean claimed,
List<String> paidPerformanceIds,
CaomeiBadgeApplyStatus applyStatus) {
ShelfInteractState state = new ShelfInteractState();
if (claimed) {
state.setClaimable(false);
state.setApplyPending(false);
return state;
}
int type = badge.getType() == null ? 0 : badge.getType();
String perfId = StringUtils.defaultString(badge.getPerformanceId());
if (type == 1) {
state.setClaimable(true);
} else if (type == 2) {
boolean canClaimByPaid = paidPerformanceIds != null && paidPerformanceIds.contains(badge.getPerformanceId());
boolean canClaimByApplyThisBadge = applyStatus.getPassedApplyBadgeIds().contains(badge.getBadgeId());
boolean canClaimByApplyThisPerf = StringUtils.isNotBlank(perfId)
&& applyStatus.getPassedApplyPerformanceIds().contains(perfId);
state.setClaimable(canClaimByPaid || canClaimByApplyThisBadge || canClaimByApplyThisPerf);
boolean pendingThisBadge = applyStatus.getPendingApplyBadgeIds().contains(badge.getBadgeId());
boolean pendingThisPerf = StringUtils.isNotBlank(perfId)
&& applyStatus.getPendingApplyPerformanceIds().contains(perfId);
state.setApplyPending(pendingThisBadge || pendingThisPerf);
} else {
state.setClaimable(false);
}
return state;
}
/** 是否签证页 type=4(用于 home 响应拆分 claimedBadges / visaBadges) */
public static boolean isVisaBadgeType(Integer type) {
return type != null && type == BADGE_TYPE_VISA;
}
/**
* type=2 批量认领时的只读上下文,避免 claim 循环内重复查实名/购票/补签。
*/
@Getter
public static final class Type2ClaimContext {
/** 是否已实名(state=1 且有身份证号) */
private final boolean realNameOk;
/** 身份证下已支付订单关联的场次 ID */
private final Set<String> paidPerformanceSet;
/** 补签审核通过的 badgeId */
private final Set<String> passedApplyBadgeIds;
/** 补签审核通过的 performanceId */
private final Set<String> passedApplyPerformanceIds;
/**
* 场次 → 该场次下所有 type=2 的 badgeId 集合;
* 用于「同场次任一徽章补签通过则本场次均可领」规则。
*/
private final Map<String, Set<String>> perfAllBadgeIds;
private Type2ClaimContext(boolean realNameOk,
Set<String> paidPerformanceSet,
Set<String> passedApplyBadgeIds,
Set<String> passedApplyPerformanceIds,
Map<String, Set<String>> perfAllBadgeIds) {
this.realNameOk = realNameOk;
this.paidPerformanceSet = paidPerformanceSet;
this.passedApplyBadgeIds = passedApplyBadgeIds;
this.passedApplyPerformanceIds = passedApplyPerformanceIds;
this.perfAllBadgeIds = perfAllBadgeIds;
}
/** 未实名时的空上下文,{@link #canClaimPerformanceBadge} 将全部返回 false */
static Type2ClaimContext notEligible() {
return new Type2ClaimContext(false, Collections.emptySet(), Collections.emptySet(),
Collections.emptySet(), Collections.emptyMap());
}
}
/**
* 首页货架项的前端交互标记(对应 {@link com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportBadgeShelfItemVo})。
*/
@Getter
@Setter
public static final class ShelfInteractState {
/** 未领取时是否展示为可点击认领 */
private boolean claimable;
/** 是否展示补签审核中(仅 type=2 有意义) */
private boolean applyPending;
}
}
package com.liquidnet.service.adam.service.caomei;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.util.QueueUtils;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 草莓徽章「发放」统一入口:写入用户徽章 Redis({@code INFO_CAOMEI_BADGE_USER + uid}),
* 并通过 MQ 异步 INSERT {@code adam_caomei_user_badge}。
* <p>
* 调用方:绑护照发 type=1、用户 claim、home 静默发 type=4 签证页。
* <p>
* {@code source} 与库表一致:1-绑定护照,2-购票自动,3-补签通过,4-现场管理员(本服务未使用 4)。
*/
@Slf4j
@Service
public class CaomeiBadgeGrantService {
@Autowired
private AdamRdmService adamRdmService;
@Autowired
private QueueUtils queueUtils;
/**
* 将运营配置的徽章实体转为用户已获列表中的缓存 DTO(尚未写 Redis)。
*
* @param badge 上架徽章配置(含 badgeId、名称、type、performanceId 等)
* @param source 获得途径,见类说明
* @param claimedAt 获得时间,null 时使用当前时间
*/
public AdamCaomeiPassportUserBadgeDto toUserBadgeDto(AdamCaomeiBadge badge, int source, Date claimedAt) {
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(claimedAt != null ? claimedAt : DateUtil.now());
dto.setSource(source);
return dto;
}
/**
* 批量发放:对比用户当前已拥有 badgeId,仅对「尚未拥有」的徽章追加缓存并落库。
* <p>
* 适用于:绑护照一次性发多个 type=1、home 末尾静默发多个 type=4。
*
* @param uid 当前用户 ID
* @param badges 待发放的配置列表(通常来自 published 缓存筛选)
* @param source 获得途径
* @param grantAt 统一获得时间(批量场景 bind/签证 使用同一时间戳),null 则取当前时间
* @return 本次实际写入 Redis 的 DTO;空列表表示全部已拥有、未发生发放
*/
public List<AdamCaomeiPassportUserBadgeDto> grantBadgesIfAbsent(String uid,
List<AdamCaomeiBadge> badges,
int source,
Date grantAt) {
if (StringUtils.isBlank(uid) || CollectionUtils.isEmpty(badges)) {
return new ArrayList<>();
}
Date at = grantAt != null ? grantAt : DateUtil.now();
List<AdamCaomeiPassportUserBadgeDto> cacheList = copyUserBadgeCache(uid);
Set<String> existedBadgeIds = cacheList.stream()
.map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
List<AdamCaomeiPassportUserBadgeDto> appendVos = badges.stream()
.filter(b -> b != null && StringUtils.isNotBlank(b.getBadgeId()))
.filter(b -> !existedBadgeIds.contains(b.getBadgeId()))
.map(b -> toUserBadgeDto(b, source, at))
.collect(Collectors.toList());
if (appendVos.isEmpty()) {
return appendVos;
}
adamRdmService.addUserCaomeiBadgeDtosByUid(uid, cacheList, appendVos);
sendUserBadgeInsertMq(uid, appendVos, source, at);
return appendVos;
}
/**
* 单条发放:用于 POST claim 循环内逐枚写入。
* <p>
* 调用方须已做「未领取」校验;本方法不再查重。
*
* @param uid 用户 ID
* @param badge 单枚上架徽章配置
* @param source 获得途径(type=2 时由 Eligibility 解析为 2 或 3)
* @param mutableCacheList 当前请求内持有的用户徽章列表副本,发放后会 append 并写回 Redis
*/
public void grantOne(String uid,
AdamCaomeiBadge badge,
int source,
List<AdamCaomeiPassportUserBadgeDto> mutableCacheList) {
if (badge == null || StringUtils.isBlank(badge.getBadgeId())) {
return;
}
Date now = new Date();
AdamCaomeiPassportUserBadgeDto dto = toUserBadgeDto(badge, source, now);
adamRdmService.addUserCaomeiBadgeDtoByUid(uid, mutableCacheList, dto);
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_user_badge.add", uid, badge.getBadgeId(), source, now)
);
}
/** 读取用户徽章 Redis,返回可修改副本(miss 时可能为空列表) */
private List<AdamCaomeiPassportUserBadgeDto> copyUserBadgeCache(String uid) {
List<AdamCaomeiPassportUserBadgeDto> cache = adamRdmService.getUserCaomeiBadgesByUid(uid);
if (cache == null) {
return new ArrayList<>();
}
return new ArrayList<>(cache);
}
/** 批量 MQ:仅对 appendVos 中的 badgeId 发送 INSERT,与 Redis 追加范围一致 */
private void sendUserBadgeInsertMq(String uid,
List<AdamCaomeiPassportUserBadgeDto> appendVos,
int source,
Date grantAt) {
LinkedList<Object[]> paramsList = new LinkedList<>();
for (AdamCaomeiPassportUserBadgeDto dto : appendVos) {
if (dto == null || StringUtils.isBlank(dto.getBadgeId())) {
continue;
}
paramsList.add(new Object[]{uid, dto.getBadgeId(), source, grantAt});
}
if (paramsList.isEmpty()) {
return;
}
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_user_badge.add", paramsList)
);
}
}
......@@ -7,7 +7,6 @@ import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyParam;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyRecordUserDto;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyRecordUserVo;
import com.liquidnet.service.adam.dto.vo.AdamRealInfoVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
......@@ -16,6 +15,8 @@ import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamCaomeiBadgeUserService;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeEligibilityService;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeGrantService;
import com.liquidnet.service.adam.util.QueueUtils;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
......@@ -29,12 +30,10 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;
@Slf4j
......@@ -50,6 +49,10 @@ public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserServi
@Autowired
private AdamRdmService adamRdmService;
@Autowired
private CaomeiBadgeGrantService caomeiBadgeGrantService;
@Autowired
private CaomeiBadgeEligibilityService caomeiBadgeEligibilityService;
@Autowired
private QueueUtils queueUtils;
@Override
......@@ -133,56 +136,13 @@ public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserServi
}
}
Set<String> paidPerformanceSet = Collections.emptySet();
Set<String> passedApplyBadgeIds = Collections.emptySet();
Set<String> passedApplyPerformanceIds = Collections.emptySet();
Map<String, Set<String>> perfAllBadgeIds = Collections.emptyMap();
CaomeiBadgeEligibilityService.Type2ClaimContext type2Ctx = null;
if (hasType2) {
AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
if (real == null || real.getState() == null || real.getState() != 1 || StringUtils.isBlank(real.getIdCard())) {
type2Ctx = caomeiBadgeEligibilityService.buildType2ClaimContext(uid, orderedBadges);
if (!type2Ctx.isRealNameOk()) {
log.error("[claimBadges] 认领演出徽章需先实名, uid: {}, badgeIds: {}", uid, requestBadgeIds);
return ResponseDto.failure(ErrorMapping.get("10610"));
}
List<String> paidPerformanceIds = adamRdmService.getPaidPerformanceIdsByIdCard(real.getIdCard());
paidPerformanceSet = paidPerformanceIds == null ? Collections.emptySet() : new HashSet<>(paidPerformanceIds);
List<AdamCaomeiBadgeApplyRecord> passedApplyRecords = badgeApplyRecordMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 1)
);
passedApplyBadgeIds = new HashSet<>();
passedApplyPerformanceIds = new HashSet<>();
if (passedApplyRecords != null) {
for (AdamCaomeiBadgeApplyRecord r : passedApplyRecords) {
if (r == null) {
continue;
}
if (StringUtils.isNotBlank(r.getBadgeId())) {
passedApplyBadgeIds.add(r.getBadgeId());
}
if (StringUtils.isNotBlank(r.getPerformanceId())) {
passedApplyPerformanceIds.add(r.getPerformanceId());
}
}
}
Set<String> targetPerfIds = orderedBadges.stream()
.filter(b -> b.getType() != null && b.getType() == 2)
.map(AdamCaomeiBadge::getPerformanceId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
if (!targetPerfIds.isEmpty()) {
List<AdamCaomeiBadge> perfBadges = adamCaomeiBadgeMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadge.class)
.eq(AdamCaomeiBadge::getType, 2)
.in(AdamCaomeiBadge::getPerformanceId, targetPerfIds)
);
perfAllBadgeIds = perfBadges.stream()
.filter(b -> StringUtils.isNotBlank(b.getPerformanceId()) && StringUtils.isNotBlank(b.getBadgeId()))
.collect(Collectors.groupingBy(AdamCaomeiBadge::getPerformanceId,
Collectors.mapping(AdamCaomeiBadge::getBadgeId, Collectors.toSet())));
}
}
List<String> claimedBadgeIds = new ArrayList<>();
......@@ -190,25 +150,14 @@ public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserServi
int type = badge.getType() == null ? 0 : badge.getType();
int source = 1;
if (type == 2) {
String perfId = StringUtils.trimToEmpty(badge.getPerformanceId());
boolean hasPaidRecord = StringUtils.isNotBlank(perfId) && paidPerformanceSet.contains(perfId);
boolean hasPassedApply = false;
if (StringUtils.isNotBlank(perfId)) {
if (passedApplyPerformanceIds.contains(perfId)) {
hasPassedApply = true;
} else {
Set<String> badgeIdSet = perfAllBadgeIds.getOrDefault(perfId, Collections.emptySet());
hasPassedApply = badgeIdSet.stream().anyMatch(passedApplyBadgeIds::contains);
}
}
if (!hasPaidRecord && !hasPassedApply) {
if (!caomeiBadgeEligibilityService.canClaimPerformanceBadge(badge, type2Ctx)) {
log.error("[claimBadges] 无购票记录且无通过补签,无法认领, uid: {}, badgeId: {}", uid, badge.getBadgeId());
return ResponseDto.failure(ErrorMapping.get("10611"));
}
source = hasPaidRecord ? 2 : 3;
source = caomeiBadgeEligibilityService.resolveClaimSourceForType2(badge, type2Ctx);
}
grantUserBadgeRedisThenMq(uid, badge, source, badgeVos);
caomeiBadgeGrantService.grantOne(uid, badge, source, badgeVos);
claimedBadgeIds.add(badge.getBadgeId());
}
......@@ -216,33 +165,6 @@ public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserServi
return ResponseDto.success(claimedBadgeIds);
}
/**
* Redis 追加用户徽章展示 DTO,再发 MQ 异步执行 sqlmap 中的 INSERT。
*/
private void grantUserBadgeRedisThenMq(String uid, AdamCaomeiBadge badge, int source,
List<AdamCaomeiPassportUserBadgeDto> badgeVos) {
Date now = new Date();
AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
dto.setBadgeId(badge.getBadgeId());
dto.setBadgeName(StringUtils.defaultString(badge.getName()));
dto.setIcon(StringUtils.defaultString(badge.getIcon()));
dto.setShareText(StringUtils.defaultString(badge.getShareText()));
dto.setType(badge.getType());
dto.setClaimedAt(now);
dto.setSource(source);
dto.setSubTitle(badge.getSubTitle());
dto.setPerformanceId(StringUtils.defaultString(badge.getPerformanceId()));
adamRdmService.addUserCaomeiBadgeDtoByUid(uid, badgeVos, dto);
long t = System.currentTimeMillis();
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_user_badge.add", uid, badge.getBadgeId(), source, now)
);
log.debug("[claimBadge] MQ耗时:{}ms, uid: {}, badgeId: {}", System.currentTimeMillis() - t, uid, badge.getBadgeId());
}
@Override
public ResponseDto<List<AdamCaomeiBadgeApplyRecordUserVo>> getApplyRecords(String uid) {
if (StringUtils.isBlank(uid)) {
......
......@@ -6,17 +6,15 @@ import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.vo.*;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamCaomeiPassportUserService;
import com.liquidnet.service.adam.util.QueueUtils;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeApplyStatus;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeEligibilityService;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeGrantService;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -31,14 +29,20 @@ 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
private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
@Autowired
private AdamRdmService adamRdmService;
@Autowired
private QueueUtils queueUtils;
private CaomeiBadgeGrantService caomeiBadgeGrantService;
@Autowired
private CaomeiBadgeEligibilityService caomeiBadgeEligibilityService;
@Override
@Transactional(rollbackFor = Exception.class)
......@@ -95,49 +99,9 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
return ResponseDto.success();
}
List<String> badgeIds = passportTypeBadges.stream()
.map(AdamCaomeiBadge::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
Date grantAt = DateUtil.now();
// 兼容 claimBadge 的写法:把本次发放的护照徽章同时写入用户徽章缓存列表
List<AdamCaomeiPassportUserBadgeDto> cacheBadgeVos = adamRdmService.getUserCaomeiBadgesByUid(uid);
if (cacheBadgeVos == null) {
cacheBadgeVos = new ArrayList<>();
} else {
cacheBadgeVos = new ArrayList<>(cacheBadgeVos);
}
Set<String> existedBadgeIds = cacheBadgeVos.stream()
.map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
List<AdamCaomeiPassportUserBadgeDto> appendVos = passportTypeBadges.stream()
.filter(b -> b != null && StringUtils.isNotBlank(b.getBadgeId()) && !existedBadgeIds.contains(b.getBadgeId()))
.map(b -> {
AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
dto.setBadgeId(b.getBadgeId());
dto.setBadgeName(StringUtils.defaultString(b.getName()));
dto.setIcon(StringUtils.defaultString(b.getIcon()));
dto.setShareText(StringUtils.defaultString(b.getShareText()));
dto.setType(b.getType());
dto.setClaimedAt(grantAt);
dto.setSource(1);
dto.setSubTitle(b.getSubTitle());
return dto;
})
.collect(Collectors.toList());
adamRdmService.addUserCaomeiBadgeDtosByUid(uid, cacheBadgeVos, appendVos);
LinkedList<Object[]> paramsList = new LinkedList<>();
for (String badgeId : badgeIds) {
paramsList.add(new Object[]{uid, badgeId, 1, grantAt});
}
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_user_badge.add", paramsList)
);
List<AdamCaomeiPassportUserBadgeDto> appendVos = caomeiBadgeGrantService.grantBadgesIfAbsent(
uid, passportTypeBadges, 1, grantAt);
// 5. 返回本次绑定场景下的护照类型徽章列表(用于前端弹窗)
List<AdamCaomeiPassportUserClaimedBadgeVo> grantedBadges = appendVos.stream().map(b -> {
......@@ -190,6 +154,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<>();
......@@ -200,29 +165,49 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
if (published == null) {
published = new ArrayList<>();
}
ApplyBadgeStatus applyBadgeStatus = loadApplyBadgeStatus(uid, published);
CaomeiBadgeApplyStatus applyBadgeStatus = caomeiBadgeEligibilityService.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 落库)
List<AdamCaomeiPassportUserBadgeDto> visaGranted = syncVisaPageBadgesIfEligible(
uid, published, paidPerformanceIds, applyBadgeStatus);
if (!visaGranted.isEmpty()) {
rows.addAll(visaGranted);
log.info("[getPassportHome] 本次静默发放签证页, uid: {}, badgeIds: {}", uid,
visaGranted.stream().map(AdamCaomeiPassportUserBadgeDto::getBadgeId).collect(Collectors.toList()));
}
Map<String, String> claimedPerformanceTitleById = buildClaimedPerformanceTitleMap(rows);
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows, claimedPerformanceTitleById);
// 首页货架:排除 type=4
List<AdamCaomeiBadge> shelfPublished = filterShelfPublished(published);
// 已领徽章 + 货架 type=2 的演出 ID 合并后批量解析名称,避免重复循环查库
Map<String, String> performanceTitleById = adamRdmService.getPerformanceTitleMapByIds(
collectPerformanceIdsForTitleLookup(rows, shelfPublished));
List<AdamCaomeiPassportUserClaimedBadgeVo> userBadgeVos = toClaimedBadgeVos(rows, performanceTitleById);
List<AdamCaomeiPassportUserClaimedBadgeVo> visaBadges = userBadgeVos.stream()
.filter(v -> isVisaBadgeType(v.getType()))
.collect(Collectors.toList());
home.setVisaBadges(visaBadges);
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = userBadgeVos.stream()
.filter(v -> !isVisaBadgeType(v.getType()))
.collect(Collectors.toList());
home.setClaimedBadges(claimed);
log.info("[getPassportHome] 用户已认领的徽章数量, uid: {}, 数量: {}", uid, claimed.size());
log.info("[getPassportHome] 用户已认领徽章(不含签证页) {}, 签证页 {}, uid: {}",
claimed.size(), visaBadges.size(), uid);
// 转换为 Map 方便后续匹配货架上的徽章是否已认领
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap = toClaimedBadgeMap(rows);
log.info("[getPassportHome] 系统已上架的徽章数量, uid: {}, 数量: {}", uid, published.size());
// 演出纪念徽章:批量查演出名称,供前端按演出分组展示
Map<String, String> performanceTitleById = buildPerformanceTitleMap(published);
// 8. 组装全部上架徽章列表 (扁平结构,前端按 type / 演出名称 筛选分组展示)
List<AdamCaomeiPassportBadgeShelfItemVo> allBadges = toShelfItems(
published,
shelfPublished,
claimedMap,
paidPerformanceIds,
applyBadgeStatus,
......@@ -258,52 +243,6 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
return card;
}
private ApplyBadgeStatus loadApplyBadgeStatus(String uid, List<AdamCaomeiBadge> published) {
Map<String, String> badgeIdToPerformanceId = new HashMap<>();
if (published != null) {
for (AdamCaomeiBadge b : published) {
if (b != null && StringUtils.isNotBlank(b.getBadgeId())) {
badgeIdToPerformanceId.put(b.getBadgeId(), StringUtils.trimToEmpty(b.getPerformanceId()));
}
}
}
List<AdamCaomeiBadgeApplyRecord> applyRecords = badgeApplyRecordMapper.selectList(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.in(AdamCaomeiBadgeApplyRecord::getAuditStatus, 0, 1)
);
Set<String> passedApplyBadgeIds = new HashSet<>();
Set<String> pendingApplyBadgeIds = new HashSet<>();
Set<String> passedApplyPerformanceIds = new HashSet<>();
Set<String> pendingApplyPerformanceIds = new HashSet<>();
for (AdamCaomeiBadgeApplyRecord r : applyRecords) {
if (r == null || StringUtils.isBlank(r.getBadgeId()) || r.getAuditStatus() == null) {
continue;
}
String perf = StringUtils.trimToEmpty(r.getPerformanceId());
if (StringUtils.isBlank(perf)) {
perf = StringUtils.trimToEmpty(badgeIdToPerformanceId.get(r.getBadgeId()));
}
if (r.getAuditStatus() == 1) {
passedApplyBadgeIds.add(r.getBadgeId());
if (StringUtils.isNotBlank(perf)) {
passedApplyPerformanceIds.add(perf);
}
} else if (r.getAuditStatus() == 0) {
pendingApplyBadgeIds.add(r.getBadgeId());
if (StringUtils.isNotBlank(perf)) {
pendingApplyPerformanceIds.add(perf);
}
}
}
return new ApplyBadgeStatus(
passedApplyBadgeIds,
pendingApplyBadgeIds,
passedApplyPerformanceIds,
pendingApplyPerformanceIds
);
}
private static List<AdamCaomeiPassportUserClaimedBadgeVo> toClaimedBadgeVos(List<AdamCaomeiPassportUserBadgeDto> rows,
Map<String, String> performanceTitleById) {
return rows.stream().map(r -> {
......@@ -314,10 +253,13 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
v.setIcon(StringUtils.defaultString(r.getIcon()));
v.setShareText(StringUtils.defaultString(r.getShareText()));
v.setType(r.getType());
if (r.getType() != null && r.getType() == 2 && StringUtils.isNotBlank(r.getPerformanceId())) {
String title = performanceTitleById != null ? performanceTitleById.get(r.getPerformanceId()) : null;
v.setPerformanceName(StringUtils.isNotBlank(title) ? title : r.getPerformanceId());
String perfId = StringUtils.trimToEmpty(r.getPerformanceId());
if (needsPerformanceFields(r.getType()) && StringUtils.isNotBlank(perfId)) {
v.setPerformanceId(perfId);
String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
v.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
} else {
v.setPerformanceId("");
v.setPerformanceName("");
}
v.setClaimedAt(r.getClaimedAt());
......@@ -326,58 +268,111 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
}).collect(Collectors.toList());
}
private Map<String, String> buildClaimedPerformanceTitleMap(List<AdamCaomeiPassportUserBadgeDto> claimedRows) {
List<String> perfIds = claimedRows.stream()
.filter(r -> r != null && r.getType() != null && r.getType() == 2)
.map(AdamCaomeiPassportUserBadgeDto::getPerformanceId)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (perfIds.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> map = new HashMap<>(perfIds.size() * 2);
for (String perfId : perfIds) {
String title = adamRdmService.getPerformanceTitleById(perfId);
if (StringUtils.isNotBlank(title)) {
map.put(perfId, title);
}
}
return map;
}
private static Map<String, AdamCaomeiPassportUserBadgeDto> toClaimedBadgeMap(List<AdamCaomeiPassportUserBadgeDto> rows) {
return rows.stream()
.filter(r -> StringUtils.isNotBlank(r.getBadgeId()))
.collect(Collectors.toMap(AdamCaomeiPassportUserBadgeDto::getBadgeId, Function.identity(), (a, b) -> a));
}
private Map<String, String> buildPerformanceTitleMap(List<AdamCaomeiBadge> published) {
List<String> perfIds = published.stream()
.filter(b -> b != null && b.getType() != null && b.getType() == 2)
.map(AdamCaomeiBadge::getPerformanceId)
.filter(StringUtils::isNotBlank)
.distinct()
/** 收集首页需展示演出名称的场次 ID(已领 type=2/4 + 货架 type=2) */
private static List<String> collectPerformanceIdsForTitleLookup(List<AdamCaomeiPassportUserBadgeDto> userRows,
List<AdamCaomeiBadge> shelfPublished) {
Set<String> ids = new LinkedHashSet<>();
if (userRows != null) {
for (AdamCaomeiPassportUserBadgeDto r : userRows) {
if (r != null && needsPerformanceFields(r.getType()) && StringUtils.isNotBlank(r.getPerformanceId())) {
ids.add(r.getPerformanceId().trim());
}
}
}
if (shelfPublished != null) {
for (AdamCaomeiBadge b : shelfPublished) {
if (b != null && b.getType() != null && b.getType() == 2 && StringUtils.isNotBlank(b.getPerformanceId())) {
ids.add(b.getPerformanceId().trim());
}
}
}
return new ArrayList<>(ids);
}
/**
* home 末尾:对已上架 type=4、用户在该场次已购票或补签审核通过、且尚未获得的签证页执行静默发放。
* <p>
* 购票:source=2;仅补签通过(无购票记录):source=3,与演出纪念徽章认领一致。
*
* @param applyBadgeStatus {@link CaomeiBadgeEligibilityService#loadApplyBadgeStatus},取已通过补签的场次 ID
* @return 本次实际新增的 user 缓存 DTO(供 home 本地 merge,避免二次读 Redis)
*/
private List<AdamCaomeiPassportUserBadgeDto> syncVisaPageBadgesIfEligible(String uid,
List<AdamCaomeiBadge> published,
List<String> paidPerformanceIds,
CaomeiBadgeApplyStatus applyBadgeStatus) {
if (StringUtils.isBlank(uid) || published == null || published.isEmpty()) {
return Collections.emptyList();
}
Set<String> paidSet = paidPerformanceIds == null
? Collections.emptySet()
: paidPerformanceIds.stream().filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toSet());
Set<String> passedApplyPerfSet = applyBadgeStatus == null
? Collections.emptySet()
: applyBadgeStatus.getPassedApplyPerformanceIds();
if (paidSet.isEmpty() && passedApplyPerfSet.isEmpty()) {
return Collections.emptyList();
}
// 筛选所有 type=4的徽章
List<AdamCaomeiBadge> visaPublished = published.stream()
.filter(b -> b != null && isVisaBadgeType(b.getType()))
.filter(b -> StringUtils.isNotBlank(b.getPerformanceId()))
.collect(Collectors.toList());
if (perfIds.isEmpty()) {
return Collections.emptyMap();
if (visaPublished.isEmpty()) {
return Collections.emptyList();
}
Map<String, String> map = new HashMap<>(perfIds.size() * 2);
for (String perfId : perfIds) {
String title = adamRdmService.getPerformanceTitleById(perfId);
if (StringUtils.isNotBlank(title)) {
map.put(perfId, title);
}
List<AdamCaomeiBadge> grantByPaid = visaPublished.stream()
.filter(b -> paidSet.contains(StringUtils.trimToEmpty(b.getPerformanceId())))
.collect(Collectors.toList());
List<AdamCaomeiBadge> grantByApplyOnly = visaPublished.stream()
.filter(b -> {
String perfId = StringUtils.trimToEmpty(b.getPerformanceId());
return !paidSet.contains(perfId) && passedApplyPerfSet.contains(perfId);
})
.collect(Collectors.toList());
Date grantAt = DateUtil.now();
List<AdamCaomeiPassportUserBadgeDto> granted = new ArrayList<>();
if (!grantByPaid.isEmpty()) {
granted.addAll(caomeiBadgeGrantService.grantBadgesIfAbsent(
uid, grantByPaid, USER_BADGE_SOURCE_PAID_AUTO, grantAt));
}
return map;
if (!grantByApplyOnly.isEmpty()) {
granted.addAll(caomeiBadgeGrantService.grantBadgesIfAbsent(uid, grantByApplyOnly, 3, grantAt));
}
return granted;
}
/** type=2 演出纪念、type=4 签证页需展示关联场次 */
private static boolean needsPerformanceFields(Integer type) {
return type != null && (type == 2 || type == BADGE_TYPE_VISA);
}
private static List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
ApplyBadgeStatus applyBadgeStatus,
Map<String, String> performanceTitleById) {
/** 首页徽章货架/墙:排除签证页配置 */
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 List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
CaomeiBadgeApplyStatus applyBadgeStatus,
Map<String, String> performanceTitleById) {
return published.stream()
.map(b -> toShelfItem(b, claimedMap, paidPerformanceIds, applyBadgeStatus, performanceTitleById))
.collect(Collectors.toList());
......@@ -391,11 +386,11 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
* @param performanceTitleById 演出 ID → 演出名称(仅 type=2 使用)
* @return
*/
private static AdamCaomeiPassportBadgeShelfItemVo toShelfItem(AdamCaomeiBadge b,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
ApplyBadgeStatus applyBadgeStatus,
Map<String, String> performanceTitleById) {
private AdamCaomeiPassportBadgeShelfItemVo toShelfItem(AdamCaomeiBadge b,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
CaomeiBadgeApplyStatus applyBadgeStatus,
Map<String, String> performanceTitleById) {
AdamCaomeiPassportBadgeShelfItemVo v = new AdamCaomeiPassportBadgeShelfItemVo();
v.setBadgeId(b.getBadgeId());
v.setName(StringUtils.defaultString(b.getName()));
......@@ -410,38 +405,15 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
v.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
}
v.setApplyPending(false);
// 判断当前徽章是否已认领
AdamCaomeiPassportUserBadgeDto got = claimedMap.get(b.getBadgeId());
boolean claimed = got != null;
v.setClaimed(claimed);
v.setClaimedAt(got != null ? got.getClaimedAt() : null);
// 针对未认领的徽章,根据类型判断是否可认领 (claimable)
if (!claimed) {
if (type == 1) {
// 护照纪念徽章:只要绑定了护照,就可认领(通常是绑定时漏发或后来新上架的)
v.setClaimable(true);
} else if (type == 2) {
// 演出纪念徽章:有购票记录,或本场次任一补签审核通过,则本场次关联徽章均可认领
boolean canClaimByPaid = paidPerformanceIds != null && paidPerformanceIds.contains(b.getPerformanceId());
boolean canClaimByApplyThisBadge = applyBadgeStatus.getPassedApplyBadgeIds().contains(b.getBadgeId());
boolean canClaimByApplyThisPerf = StringUtils.isNotBlank(perfId)
&& applyBadgeStatus.getPassedApplyPerformanceIds().contains(perfId);
v.setClaimable(canClaimByPaid || canClaimByApplyThisBadge || canClaimByApplyThisPerf);
boolean pendingThisBadge = applyBadgeStatus.getPendingApplyBadgeIds().contains(b.getBadgeId());
boolean pendingThisPerf = StringUtils.isNotBlank(perfId)
&& applyBadgeStatus.getPendingApplyPerformanceIds().contains(perfId);
v.setApplyPending(pendingThisBadge || pendingThisPerf);
} else if (type == 3) {
// 特殊徽章:不可自助领取,需要提示用户
v.setClaimable(false);
}
} else {
// 已认领的徽章,可认领状态置为 false
v.setClaimable(false);
}
CaomeiBadgeEligibilityService.ShelfInteractState interact =
caomeiBadgeEligibilityService.resolveShelfInteract(b, claimed, paidPerformanceIds, applyBadgeStatus);
v.setClaimable(interact.isClaimable());
v.setApplyPending(interact.isApplyPending());
return v;
}
......@@ -457,36 +429,4 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
);
}
private static final class ApplyBadgeStatus {
private final Set<String> passedApplyBadgeIds;
private final Set<String> pendingApplyBadgeIds;
private final Set<String> passedApplyPerformanceIds;
private final Set<String> pendingApplyPerformanceIds;
private ApplyBadgeStatus(Set<String> passedApplyBadgeIds,
Set<String> pendingApplyBadgeIds,
Set<String> passedApplyPerformanceIds,
Set<String> pendingApplyPerformanceIds) {
this.passedApplyBadgeIds = passedApplyBadgeIds;
this.pendingApplyBadgeIds = pendingApplyBadgeIds;
this.passedApplyPerformanceIds = passedApplyPerformanceIds;
this.pendingApplyPerformanceIds = pendingApplyPerformanceIds;
}
Set<String> getPassedApplyBadgeIds() {
return passedApplyBadgeIds;
}
Set<String> getPendingApplyBadgeIds() {
return pendingApplyBadgeIds;
}
Set<String> getPassedApplyPerformanceIds() {
return passedApplyPerformanceIds;
}
Set<String> getPendingApplyPerformanceIds() {
return pendingApplyPerformanceIds;
}
}
}
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