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

Commit 732dfa7d authored by 姜秀龙's avatar 姜秀龙

Merge branch 'refs/heads/dev-caomeihuizhang' into container-test

parents 6ac0c80f 75c196d8
......@@ -76,3 +76,6 @@ ALTER TABLE `adam_caomei_badge` ADD COLUMN `sort` int(11) NOT NULL DEFAULT 0 COM
-- 2026-04-16 新增分享文案字段
ALTER TABLE `adam_caomei_badge` ADD COLUMN `share_text` varchar(255) NOT NULL DEFAULT '' COMMENT '徽章分享文案' AFTER `sort`;
-- 2026-04-23 新增徽章副标题字段
ALTER TABLE `adam_caomei_badge` ADD COLUMN `sub_title` varchar(32) NOT NULL DEFAULT '' COMMENT '徽章副标题(最多20字)' AFTER `name`;
\ No newline at end of file
......@@ -42,6 +42,12 @@ public class AdamRedisConst {
*/
public static final String INFO_CERTIFICATION_JUNK = PREFIX.concat("info:certification_junk:");
/**
* 身份证号 -> 已绑定实名的 uid(用于快速判重:同一身份证只能实名一个账号)
* value: uid(String)
*/
public static final String INFO_REAL_NAME_UID_BY_IDCARD = PREFIX.concat("info:real_name:uid_by_idcard:");
public static final String INFO_MEMBER_JOINUS = PREFIX.concat("info:member:joinus:");
public static final String INFO_MEMBER_SIMPLE = PREFIX.concat("info:member:simple");
public static final String INFO_MEMBER_CATEGORY = PREFIX.concat("info:member:category:");
......
......@@ -20,6 +20,9 @@ public class AdamCaomeiBadgeParam {
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章副标题")
private String subTitle;
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
......
......@@ -20,6 +20,12 @@ public class AdamCaomeiBadgeSearchParam {
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
private Integer displayStatus;
@ApiModelProperty(value = "添加时间起(yyyy-MM-dd)")
private String createdAtBegin;
@ApiModelProperty(value = "添加时间止(yyyy-MM-dd)")
private String createdAtEnd;
@ApiModelProperty(value = "当前页码")
private Integer pageNum = 1;
......
......@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("草莓护照-补签审核详情")
......@@ -39,6 +40,9 @@ public class AdamCaomeiBadgeApplyAuditDetailVo {
@ApiModelProperty(value = "申请附件")
private String proofImageUrl;
@ApiModelProperty(value = "申请附件 URL 列表(解析自原始字段)")
private List<String> proofImageUrls;
@ApiModelProperty(value = "审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
......
......@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("草莓护照-补签审核列表")
......@@ -33,6 +34,9 @@ public class AdamCaomeiBadgeApplyAuditVo {
@ApiModelProperty(value = "申请附件")
private String proofImageUrl;
@ApiModelProperty(value = "申请附件 URL 列表(解析自原始字段)")
private List<String> proofImageUrls;
@ApiModelProperty(value = "审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
......
......@@ -22,6 +22,9 @@ public class AdamCaomeiBadgeVo {
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章副标题")
private String subTitle;
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
......
......@@ -18,6 +18,9 @@ public class AdamCaomeiPassportBadgeShelfItemVo {
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("副标题")
private String subTitle;
@ApiModelProperty("图标")
private String icon;
......
......@@ -18,6 +18,9 @@ public class AdamCaomeiPassportUserClaimedBadgeVo {
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("副标题")
private String subTitle;
@ApiModelProperty("图标")
private String icon;
......@@ -27,6 +30,9 @@ public class AdamCaomeiPassportUserClaimedBadgeVo {
@ApiModelProperty("类型 1护照类型徽章 2演出类型徽章 3特殊徽章")
private Integer type;
@ApiModelProperty("关联演出名称(仅 type=2 有值)")
private String performanceName;
@ApiModelProperty("获得时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date claimedAt;
......
......@@ -18,11 +18,13 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.Objects;
@Api(tags = "草莓护照-徽章管理")
@Controller
@RequestMapping("adam/caomei/badge")
......@@ -30,9 +32,15 @@ public class AdamCaomeiBadgeController extends BaseController {
private final String prefix = "zhengzai/adam/caomei/badge";
private static final int BADGE_NAME_MAX_CHARS = 20;
private static final int BADGE_SUBTITLE_MAX_CHARS = 20;
@Autowired
private IAdamCaomeiBadgeAdminService adamCaomeiBadgeAdminService;
@Value("${liquidnet.client.admin.platformUrl}")
private String platformUrl;
@RequiresPermissions("adam:caomei:badge:list")
@GetMapping()
public String view() {
......@@ -50,7 +58,8 @@ public class AdamCaomeiBadgeController extends BaseController {
@RequiresPermissions("adam:caomei:badge:add")
@GetMapping("add")
public String add() {
public String add(ModelMap mmap) {
mmap.put("platformUrl", platformUrl);
return prefix + "/badge_add";
}
......@@ -59,8 +68,26 @@ public class AdamCaomeiBadgeController extends BaseController {
@PostMapping("add")
@ResponseBody
public AjaxResult addSave(AdamCaomeiBadgeParam param) {
if (param == null) {
return error("参数错误");
}
AjaxResult nameCheck = validateBadgeName(param.getName());
if (nameCheck != null) {
return nameCheck;
}
AjaxResult subTitleCheck = validateBadgeSubTitle(param.getSubTitle());
if (subTitleCheck != null) {
return subTitleCheck;
}
String trimmedName = StringUtils.trimToEmpty(param.getName());
if (adamCaomeiBadgeAdminService.existsOtherBadgeWithSameName(trimmedName, null)) {
return error("徽章名称已存在,请勿重复");
}
AdamCaomeiBadge badge = new AdamCaomeiBadge();
BeanUtils.copyProperties(param, badge);
badge.setName(trimmedName);
badge.setSubTitle(StringUtils.trimToEmpty(param.getSubTitle()));
badge.setShareText(StringUtils.defaultString(badge.getShareText()));
badge.setBadgeId(IDGenerator.nextSnowId());
badge.setDisplayStatus(0); // 默认下架
......@@ -72,9 +99,14 @@ public class AdamCaomeiBadgeController extends BaseController {
// 演出类型校验
if (badge.getType() != null && badge.getType() == 2) {
if (StringUtils.isBlank(badge.getPerformanceId())) {
String pid = StringUtils.trimToEmpty(badge.getPerformanceId());
if (StringUtils.isBlank(pid)) {
return error("演出纪念徽章必须关联演出");
}
badge.setPerformanceId(pid);
if (!adamCaomeiBadgeAdminService.kylinPerformanceExists(pid)) {
return error("无相关演出");
}
} else {
badge.setPerformanceId("");
}
......@@ -115,6 +147,7 @@ public class AdamCaomeiBadgeController extends BaseController {
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, badgeId), false
);
mmap.put("badge", badge);
mmap.put("platformUrl", platformUrl);
return prefix + "/badge_edit";
}
......@@ -123,18 +156,50 @@ public class AdamCaomeiBadgeController extends BaseController {
@PostMapping("edit")
@ResponseBody
public AjaxResult editSave(AdamCaomeiBadgeParam param) {
if (param == null || StringUtils.isBlank(param.getBadgeId())) {
return error("参数错误");
}
AdamCaomeiBadge oldBadge = adamCaomeiBadgeAdminService.getOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, param.getBadgeId()), false
);
if (oldBadge == null || oldBadge.getDisplayStatus() == 1) {
return error("已发布的徽章不允许修改");
}
AjaxResult nameCheck = validateBadgeName(param.getName());
if (nameCheck != null) {
return nameCheck;
}
AjaxResult subTitleCheck = validateBadgeSubTitle(param.getSubTitle());
if (subTitleCheck != null) {
return subTitleCheck;
}
String trimmedName = StringUtils.trimToEmpty(param.getName());
if (adamCaomeiBadgeAdminService.existsOtherBadgeWithSameName(trimmedName, param.getBadgeId())) {
return error("徽章名称已存在,请勿重复");
}
if (!Objects.equals(param.getType(), oldBadge.getType())) {
return error("徽章类型不允许修改");
}
AdamCaomeiBadge badge = new AdamCaomeiBadge();
BeanUtils.copyProperties(param, badge);
badge.setName(trimmedName);
badge.setSubTitle(StringUtils.trimToEmpty(param.getSubTitle()));
badge.setShareText(StringUtils.defaultString(badge.getShareText()));
badge.setMid(oldBadge.getMid());
badge.setUpdatedAt(new java.util.Date());
if (badge.getType() != null && badge.getType() != 2) {
// 徽章类型与「已发布不可改」一致:编辑时不允许变更类型
badge.setType(oldBadge.getType());
if (badge.getType() != null && badge.getType() == 2) {
String pid = StringUtils.trimToEmpty(badge.getPerformanceId());
if (StringUtils.isBlank(pid)) {
return error("演出纪念徽章必须关联演出");
}
badge.setPerformanceId(pid);
if (!adamCaomeiBadgeAdminService.kylinPerformanceExists(pid)) {
return error("无相关演出");
}
} else {
badge.setPerformanceId("");
}
return toAjax(adamCaomeiBadgeAdminService.updateById(badge));
......@@ -157,4 +222,31 @@ public class AdamCaomeiBadgeController extends BaseController {
updateBadge.setUpdatedAt(new java.util.Date());
return toAjax(adamCaomeiBadgeAdminService.updateById(updateBadge));
}
/**
* @return 校验通过返回 null,否则返回错误 AjaxResult
*/
private AjaxResult validateBadgeName(String name) {
String n = StringUtils.trimToEmpty(name);
if (StringUtils.isBlank(n)) {
return error("徽章名称不能为空");
}
int len = n.codePointCount(0, n.length());
if (len > BADGE_NAME_MAX_CHARS) {
return error("徽章名称不能超过" + BADGE_NAME_MAX_CHARS + "个字");
}
return null;
}
private AjaxResult validateBadgeSubTitle(String subTitle) {
String s = StringUtils.trimToEmpty(subTitle);
if (StringUtils.isBlank(s)) {
return null;
}
int len = s.codePointCount(0, s.length());
if (len > BADGE_SUBTITLE_MAX_CHARS) {
return error("徽章副标题不能超过" + BADGE_SUBTITLE_MAX_CHARS + "个字");
}
return null;
}
}
......@@ -2,6 +2,7 @@
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增徽章')" />
<th:block th:include="include :: bootstrap-fileinput-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
......@@ -9,13 +10,26 @@
<div class="form-group">
<label class="col-sm-3 control-label is-required">徽章名称:</label>
<div class="col-sm-8">
<input name="name" class="form-control" type="text" required>
<input name="name" class="form-control" type="text" maxlength="20" required placeholder="最多20个字">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">徽章副标题:</label>
<div class="col-sm-8">
<input name="subTitle" class="form-control" type="text" maxlength="20" placeholder="最多20个字">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">徽章图标:</label>
<div class="col-sm-8">
<input name="icon" class="form-control" type="text" placeholder="输入Emoji或图片URL" required>
<input type="hidden" name="icon" id="badgeIconUrl" value="">
<div id="badgeIconPreviewWrap" class="m-b" style="display:none;">
<img id="badgeIconPreview" src="" alt="预览" style="max-height:96px;border-radius:4px;border:1px solid #eee;"/>
</div>
<div class="file-loading">
<input id="fileinput-badge-icon" type="file" name="file" data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持 jpg、png、gif、webp,单张最大 5M;上传后自动保存为图片地址。</span>
</div>
</div>
<div class="form-group">
......@@ -51,8 +65,10 @@
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
<th:block th:include="include :: bootstrap-fileinput-js" />
<script type="text/javascript" th:inline="javascript">
var prefix = ctx + "adam/caomei/badge";
var platformUrl = /*[[${platformUrl}]]*/ '';
function typeChange(val) {
if (val == 2) {
......@@ -70,10 +86,42 @@
});
function submitHandler() {
if (!$("#badgeIconUrl").val()) {
$.modal.msgWarning("请先上传徽章图标");
return;
}
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-badge-add').serialize());
}
}
$(function () {
$("#fileinput-badge-icon").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "other",
"buckType": 1
},
autoReplace: true,
dropZoneTitle: "点击或拖拽上传徽章图标",
maxFileCount: 1,
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件'
}).on("filebatchselected", function () {
$(this).fileinput("upload");
}).on("fileuploaded", function (event, data) {
if (!data.response || !data.response.data || !data.response.data.ossPath) {
$.modal.alertWarning((data.response && data.response.msg) ? data.response.msg : "图片上传失败");
return;
}
var fullUrl = "https://img.zhengzai.tv/" + data.response.data.ossPath;
$("#badgeIconUrl").val(fullUrl);
$("#badgeIconPreview").attr("src", fullUrl);
$("#badgeIconPreviewWrap").show();
});
});
</script>
</body>
</html>
......@@ -12,6 +12,12 @@
<div class="form-control-static" th:text="*{name}"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">徽章副标题:</label>
<div class="col-sm-8">
<div class="form-control-static" th:text="*{subTitle != null and subTitle != '' ? subTitle : '-'}"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">徽章图标:</label>
<div class="col-sm-8">
......
......@@ -2,6 +2,7 @@
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改徽章')" />
<th:block th:include="include :: bootstrap-fileinput-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
......@@ -10,24 +11,44 @@
<div class="form-group">
<label class="col-sm-3 control-label is-required">徽章名称:</label>
<div class="col-sm-8">
<input name="name" th:field="*{name}" class="form-control" type="text" required>
<input name="name" th:field="*{name}" class="form-control" type="text" maxlength="20" required placeholder="最多20个字">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">徽章副标题:</label>
<div class="col-sm-8">
<input name="subTitle" th:field="*{subTitle}" class="form-control" type="text" maxlength="20" placeholder="最多20个字">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">徽章图标:</label>
<div class="col-sm-8">
<input name="icon" th:field="*{icon}" class="form-control" type="text" placeholder="输入Emoji或图片URL" required>
<input type="hidden" name="icon" id="badgeIconUrl" th:value="*{icon}">
<div id="badgeIconPreviewWrap" class="m-b"
th:style="${badge.icon != null and (#strings.startsWith(badge.icon, 'http') or #strings.startsWith(badge.icon, '/'))} ? '' : 'display:none;'">
<img id="badgeIconPreview" alt="当前图标"
th:src="${badge.icon != null and (#strings.startsWith(badge.icon, 'http') or #strings.startsWith(badge.icon, '/'))} ? ${badge.icon} : ''"
style="max-height:96px;border-radius:4px;border:1px solid #eee;"/>
</div>
<p id="badgeIconLegacyHint" class="help-block"
th:if="${badge.icon != null and !(#strings.startsWith(badge.icon, 'http') or #strings.startsWith(badge.icon, '/'))}">
当前为 Emoji 文本:<strong th:text="${badge.icon}"></strong>,上传图片保存后将替换为图片地址。
</p>
<div class="file-loading">
<input id="fileinput-badge-icon" type="file" name="file" data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持 jpg、png、gif、webp,单张最大 5M;重新上传将覆盖地址。</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">徽章类型:</label>
<div class="col-sm-8">
<select name="type" class="form-control m-b" th:field="*{type}" required onchange="typeChange(this.value)">
<option value="">请选择</option>
<option value="1">护照纪念徽章</option>
<option value="2">演出纪念徽章</option>
<!-- <option value="3">特殊徽章</option> -->
<input type="hidden" name="type" th:value="*{type}"/>
<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>
</select>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 徽章类型保存后不可修改</span>
</div>
</div>
<div class="form-group">
......@@ -52,11 +73,13 @@
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
<th:block th:include="include :: bootstrap-fileinput-js" />
<script type="text/javascript" th:inline="javascript">
var prefix = ctx + "adam/caomei/badge";
var platformUrl = /*[[${platformUrl}]]*/ '';
function typeChange(val) {
if (val == 2) {
if (val == 2 || val == '2') {
$("#ticketTimesDiv").show();
$("#performanceId").prop("required", true);
} else {
......@@ -66,11 +89,46 @@
}
}
$(function () {
typeChange($('input[name="type"]').val());
$("#fileinput-badge-icon").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "other",
"buckType": 1
},
autoReplace: true,
dropZoneTitle: "点击或拖拽上传徽章图标(可选,用于更换)",
maxFileCount: 1,
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件'
}).on("filebatchselected", function () {
$(this).fileinput("upload");
}).on("fileuploaded", function (event, data) {
if (!data.response || !data.response.data || !data.response.data.ossPath) {
$.modal.alertWarning((data.response && data.response.msg) ? data.response.msg : "图片上传失败");
return;
}
var fullUrl = "https://img.zhengzai.tv/" + data.response.data.ossPath;
$("#badgeIconUrl").val(fullUrl);
$("#badgeIconPreview").attr("src", fullUrl);
$("#badgeIconPreviewWrap").show();
$("#badgeIconLegacyHint").hide();
});
});
$("#form-badge-edit").validate({
focusCleanup: true
});
function submitHandler() {
if (!$("#badgeIconUrl").val()) {
$.modal.msgWarning("请先上传徽章图标(若为历史 Emoji 数据,请上传一张图片作为图标)");
return;
}
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-badge-edit').serialize());
}
......
......@@ -28,6 +28,12 @@
<option value="1">已发布</option>
</select>
</li>
<li class="select-time">
<label>添加时间:</label>
<input type="text" class="time-input" id="badgeCreatedBegin" placeholder="开始日期" name="createdAtBegin"/>
<span> - </span>
<input type="text" class="time-input" id="badgeCreatedEnd" placeholder="结束日期" name="createdAtEnd"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
......@@ -86,6 +92,13 @@
field: 'name',
title: '名称'
},
{
field: 'subTitle',
title: '副标题',
formatter: function(value) {
return value ? value : '-';
}
},
{
field: 'type',
title: '类型',
......
......@@ -22,9 +22,9 @@
<tr>
<td>申请附件</td>
<td>
<span th:if="${detail.proofImageUrl == null or detail.proofImageUrl == ''}">-</span>
<a th:if="${detail.proofImageUrl != null and detail.proofImageUrl != ''}" th:href="${detail.proofImageUrl}" target="_blank">
<img th:src="${detail.proofImageUrl}" style="max-height: 90px; border-radius: 4px;"/>
<span th:if="${detail.proofImageUrls == null or detail.proofImageUrls.isEmpty()}">-</span>
<a th:each="u : ${detail.proofImageUrls}" th:href="${u}" target="_blank" style="display:inline-block;margin:0 8px 8px 0;">
<img th:src="${u}" style="max-height: 90px; border-radius: 4px;"/>
</a>
</td>
</tr>
......
......@@ -59,9 +59,19 @@
field: 'proofImageUrl',
title: '申请附件',
align: 'center',
formatter: function(value) {
formatter: function(value, row) {
var urls = row.proofImageUrls;
if (!urls || urls.length === 0) {
if (!value) return '-';
return '<img src="' + value + '" style="height:32px;max-width:60px;cursor:pointer;border-radius:3px;" onclick=\'viewImage(' + JSON.stringify(value) + ')\' />';
urls = [value];
}
var html = [];
for (var i = 0; i < urls.length; i++) {
var u = urls[i];
if (!u) continue;
html.push('<a href="' + u + '" target="_blank" title="查看原图"><img src="' + u + '" style="height:32px;max-width:60px;margin:0 2px;cursor:pointer;border-radius:3px;"/></a>');
}
return html.length ? html.join('') : '-';
}
},
{
......@@ -93,10 +103,6 @@
$.table.init(options);
});
function viewImage(url) {
window.open(url, "_blank");
}
function detail(applyRecordId) {
$.modal.open("补签详情", prefix + "/detail/" + encodeURIComponent(applyRecordId));
}
......
......@@ -32,4 +32,17 @@ public interface IAdamCaomeiBadgeAdminService extends IService<AdamCaomeiBadge>
* 后台新增/编辑/上下架成功后应调用,避免用户端仍读到旧列表。
*/
void delPublishedCaomeiBadges();
/**
* kylin 演出是否存在(performances_id)
*/
boolean kylinPerformanceExists(String performancesId);
/**
* 是否存在与名称相同的其他徽章(trim 后精确匹配)
*
* @param name 徽章名称
* @param excludeBadgeId 编辑时排除当前徽章 ID;新增传 null 或空
*/
boolean existsOtherBadgeWithSameName(String name, String excludeBadgeId);
}
......@@ -15,12 +15,17 @@ import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeClaimUserVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.kylin.entity.KylinPerformances;
import com.liquidnet.service.kylin.mapper.KylinPerformancesMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -33,6 +38,9 @@ public class AdamCaomeiBadgeAdminServiceImpl extends ServiceImpl<AdamCaomeiBadge
@Autowired
private RedisDataSourceUtil redisDataSourceUtil;
@Autowired
private KylinPerformancesMapper kylinPerformancesMapper;
@Override
public void delPublishedCaomeiBadges() {
redisDataSourceUtil.getRedisAdamUtil().del(AdamRedisConst.INFO_CAOMEI_BADGE_PUBLISHED);
......@@ -56,6 +64,31 @@ public class AdamCaomeiBadgeAdminServiceImpl extends ServiceImpl<AdamCaomeiBadge
return ok;
}
@Override
public boolean kylinPerformanceExists(String performancesId) {
String id = StringUtils.trimToEmpty(performancesId);
if (StringUtils.isBlank(id)) {
return false;
}
int c = kylinPerformancesMapper.selectCount(
Wrappers.lambdaQuery(KylinPerformances.class).eq(KylinPerformances::getPerformancesId, id)
);
return c > 0;
}
@Override
public boolean existsOtherBadgeWithSameName(String name, String excludeBadgeId) {
String n = StringUtils.trimToEmpty(name);
if (StringUtils.isBlank(n)) {
return false;
}
LambdaQueryWrapper<AdamCaomeiBadge> w = Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getName, n);
if (StringUtils.isNotBlank(excludeBadgeId)) {
w.ne(AdamCaomeiBadge::getBadgeId, excludeBadgeId.trim());
}
return this.count(w) > 0;
}
@Override
public PageInfo<AdamCaomeiBadgeVo> listWithClaimedCount(AdamCaomeiBadgeSearchParam param) {
LambdaQueryWrapper<AdamCaomeiBadge> queryWrapper = Wrappers.lambdaQuery(AdamCaomeiBadge.class);
......@@ -68,6 +101,22 @@ public class AdamCaomeiBadgeAdminServiceImpl extends ServiceImpl<AdamCaomeiBadge
if (param.getDisplayStatus() != null) {
queryWrapper.eq(AdamCaomeiBadge::getDisplayStatus, param.getDisplayStatus());
}
if (StringUtils.isNotBlank(param.getCreatedAtBegin())) {
try {
LocalDate d = LocalDate.parse(param.getCreatedAtBegin().trim());
queryWrapper.ge(AdamCaomeiBadge::getCreatedAt, Timestamp.valueOf(d.atStartOfDay()));
} catch (DateTimeParseException e) {
log.warn("invalid createdAtBegin: {}", param.getCreatedAtBegin());
}
}
if (StringUtils.isNotBlank(param.getCreatedAtEnd())) {
try {
LocalDate d = LocalDate.parse(param.getCreatedAtEnd().trim());
queryWrapper.le(AdamCaomeiBadge::getCreatedAt, Timestamp.valueOf(d.atTime(23, 59, 59)));
} catch (DateTimeParseException e) {
log.warn("invalid createdAtEnd: {}", param.getCreatedAtEnd());
}
}
queryWrapper.orderByDesc(AdamCaomeiBadge::getSort, AdamCaomeiBadge::getUpdatedAt);
List<AdamCaomeiBadge> badges = this.list(queryWrapper);
......
......@@ -3,6 +3,7 @@ package com.liquidnet.client.admin.zhengzai.adam.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.zhengzai.adam.service.IAdamCaomeiBadgeApplyAuditAdminService;
import com.liquidnet.client.admin.zhengzai.adam.util.CaomeiBadgeApplyProofUrls;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyAuditDetailDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyAuditDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyAuditSearchParam;
......@@ -39,6 +40,7 @@ public class AdamCaomeiBadgeApplyAuditAdminServiceImpl implements IAdamCaomeiBad
AdamCaomeiBadgeApplyAuditVo vo = new AdamCaomeiBadgeApplyAuditVo();
BeanUtils.copyProperties(item, vo);
vo.setIdCard(maskIdCard(item.getIdCard()));
vo.setProofImageUrls(CaomeiBadgeApplyProofUrls.parse(item.getProofImageUrl()));
return vo;
}).collect(Collectors.toList());
PageInfo<AdamCaomeiBadgeApplyAuditVo> voPage = new PageInfo<>(voList);
......@@ -58,6 +60,7 @@ public class AdamCaomeiBadgeApplyAuditAdminServiceImpl implements IAdamCaomeiBad
AdamCaomeiBadgeApplyAuditDetailVo vo = new AdamCaomeiBadgeApplyAuditDetailVo();
BeanUtils.copyProperties(detail, vo);
vo.setIdCard(maskIdCard(detail.getIdCard()));
vo.setProofImageUrls(CaomeiBadgeApplyProofUrls.parse(detail.getProofImageUrl()));
return vo;
}
......
package com.liquidnet.client.admin.zhengzai.adam.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 补签申请附件 URL 解析(支持逗号/分号/换行分隔或 JSON 字符串数组)。
*/
public final class CaomeiBadgeApplyProofUrls {
private CaomeiBadgeApplyProofUrls() {
}
public static List<String> parse(String raw) {
if (StringUtils.isBlank(raw)) {
return Collections.emptyList();
}
String t = raw.trim();
if (t.startsWith("[") && t.endsWith("]")) {
try {
JSONArray arr = JSON.parseArray(t);
if (arr != null && !arr.isEmpty()) {
Set<String> seen = new LinkedHashSet<>();
for (int i = 0; i < arr.size(); i++) {
String u = StringUtils.trimToEmpty(arr.getString(i));
if (StringUtils.isNotBlank(u)) {
seen.add(u);
}
}
return new ArrayList<>(seen);
}
} catch (Exception ignored) {
// fall through to delimiter split
}
}
String[] parts = t.split("[,;\\n]+");
List<String> out = new ArrayList<>();
for (String p : parts) {
String u = StringUtils.trimToEmpty(p);
if (StringUtils.isNotBlank(u)) {
out.add(u);
}
}
return out;
}
}
......@@ -8,9 +8,11 @@ import java.util.Date;
public class AdamCaomeiPassportUserBadgeDto {
private String badgeId;
private String badgeName;
private String subTitle;
private String icon;
private String shareText;
private Integer type;
private String performanceId;
private Date claimedAt;
private Integer source;
}
......@@ -33,6 +33,11 @@ public class AdamCaomeiBadge implements Serializable {
*/
private String name;
/**
* 徽章副标题(最多20字)
*/
private String subTitle;
/**
* 徽章图标 (Emoji字符或图片URL)
*/
......
......@@ -63,9 +63,11 @@
SELECT
ub.badge_id AS badgeId,
IFNULL(b.name, '') AS badgeName,
IFNULL(b.sub_title, '') AS subTitle,
IFNULL(b.icon, '') AS icon,
IFNULL(b.share_text, '') AS shareText,
IFNULL(b.type, 0) AS type,
IFNULL(b.performance_id, '') AS performanceId,
ub.created_at AS claimedAt,
ub.source AS source
FROM adam_caomei_user_badge ub
......
......@@ -300,16 +300,15 @@ public class AdamUserController {
}
String currentUid = CurrentUtil.getCurrentUid();
AdamRealInfoVo realInfoVoByUid = adamRdmService.getRealInfoVoByUidPlain(currentUid);
// 已实名 && 三要素通过
if (null != realInfoVoByUid && realInfoVoByUid.getNode() == 3) {
// 用户输入信息与三要素通过的信息相符,则认证通过
if ((realInfoVoByUid.getName().concat(realInfoVoByUid.getIdCard())).equals(name.concat(idCard))) {
// 已实名:无论二要素(node=2)还是三要素(node=3),都视为已完成实名,直接返回(避免重复实名/跨入口重复认证)
if (null != realInfoVoByUid && realInfoVoByUid.getState() != null && realInfoVoByUid.getState() == 1) {
realInfoVoByUid.setName(SensitizeUtil.chineseName(realInfoVoByUid.getName()));
realInfoVoByUid.setIdCard(SensitizeUtil.custom(realInfoVoByUid.getIdCard(), 3, 2));
return ResponseDto.success(realInfoVoByUid);
} else {
return ResponseDto.failure(ErrorMapping.get("10113"));
}
ResponseDto<AdamRealInfoVo> guard = guardIdCardNotBoundToOtherUid(currentUid, idCard);
if (guard != null) {
return guard;
}
AdamRealInfoVo vo = adamUserService.identityForUpsert(currentUid, name, idCard, (String) CurrentUtil.getTokenClaims().get(CurrentUtil.TOKEN_MOBILE), true);
vo.setName(SensitizeUtil.chineseName(vo.getName()));
......@@ -504,7 +503,8 @@ public class AdamUserController {
return ResponseDto.failure(ErrorMapping.get("10114"));
}
AdamRealInfoVo realInfoVoByUidPlain = adamRdmService.getRealInfoVoByUidPlain(uid);
if (null == realInfoVoByUidPlain || 3 != realInfoVoByUidPlain.getNode()) {
// 已实名:兼容二要素(node=2) 与 三要素(node=3),统一以 state=1 为准
if (null == realInfoVoByUidPlain || realInfoVoByUidPlain.getState() == null || realInfoVoByUidPlain.getState() != 1) {
return ResponseDto.failure(ErrorMapping.get("10115"));
}
AdamUserIdentityInfoVo userIdentityInfoVo = AdamUserIdentityInfoVo.getNew();
......@@ -590,6 +590,10 @@ public class AdamUserController {
return ResponseDto.failure(ErrorMapping.get("10104"));
}
ResponseDto<AdamRealInfoVo> guard = guardIdCardNotBoundToOtherUid(currentUid, idCard);
if (guard != null) {
return guard;
}
AdamRealInfoVo vo = adamUserService.verifyTwoElements(currentUid, name, idCard);
if (null == vo) {
log.error("[identityV2] 二要素认证失败, param: {}", JsonUtils.toJson(param));
......@@ -601,6 +605,21 @@ public class AdamUserController {
}
/**
* 同一个身份证号(state=1)只能绑定一个账号。
* 放在 controller 层:对所有实名入口统一拦截,返回明确错误码给前端。
*/
private ResponseDto<AdamRealInfoVo> guardIdCardNotBoundToOtherUid(String uid, String idCard) {
if (StringUtils.isBlank(uid) || StringUtils.isBlank(idCard)) {
return null;
}
String boundUid = adamRdmService.getRealNameBoundUidByIdCard(idCard);
if (StringUtils.isNotBlank(boundUid) && !uid.equals(boundUid)) {
return ResponseDto.failure(ErrorMapping.get("10614"));
}
return null;
}
/* ---------------------------- Internal Method ---------------------------- */
private static final String PHP_API_SMS_CODE_VALID = "/smsValidation";
......
......@@ -2,6 +2,7 @@ package com.liquidnet.service.adam.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.JsonNode;
import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.common.cache.redis.util.RedisUtil;
......@@ -15,17 +16,20 @@ import com.liquidnet.service.adam.dto.AdamUserInfoDto;
import com.liquidnet.service.adam.dto.vo.*;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamEnters;
import com.liquidnet.service.adam.entity.AdamRealName;
import com.liquidnet.service.adam.entity.AdamUserMember;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.mapper.AdamEntersMapper;
import com.liquidnet.service.adam.mapper.AdamRealNameMapper;
import com.liquidnet.service.adam.mapper.AdamUserMapper;
import com.liquidnet.service.adam.mapper.AdamUserMemberMapper;
import com.liquidnet.service.adam.util.ObjectUtil;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -33,6 +37,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
......@@ -55,6 +60,8 @@ public class AdamRdmService {
@Autowired
AdamUserMemberMapper adamUserMemberMapper;
@Autowired
AdamRealNameMapper adamRealNameMapper;
@Autowired
AdamEntersMapper adamEntersMapper;
@Autowired
AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
......@@ -271,6 +278,50 @@ public class AdamRdmService {
redisUtil.del(AdamRedisConst.INFO_REAL_NAME.concat(uid));
}
/**
* 根据身份证号查询已实名绑定的 uid(优先 Redis,未命中回源 MySQL 并回写 Redis)
* 返回空表示未绑定或不可用。
*/
public String getRealNameBoundUidByIdCard(String idCard) {
if (StringUtils.isEmpty(idCard)) {
return "";
}
String key = AdamRedisConst.INFO_REAL_NAME_UID_BY_IDCARD.concat(idCard);
try {
String uid = (String) redisUtil.get(key);
if (!StringUtils.isEmpty(uid)) {
return uid;
}
} catch (Exception e) {
log.warn("[getRealNameBoundUidByIdCard] redis get failed, key: {}", key, e);
}
// 回源 MySQL:取任意一个 state=1 的 uid(同证件号按规则应唯一)
try {
AdamRealName r = adamRealNameMapper.selectOne(
Wrappers.lambdaQuery(AdamRealName.class)
.eq(AdamRealName::getState, 1)
.eq(AdamRealName::getType, 1)
.eq(AdamRealName::getIdCard, idCard)
.orderByDesc(AdamRealName::getMid)
.last("limit 1")
);
String uid = r != null ? org.springframework.util.StringUtils.trimWhitespace(r.getUid()) : "";
if (!StringUtils.isEmpty(uid)) {
try {
// 30天过期:防止长期脏数据无法自愈;真实解绑/注销可再补 delete
redisUtil.set(key, uid, 60L * 60 * 24 * 30);
} catch (Exception e) {
log.warn("[getRealNameBoundUidByIdCard] redis set failed, key: {}", key, e);
}
}
return uid;
} catch (Exception e) {
log.error("[getRealNameBoundUidByIdCard] mysql fallback failed, idCard: {}", idCard, e);
// 降级:查询失败直接放行(由上层决定是否继续阻断)
return "";
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 认证失败的<ID_TYPE+ID_NO, ID_NAME> */
public boolean setCertificationJunk(int idType, String idNo, String idName) {
......@@ -756,35 +807,83 @@ public class AdamRdmService {
* 读取不到时返回 null。
*/
public String getPerformanceTitleById(String performanceId) {
if (StringUtils.isEmpty(performanceId)) {
return null;
}
try {
if (redisDataSourceUtil == null || redisDataSourceUtil.getRedisKylinUtil() == null) {
KylinPerformanceVo vo = getKylinPerformanceVoById(performanceId);
if (vo != null) {
return vo.getTitle();
}
return null;
} catch (Exception e) {
log.warn("[getPerformanceTitleById] 读取演出缓存失败, performanceId: {}", performanceId, e);
return null;
}
Object cached = redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.PERFORMANCES + performanceId);
if (cached instanceof KylinPerformanceVo) {
return ((KylinPerformanceVo) cached).getTitle();
}
// 兼容:部分环境序列化配置不同,可能反序列化成 Map(如 LinkedHashMap)
if (cached instanceof Map) {
Object title = ((Map<?, ?>) cached).get("title");
return title == null ? null : String.valueOf(title);
/**
* 从演出缓存读取开演/结束时间(yyyyMMddHHmmss),用于草莓徽章补签自动审核。
* 读取不到时返回 null。
*/
public PerformanceTimeRange getPerformanceTimeRangeById(String performanceId) {
try {
KylinPerformanceVo vo = getKylinPerformanceVoById(performanceId);
if (vo == null) {
return null;
}
String start = vo.getTimeStart();
String end = vo.getTimeEnd();
LocalDateTime st = parseKylinTime(start);
LocalDateTime et = parseKylinTime(end);
if (st == null || et == null) {
return null;
}
return new PerformanceTimeRange(st, et);
} catch (Exception e) {
log.warn("[getPerformanceTitleById] 读取演出缓存失败, performanceId: {}", performanceId, e);
log.warn("[getPerformanceTimeRangeById] 读取演出时间失败, performanceId: {}", performanceId, e);
return null;
}
}
/**
* 删除用户徽章redis
* @param uid
* 统一从 kylin redis 缓存读取演出 VO。
* 缓存若因序列化差异读成 Map,则尽量转换成 {@link KylinPerformanceVo} 后返回。
*/
public void delUserCaomeiBadgesByUid(String uid) {
redisUtil.del(AdamRedisConst.INFO_CAOMEI_BADGE_USER.concat(uid));
public KylinPerformanceVo getKylinPerformanceVoById(String performanceId) {
return (KylinPerformanceVo) redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.PERFORMANCES + performanceId);
}
private static final DateTimeFormatter KYLIN_TIME_COMPACT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter KYLIN_TIME_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static LocalDateTime parseKylinTime(String v) {
if (StringUtils.isEmpty(v)) {
return null;
}
String t = v.trim();
// 优先 kylin 常见格式:yyyyMMddHHmmss
try {
if (t.length() >= 14) {
return LocalDateTime.parse(t.substring(0, 14), KYLIN_TIME_COMPACT);
}
} catch (Exception ignored) { }
// 兼容:yyyy-MM-dd HH:mm:ss
try {
if (t.length() >= 19) {
return LocalDateTime.parse(t.substring(0, 19), KYLIN_TIME_DASH);
}
} catch (Exception ignored) { }
return null;
}
@Getter
public static final class PerformanceTimeRange {
private final LocalDateTime start;
private final LocalDateTime end;
public PerformanceTimeRange(LocalDateTime start, LocalDateTime end) {
this.start = start;
this.end = end;
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | List<购买会员黑名单UID> */
......
......@@ -227,17 +227,45 @@ public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserServi
final String applyRecordId = IDGenerator.nextSnowId();
long t = System.currentTimeMillis();
String performanceId = StringUtils.trimToEmpty(badge.getPerformanceId());
boolean autoPass = isPerformanceOngoing(performanceId);
if (autoPass) {
// 与 admin 审核通过保持一致:仅将申请记录标记为已通过,不自动发放徽章
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_badge_apply_record.add_passed",
applyRecordId,
uid,
badgeId,
performanceId,
proofImageUrl)
);
} else {
queueUtils.sendMsgByRedis(
MQConst.AdamQueue.SQL_UCENTER.getKey(),
SqlMapping.get("adam_caomei_badge_apply_record.add",
applyRecordId,
uid,
badgeId,
badge.getPerformanceId(),
performanceId,
proofImageUrl)
);
}
log.info("[claimBadge] MQ耗时:{}ms, uid: {}, badgeId: {}", System.currentTimeMillis() - t, uid, badge.getBadgeId());
return ResponseDto.success(applyRecordId);
}
private boolean isPerformanceOngoing(String performanceId) {
if (StringUtils.isBlank(performanceId)) {
return false;
}
AdamRdmService.PerformanceTimeRange r = adamRdmService.getPerformanceTimeRangeById(performanceId);
if (r == null || r.getStart() == null || r.getEnd() == null) {
return false;
}
java.time.LocalDateTime now = java.time.LocalDateTime.now();
return (now.isEqual(r.getStart()) || now.isAfter(r.getStart()))
&& (now.isEqual(r.getEnd()) || now.isBefore(r.getEnd()));
}
}
......@@ -201,7 +201,8 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
if (rows == null) {
rows = new ArrayList<>();
}
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows);
Map<String, String> claimedPerformanceTitleById = buildClaimedPerformanceTitleMap(rows);
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows, claimedPerformanceTitleById);
home.setClaimedBadges(claimed);
log.info("[getPassportHome] 用户已认领的徽章数量, uid: {}, 数量: {}", uid, claimed.size());
......@@ -278,20 +279,48 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
return new ApplyBadgeStatus(passedApplyBadgeIds, pendingApplyBadgeIds);
}
private static List<AdamCaomeiPassportUserClaimedBadgeVo> toClaimedBadgeVos(List<AdamCaomeiPassportUserBadgeDto> rows) {
private static List<AdamCaomeiPassportUserClaimedBadgeVo> toClaimedBadgeVos(List<AdamCaomeiPassportUserBadgeDto> rows,
Map<String, String> performanceTitleById) {
return rows.stream().map(r -> {
AdamCaomeiPassportUserClaimedBadgeVo v = new AdamCaomeiPassportUserClaimedBadgeVo();
v.setBadgeId(r.getBadgeId());
v.setName(StringUtils.defaultString(r.getBadgeName()));
v.setSubTitle(StringUtils.defaultString(r.getSubTitle()));
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());
} else {
v.setPerformanceName("");
}
v.setClaimedAt(r.getClaimedAt());
v.setSource(r.getSource());
return v;
}).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()))
......@@ -348,6 +377,7 @@ public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUse
AdamCaomeiPassportBadgeShelfItemVo v = new AdamCaomeiPassportBadgeShelfItemVo();
v.setBadgeId(b.getBadgeId());
v.setName(StringUtils.defaultString(b.getName()));
v.setSubTitle(StringUtils.defaultString(b.getSubTitle()));
v.setIcon(StringUtils.defaultString(b.getIcon()));
v.setShareText(StringUtils.defaultString(b.getShareText()));
v.setType(b.getType());
......
......@@ -101,6 +101,7 @@
10611=\u60A8\u6682\u65E0\u8D2D\u7968\u8BB0\u5F55\uFF0C\u8BF7\u4E0A\u4F20\u8BA2\u5355\u622A\u56FE
10612=\u7279\u6B8A\u5FBD\u7AE0\u4E0D\u53EF\u81EA\u52A9\u9886\u53D6
10613=\u672A\u77E5\u7684\u5FBD\u7AE0\u7C7B\u578B
10614=\u8BE5\u5B9E\u540D\u4FE1\u606F\u5DF2\u7ED1\u5B9A\u5176\u4ED6\u8D26\u53F7
......
......@@ -88,6 +88,9 @@ adam_caomei_user_badge.add=INSERT INTO adam_caomei_user_badge (user_id, badge_id
# \u7528\u6237\u7AEF applyBadge\uFF1A\u4E0E\u300C\u5148\u5199 Redis \u8865\u7B7E\u5217\u8868\u3001\u518D MQ \u5F02\u6B65\u843D\u5E93\u300D\u914D\u5957\uFF1B\u53C2\u6570\u987A\u5E8F apply_record_id, user_id, badge_id, performance_id, proof_image_url\uFF1Baudit_status=0\u3001reject_reason \u7A7A\u4E32\u3001\u65F6\u95F4\u7531 now() \u5199\u5165
adam_caomei_badge_apply_record.add=INSERT INTO adam_caomei_badge_apply_record (apply_record_id, user_id, badge_id, performance_id, proof_image_url, audit_status, reject_reason, created_at, updated_at) VALUES (?,?,?,?,?,0,'',now(),now())
# 补签自动通过:直接写入 audit_status=1
adam_caomei_badge_apply_record.add_passed=INSERT INTO adam_caomei_badge_apply_record (apply_record_id, user_id, badge_id, performance_id, proof_image_url, audit_status, reject_reason, created_at, updated_at) VALUES (?,?,?,?,?,1,'',now(),now())
# ----------------------------------------------------
candy_user_coupon.close=UPDATE candy_user_coupon SET state=2,updated_at=sysdate(),operator='close' WHERE uid=? AND state=1
goblin_user_coupon.close=UPDATE goblin_user_coupon SET state=2,updated_at=sysdate(),operator='close' WHERE uid=? AND state=1
......
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