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

Commit 9d2a232a authored by wangyifan's avatar wangyifan

Merge branch 'dev-caomeihuizhang' into container-test

parents 4faa4246 d1c3fb46
-- ----------------------------
-- 1. 护照实体表 (预先生成,供用户扫码绑定)
-- ----------------------------
CREATE TABLE `adam_caomei_passport` (
`mid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`passport_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '护照编号 (全局唯一,用于生成二维码)',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '绑定状态: 0-未绑定, 1-已绑定, 2-已作废',
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '绑定的用户ID',
`bound_at` datetime DEFAULT NULL COMMENT '首次绑定时间',
`unbound_at` datetime DEFAULT NULL COMMENT '最近一次解绑时间 (后台操作解绑时记录)',
`batch_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '生成/印刷批次号 (便于后期溯源管理)',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '后台运营备注',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_passport_no` (`passport_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='草莓护照-实体编号表';
-- ----------------------------
-- 2. 徽章配置表 (运营后台配置,已移除 claimed_count,后台列表实时 COUNT 统计)
-- ----------------------------
CREATE TABLE `adam_caomei_badge` (
`mid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`badge_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '徽章ID',
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '徽章名称',
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '徽章图标 (Emoji字符或图片URL)',
`type` tinyint(4) NOT NULL COMMENT '徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章',
`performance_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '关联演出ID (仅演出纪念徽章必填,其他类型为空)',
`display_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '上架状态: 0-下架(默认), 1-已发布',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`mid`),
KEY `idx_badge_id` (`badge_id`),
KEY `idx_type_status` (`type`,`display_status`),
KEY `idx_performance_id` (`performance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='草莓护照-徽章配置表';
-- ----------------------------
-- 3. 用户徽章获得记录表 (支撑用户徽章墙与后台领取人数统计)
-- ----------------------------
CREATE TABLE `adam_caomei_user_badge` (
`mid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户id',
`badge_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '徽章ID',
`source` tinyint(4) NOT NULL COMMENT '获取途径: 1-绑定护照自动发放, 2-购票自动发放, 3-补签审核通过, 4-现场管理员手动发放',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '获得时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_user_badge` (`user_id`,`badge_id`) COMMENT '联合唯一索引: 同一用户同一徽章只能获得一次',
KEY `idx_badge_id` (`badge_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='草莓护照-用户徽章获得记录表';
-- ----------------------------
-- 4. 徽章补签申请记录表 (支撑补签审核流程)
-- ----------------------------
CREATE TABLE `adam_caomei_badge_apply_record` (
`mid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`apply_record_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '补签id',
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户id',
`badge_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '申请补签的徽章ID',
`performance_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '关联的演出ID (冗余字段,便于后台筛选)',
`proof_image_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '订单截图/凭证图片URL',
`audit_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '审核状态: 0-待审核, 1-已通过, 2-已驳回',
`reject_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '驳回理由 (驳回时必填,用户端可见)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '审核/更新时间',
PRIMARY KEY (`mid`),
KEY `idx_record_id`(`apply_record_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_audit_status` (`audit_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='草莓护照-徽章补签申请记录表';
-- 2026-04-13 新增排序字段
ALTER TABLE `adam_caomei_badge` ADD COLUMN `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序值,数值越大越靠前' AFTER `display_status`;
-- 2026-04-16 新增分享文案字段
ALTER TABLE `adam_caomei_badge` ADD COLUMN `share_text` varchar(255) NOT NULL DEFAULT '' COMMENT '徽章分享文案' AFTER `sort`;
\ No newline at end of file
......@@ -20,6 +20,8 @@ public class AdamRedisConst {
public static final String INFO_THIRD_PARTY = PREFIX.concat("info:third_party:");
public static final String INFO_ENTERS = PREFIX.concat("info:enters:");
public static final String INFO_ADDRESSES = PREFIX.concat("info:addresses:");
public static final String INFO_CAOMEI_BADGE_PUBLISHED = PREFIX.concat("info:caomei:badge:published");
public static final String INFO_CAOMEI_BADGE_USER = PREFIX.concat("info:caomei:badge:user:");
/**
* {adam:info:biz:{uid},List<com.liquidnet.service.adam.dto.vo.AdamUserBizAcctVo>}
*/
......
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("草莓护照-补签审核搜索参数")
public class AdamCaomeiBadgeApplyAuditSearchParam {
@ApiModelProperty(value = "用户名/姓名")
private String userName;
@ApiModelProperty(value = "审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("草莓徽章-补签申请参数")
public class AdamCaomeiBadgeApplyParam {
@NotBlank(message = "徽章ID不能为空")
@ApiModelProperty(value = "徽章ID", required = true)
private String badgeId;
@NotBlank(message = "凭证图片不能为空")
@ApiModelProperty(value = "订单截图/凭证图片URL", required = true)
private String proofImageUrl;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("草莓徽章-认领参数")
public class AdamCaomeiBadgeClaimParam {
@NotBlank(message = "徽章ID不能为空")
@ApiModelProperty(value = "徽章ID", required = true)
private String badgeId;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("草莓护照-徽章领取用户搜索参数")
public class AdamCaomeiBadgeClaimUserSearchParam {
@ApiModelProperty(value = "徽章ID")
private String badgeId;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 草莓护照-徽章管理参数
*/
@Data
@ApiModel("草莓护照-徽章管理参数")
public class AdamCaomeiBadgeParam {
@ApiModelProperty(value = "主键ID")
private Long mid;
@ApiModelProperty(value = "徽章ID")
private String badgeId;
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
private Integer type;
@ApiModelProperty(value = "关联演出ID (仅演出纪念徽章必填,其他类型为空)")
private String performanceId;
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
private Integer displayStatus;
@ApiModelProperty(value = "排序值,数值越大越靠前")
private Integer sort;
@ApiModelProperty(value = "徽章分享文案")
private String shareText;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 草莓护照-徽章搜索参数
*/
@Data
@ApiModel("草莓护照-徽章搜索参数")
public class AdamCaomeiBadgeSearchParam {
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
private Integer type;
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
private Integer displayStatus;
@ApiModelProperty(value = "当前页码")
private Integer pageNum = 1;
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 10;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("草莓护照-编号参数")
public class AdamCaomeiPassportNoParam {
@NotBlank(message = "护照编码不能为空")
@ApiModelProperty(value = "护照实体编号(扫码或手输)", required = true)
private String passportNo;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("草莓护照-护照管理搜索参数")
public class AdamCaomeiPassportSearchParam {
@ApiModelProperty(value = "护照编号")
private String passportNo;
@ApiModelProperty(value = "关联用户名(昵称/真实姓名模糊)")
private String userName;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "证件号")
private String idCard;
@ApiModelProperty(value = "绑定状态:0-未绑定 1-已绑定 2-已作废,空为全部")
private Integer bindStatus;
}
package com.liquidnet.service.adam.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("二要素认证参数")
public class AdamIdentityParam {
@ApiModelProperty(value = "姓名(手动填写时传)")
private String name;
@ApiModelProperty(value = "证件号(手动填写时传)")
private String idCard;
@ApiModelProperty(value = "入场人ID(选择观演人时传)")
private String entersId;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-补签审核详情")
public class AdamCaomeiBadgeApplyAuditDetailVo {
@ApiModelProperty(value = "申请ID")
private String applyRecordId;
@ApiModelProperty(value = "申请账号")
private String userId;
@ApiModelProperty(value = "用户名")
private String nickname;
@ApiModelProperty(value = "姓名")
private String realName;
@ApiModelProperty(value = "证件号(脱敏)")
private String idCard;
@ApiModelProperty(value = "申请徽章ID")
private String badgeId;
@ApiModelProperty(value = "申请徽章")
private String badgeName;
@ApiModelProperty(value = "关联演出ID")
private String performanceId;
@ApiModelProperty(value = "关联演出")
private String performanceName;
@ApiModelProperty(value = "申请附件")
private String proofImageUrl;
@ApiModelProperty(value = "审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
@ApiModelProperty(value = "驳回理由")
private String rejectReason;
@ApiModelProperty(value = "申请时间")
private Date createdAt;
@ApiModelProperty(value = "审核时间")
private Date updatedAt;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-补签审核列表")
public class AdamCaomeiBadgeApplyAuditVo {
@ApiModelProperty(value = "申请ID")
private String applyRecordId;
@ApiModelProperty(value = "申请账号")
private String userId;
@ApiModelProperty(value = "用户名")
private String nickname;
@ApiModelProperty(value = "姓名")
private String realName;
@ApiModelProperty(value = "证件号(脱敏)")
private String idCard;
@ApiModelProperty(value = "申请徽章")
private String badgeName;
@ApiModelProperty(value = "关联演出")
private String performanceName;
@ApiModelProperty(value = "申请附件")
private String proofImageUrl;
@ApiModelProperty(value = "审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
@ApiModelProperty(value = "申请时间")
private Date createdAt;
}
package com.liquidnet.service.adam.dto.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.liquidnet.commons.lang.util.DateUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓徽章-补签记录(用户端)")
public class AdamCaomeiBadgeApplyRecordUserVo {
@ApiModelProperty("补签申请ID")
private String applyRecordId;
@ApiModelProperty("申请徽章名称")
private String badgeName;
@ApiModelProperty("申请时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date applyTime;
@ApiModelProperty("审核状态:0-待审核 1-已通过 2-已驳回")
private Integer auditStatus;
@ApiModelProperty("驳回理由")
private String rejectReason;
@ApiModelProperty("是否可重新上传(仅驳回状态为 true)")
private Boolean canReupload;
@ApiModelProperty("凭证图片")
private String proofImageUrl;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-徽章领取用户")
public class AdamCaomeiBadgeClaimUserVo {
@ApiModelProperty(value = "用户昵称")
private String nickname;
@ApiModelProperty(value = "手机号(脱敏)")
private String mobile;
@ApiModelProperty(value = "领取时间")
private Date claimedAt;
@ApiModelProperty(value = "获取途径: 1-绑定护照自动发放, 2-购票自动发放, 3-补签审核通过, 4-现场管理员手动发放")
private Integer source;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* 草莓护照-徽章视图对象
*/
@Data
@ApiModel("草莓护照-徽章视图对象")
public class AdamCaomeiBadgeVo {
@ApiModelProperty(value = "主键ID")
private Long mid;
@ApiModelProperty(value = "徽章ID")
private String badgeId;
@ApiModelProperty(value = "徽章名称")
private String name;
@ApiModelProperty(value = "徽章图标 (Emoji字符或图片URL)")
private String icon;
@ApiModelProperty(value = "徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章")
private Integer type;
@ApiModelProperty(value = "关联演出ID")
private String performanceId;
@ApiModelProperty(value = "上架状态: 0-下架(默认), 1-已发布")
private Integer displayStatus;
@ApiModelProperty(value = "排序值,数值越大越靠前")
private Integer sort;
@ApiModelProperty(value = "徽章分享文案")
private String shareText;
@ApiModelProperty(value = "添加时间")
private Date createdAt;
@ApiModelProperty(value = "更新时间")
private Date updatedAt;
@ApiModelProperty(value = "领取人数")
private Integer claimedCount;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-用户已领徽章项")
public class AdamCaomeiPassportBadgeItemVo {
@ApiModelProperty(value = "徽章ID")
private String badgeId;
@ApiModelProperty(value = "徽章名称")
private String badgeName;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "领取时间")
private Date claimedAt;
@ApiModelProperty(value = "获取途径 source")
private Integer source;
}
package com.liquidnet.service.adam.dto.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.liquidnet.commons.lang.util.DateUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-货架徽章项(含认领状态与交互提示)")
public class AdamCaomeiPassportBadgeShelfItemVo {
@ApiModelProperty("徽章ID")
private String badgeId;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("图标")
private String icon;
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("类型 1护照 2演出 3特殊")
private Integer type;
@ApiModelProperty("关联演出ID(演出纪念徽章)")
private String performanceId;
@ApiModelProperty("是否已认领")
private boolean claimed;
@ApiModelProperty("认领时间(未认领为空)")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date claimedAt;
@ApiModelProperty("是否可认领(护照徽章未发放完全,或演出徽章有票未领)")
private boolean claimable;
@ApiModelProperty("是否存在补签待审核(仅演出徽章生效)")
private boolean applyPending;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("草莓护照-护照详情")
public class AdamCaomeiPassportDetailVo {
@ApiModelProperty(value = "护照主键")
private Long mid;
@ApiModelProperty(value = "护照编号")
private String passportNo;
@ApiModelProperty(value = "绑定状态")
private Integer status;
@ApiModelProperty(value = "绑定用户uid")
private String userId;
@ApiModelProperty(value = "首次绑定时间")
private Date boundAt;
@ApiModelProperty(value = "批次号")
private String batchNo;
@ApiModelProperty(value = "用户昵称")
private String nickname;
@ApiModelProperty(value = "手机号(脱敏)")
private String mobile;
@ApiModelProperty(value = "真实姓名")
private String realName;
@ApiModelProperty(value = "证件号(脱敏)")
private String idCard;
@ApiModelProperty(value = "已领取徽章")
private List<AdamCaomeiPassportBadgeItemVo> badges;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("草莓护照-C端首页聚合")
public class AdamCaomeiPassportHomeVo {
@ApiModelProperty("个人信息卡片")
private AdamCaomeiPassportUserCardVo userCard;
@ApiModelProperty("已认领徽章(全部获得记录,用于网格墙)")
private List<AdamCaomeiPassportUserClaimedBadgeVo> claimedBadges;
@ApiModelProperty("全部上架徽章(扁平列表,前端按类型分组展示)")
private List<AdamCaomeiPassportBadgeShelfItemVo> allBadges;
}
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("草莓护照-护照管理列表")
public class AdamCaomeiPassportListVo {
@ApiModelProperty(value = "主键")
private Long mid;
@ApiModelProperty(value = "护照ID/编号")
private String passportNo;
@ApiModelProperty(value = "关联用户昵称")
private String nickname;
@ApiModelProperty(value = "真实姓名")
private String realName;
@ApiModelProperty(value = "手机号(脱敏)")
private String mobile;
@ApiModelProperty(value = "证件号(脱敏)")
private String idCard;
@ApiModelProperty(value = "绑定状态 0未绑定 1已绑定 2已作废")
private Integer bindStatus;
@ApiModelProperty(value = "已领取徽章数")
private Integer claimedBadgeCount;
}
package com.liquidnet.service.adam.dto.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.liquidnet.commons.lang.util.DateUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-首页个人信息卡片")
public class AdamCaomeiPassportUserCardVo {
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("是否已实名认证")
private boolean realNameVerified;
@ApiModelProperty("护照编号(未绑定时为空)")
private String passportNo;
@ApiModelProperty("护照获得时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date passportClaimedAt;
@ApiModelProperty("是否已绑定实体护照")
private boolean passportBound;
}
package com.liquidnet.service.adam.dto.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.liquidnet.commons.lang.util.DateUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("草莓护照-已认领徽章(墙)")
public class AdamCaomeiPassportUserClaimedBadgeVo {
@ApiModelProperty("徽章ID")
private String badgeId;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("图标")
private String icon;
@ApiModelProperty("分享文案")
private String shareText;
@ApiModelProperty("类型 1护照类型徽章 2演出类型徽章 3特殊徽章")
private Integer type;
@ApiModelProperty("获得时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateUtil.DATE_FULL_STR)
private Date claimedAt;
@ApiModelProperty("获取途径: 1-绑定护照自动发放, 2-购票自动发放, 3-补签审核通过, 4-现场管理员手动发放")
private Integer source;
}
package com.liquidnet.service.adam.service;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyRecordUserVo;
import com.liquidnet.service.base.ResponseDto;
import java.util.List;
/**
* 草莓徽章(用户端)
*/
public interface IAdamCaomeiBadgeUserService {
/**
* 认领徽章
*/
ResponseDto<String> claimBadge(String badgeId, String uid);
/**
* 补签申请记录列表(用户端)
*/
ResponseDto<List<AdamCaomeiBadgeApplyRecordUserVo>> getApplyRecords(String uid);
/**
* 发起补签申请(驳回后可再次提交)
*/
ResponseDto<String> applyBadge(AdamCaomeiBadgeApplyParam param, String uid);
}
package com.liquidnet.service.adam.service;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportHomeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportUserClaimedBadgeVo;
import com.liquidnet.service.base.ResponseDto;
import java.util.List;
/**
* 草莓护照(用户端)
*/
public interface IAdamCaomeiPassportUserService {
/**
* 绑定实体护照:校验编号有效性、是否可绑;通过则写入并发放已上架护照纪念徽章(type=1)
*/
ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bindPassport(String passportNo);
/**
* 护照首页:个人信息、实名状态、已认领墙、按类型分组的全部上架徽章
*/
ResponseDto<AdamCaomeiPassportHomeVo> getPassportHome();
}
......@@ -72,4 +72,13 @@ public interface IAdamUserService {
AdamRealInfoVo identityForUpsert(String uid, String name, String idCard, String mobile, boolean updateFlg);
AdamRealInfoVo identityForUpdate(String uid, String mobile, int idType, int node, String idCard, String idName);
/**
* 二要素认证
* @param uid
* @param name
* @param idCard
* @return
*/
AdamRealInfoVo verifyTwoElements(String uid, String name, String idCard);
}
......@@ -126,6 +126,18 @@ public class BaseController
return rspData;
}
/**
* 响应分页数据(用于查询结果已转为 VO 列表,但需保留 PageHelper 总条数的场景)
*/
protected TableDataInfo getDataTable(PageInfo<?> pageInfo)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(0);
rspData.setRows(pageInfo.getList());
rspData.setTotal(pageInfo.getTotal());
return rspData;
}
/**
* 响应返回结果
*
......
package com.liquidnet.client.admin.web.controller.zhengzai.adam;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.common.annotation.Log;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.client.admin.zhengzai.adam.service.IAdamCaomeiBadgeApplyAuditAdminService;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyAuditSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyAuditDetailVo;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Api(tags = "草莓护照-补签审核")
@Controller
@RequestMapping("adam/caomei/badgeApplyAudit")
public class AdamCaomeiBadgeApplyAuditController extends BaseController {
private final String prefix = "zhengzai/adam/caomei/badgeApplyAudit";
@Autowired
private IAdamCaomeiBadgeApplyAuditAdminService badgeApplyAuditAdminService;
@RequiresPermissions("adam:caomei:badgeApplyAudit:list")
@GetMapping()
public String view() {
return prefix + "/list";
}
@RequiresPermissions("adam:caomei:badgeApplyAudit:list")
@PostMapping("list")
@ResponseBody
public TableDataInfo list(AdamCaomeiBadgeApplyAuditSearchParam param) {
startPage();
PageInfo<?> pageInfo = badgeApplyAuditAdminService.listApplyAudits(param);
return getDataTable(pageInfo);
}
@RequiresPermissions("adam:caomei:badgeApplyAudit:list")
@GetMapping("detail/{applyRecordId}")
public String detail(@PathVariable("applyRecordId") String applyRecordId, ModelMap mmap) {
AdamCaomeiBadgeApplyAuditDetailVo detail = badgeApplyAuditAdminService.getApplyAuditDetail(applyRecordId);
mmap.put("detail", detail);
return prefix + "/detail";
}
@RequiresPermissions("adam:caomei:badgeApplyAudit:edit")
@Log(title = "草莓护照-补签审核:通过", businessType = BusinessType.UPDATE)
@PostMapping("pass")
@ResponseBody
public AjaxResult pass(@RequestParam("applyRecordId") String applyRecordId) {
boolean ok = badgeApplyAuditAdminService.passApplyAudit(applyRecordId);
if (!ok) {
return error("审核通过失败:申请不存在或当前状态不可操作");
}
return success();
}
@RequiresPermissions("adam:caomei:badgeApplyAudit:edit")
@Log(title = "草莓护照-补签审核:驳回", businessType = BusinessType.UPDATE)
@PostMapping("reject")
@ResponseBody
public AjaxResult reject(@RequestParam("applyRecordId") String applyRecordId,
@RequestParam("rejectReason") String rejectReason) {
if (StringUtils.isBlank(rejectReason)) {
return error("驳回理由不能为空");
}
boolean ok = badgeApplyAuditAdminService.rejectApplyAudit(applyRecordId, rejectReason);
if (!ok) {
return error("驳回失败:申请不存在或当前状态不可操作");
}
return success();
}
}
package com.liquidnet.client.admin.web.controller.zhengzai.adam;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.client.admin.common.annotation.Log;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.client.admin.zhengzai.adam.service.IAdamCaomeiBadgeAdminService;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeParam;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeClaimUserSearchParam;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeSearchParam;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import io.swagger.annotations.Api;
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.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
@Api(tags = "草莓护照-徽章管理")
@Controller
@RequestMapping("adam/caomei/badge")
public class AdamCaomeiBadgeController extends BaseController {
private final String prefix = "zhengzai/adam/caomei/badge";
@Autowired
private IAdamCaomeiBadgeAdminService adamCaomeiBadgeAdminService;
@RequiresPermissions("adam:caomei:badge:list")
@GetMapping()
public String view() {
return prefix + "/badge_list";
}
@RequiresPermissions("adam:caomei:badge:list")
@PostMapping("list")
@ResponseBody
public TableDataInfo list(AdamCaomeiBadgeSearchParam param) {
startPage();
PageInfo<?> pageInfo = adamCaomeiBadgeAdminService.listWithClaimedCount(param);
return getDataTable(pageInfo);
}
@RequiresPermissions("adam:caomei:badge:add")
@GetMapping("add")
public String add() {
return prefix + "/badge_add";
}
@RequiresPermissions("adam:caomei:badge:add")
@Log(title = "草莓护照-徽章管理:新增", businessType = BusinessType.INSERT)
@PostMapping("add")
@ResponseBody
public AjaxResult addSave(AdamCaomeiBadgeParam param) {
AdamCaomeiBadge badge = new AdamCaomeiBadge();
BeanUtils.copyProperties(param, badge);
badge.setShareText(StringUtils.defaultString(badge.getShareText()));
badge.setBadgeId(IDGenerator.nextSnowId());
badge.setDisplayStatus(0); // 默认下架
if (badge.getSort() == null) {
badge.setSort(0);
}
badge.setCreatedAt(new Date());
badge.setUpdatedAt(new Date());
// 演出类型校验
if (badge.getType() != null && badge.getType() == 2) {
if (StringUtils.isBlank(badge.getPerformanceId())) {
return error("演出纪念徽章必须关联演出");
}
} else {
badge.setPerformanceId("");
}
return toAjax(adamCaomeiBadgeAdminService.save(badge));
}
@RequiresPermissions("adam:caomei:badge:list")
@GetMapping("detail/{badgeId}")
public String detail(@PathVariable("badgeId") String badgeId, ModelMap mmap) {
AdamCaomeiBadge badge = adamCaomeiBadgeAdminService.getOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, badgeId), false
);
mmap.put("badge", badge);
return prefix + "/badge_detail";
}
@RequiresPermissions("adam:caomei:badge:list")
@GetMapping("claimUsers/{badgeId}")
public String claimUsers(@PathVariable("badgeId") String badgeId, ModelMap mmap) {
mmap.put("badgeId", badgeId);
return prefix + "/badge_claim_users";
}
@RequiresPermissions("adam:caomei:badge:list")
@PostMapping("claimUsers/list")
@ResponseBody
public TableDataInfo claimUsersList(AdamCaomeiBadgeClaimUserSearchParam param) {
startPage();
PageInfo<?> pageInfo = adamCaomeiBadgeAdminService.listClaimUsers(param);
return getDataTable(pageInfo);
}
@RequiresPermissions("adam:caomei:badge:edit")
@GetMapping("edit/{badgeId}")
public String edit(@PathVariable("badgeId") String badgeId, ModelMap mmap) {
AdamCaomeiBadge badge = adamCaomeiBadgeAdminService.getOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, badgeId), false
);
mmap.put("badge", badge);
return prefix + "/badge_edit";
}
@RequiresPermissions("adam:caomei:badge:edit")
@Log(title = "草莓护照-徽章管理:修改", businessType = BusinessType.UPDATE)
@PostMapping("edit")
@ResponseBody
public AjaxResult editSave(AdamCaomeiBadgeParam param) {
AdamCaomeiBadge oldBadge = adamCaomeiBadgeAdminService.getOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, param.getBadgeId()), false
);
if (oldBadge == null || oldBadge.getDisplayStatus() == 1) {
return error("已发布的徽章不允许修改");
}
AdamCaomeiBadge badge = new AdamCaomeiBadge();
BeanUtils.copyProperties(param, badge);
badge.setShareText(StringUtils.defaultString(badge.getShareText()));
badge.setMid(oldBadge.getMid());
badge.setUpdatedAt(new java.util.Date());
if (badge.getType() != null && badge.getType() != 2) {
badge.setPerformanceId("");
}
return toAjax(adamCaomeiBadgeAdminService.updateById(badge));
}
@RequiresPermissions("adam:caomei:badge:edit")
@Log(title = "草莓护照-徽章管理:状态修改", businessType = BusinessType.UPDATE)
@PostMapping("changeStatus")
@ResponseBody
public AjaxResult changeStatus(AdamCaomeiBadgeParam param) {
AdamCaomeiBadge oldBadge = adamCaomeiBadgeAdminService.getOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class).eq(AdamCaomeiBadge::getBadgeId, param.getBadgeId()), false
);
if (oldBadge == null) {
return error("操作失败:未找到徽章");
}
AdamCaomeiBadge updateBadge = new AdamCaomeiBadge();
updateBadge.setMid(oldBadge.getMid());
updateBadge.setDisplayStatus(param.getDisplayStatus());
updateBadge.setUpdatedAt(new java.util.Date());
return toAjax(adamCaomeiBadgeAdminService.updateById(updateBadge));
}
}
package com.liquidnet.client.admin.web.controller.zhengzai.adam;
import com.liquidnet.client.admin.common.annotation.Log;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.client.admin.zhengzai.adam.service.IAdamCaomeiPassportAdminService;
import com.liquidnet.service.adam.dto.param.AdamCaomeiPassportSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportDetailVo;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
@Api(tags = "草莓护照-护照管理")
@Controller
@RequestMapping("adam/caomei/passport")
public class AdamCaomeiPassportController extends BaseController {
private final String prefix = "zhengzai/adam/caomei/passport";
@Autowired
private IAdamCaomeiPassportAdminService adamCaomeiPassportAdminService;
@RequiresPermissions("adam:caomei:passport:list")
@GetMapping()
public String view() {
return prefix + "/passport_list";
}
@RequiresPermissions("adam:caomei:passport:list")
@PostMapping("list")
@ResponseBody
public TableDataInfo list(AdamCaomeiPassportSearchParam param) {
startPage();
PageInfo<?> pageInfo = adamCaomeiPassportAdminService.listPassports(param);
return getDataTable(pageInfo);
}
@RequiresPermissions("adam:caomei:passport:list")
@GetMapping("detail/{passportNo}")
public String detail(@PathVariable("passportNo") String passportNo, ModelMap mmap) {
AdamCaomeiPassportDetailVo detail = adamCaomeiPassportAdminService.getPassportDetail(passportNo);
mmap.put("detail", detail);
return prefix + "/passport_detail";
}
@RequiresPermissions("adam:caomei:passport:edit")
@Log(title = "草莓护照-护照解绑", businessType = BusinessType.UPDATE)
@PostMapping("unbind")
@ResponseBody
public AjaxResult unbind(@RequestParam("passportNo") String passportNo) {
boolean ok = adamCaomeiPassportAdminService.unbindPassport(passportNo);
if (!ok) {
return error("解绑失败:护照不存在或当前未绑定用户");
}
return success();
}
}
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增徽章')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-badge-add">
<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>
</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>
</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" required onchange="typeChange(this.value)">
<option value="">请选择</option>
<option value="1">护照纪念徽章</option>
<option value="2">演出纪念徽章</option>
<option value="3">特殊徽章</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">排序:</label>
<div class="col-sm-8">
<input name="sort" class="form-control" type="number" min="0" value="0" placeholder="数值越大越靠前">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">分享文案:</label>
<div class="col-sm-8">
<textarea name="shareText" class="form-control" rows="3" maxlength="255" placeholder="请输入徽章分享文案"></textarea>
</div>
</div>
<div class="form-group" id="ticketTimesDiv" style="display: none;">
<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>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
var prefix = ctx + "adam/caomei/badge";
function typeChange(val) {
if (val == 2) {
$("#ticketTimesDiv").show();
$("#performanceId").prop("required", true);
} else {
$("#ticketTimesDiv").hide();
$("#performanceId").prop("required", false);
$("#performanceId").val("");
}
}
$("#form-badge-add").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-badge-add').serialize());
}
}
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('领取用户列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<input type="hidden" id="badgeId" th:value="${badgeId}">
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "adam/caomei/badge";
$(function() {
var options = {
url: prefix + "/claimUsers/list",
queryParams: function(params) {
var curParams = {
pageSize: params.limit,
pageNum: params.offset / params.limit + 1,
searchValue: params.search,
orderByColumn: params.sort,
isAsc: params.order,
badgeId: $("#badgeId").val()
};
return curParams;
},
modalName: "领取用户",
columns: [
{
field: 'nickname',
title: '用户昵称'
},
{
field: 'mobile',
title: '手机号'
},
{
field: 'claimedAt',
title: '领取时间'
},
{
field: 'source',
title: '获取途径',
formatter: function(value, row, index) {
if (value == 1) return '绑定护照自动发放';
if (value == 2) return '购票自动发放';
if (value == 3) return '补签审核通过';
if (value == 4) return '现场管理员手动发放';
return '未知';
}
}
]
};
$.table.init(options);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('徽章详情')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-badge-detail" th:object="${badge}">
<div class="form-group">
<label class="col-sm-3 control-label">徽章名称:</label>
<div class="col-sm-8">
<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">
<span th:if="*{icon != null and (!icon.startsWith('http') and !icon.startsWith('/'))}" style="font-size: 24px;" th:text="*{icon}"></span>
<img th:if="*{icon != null and (icon.startsWith('http') or icon.startsWith('/'))}" th:src="*{icon}" style="max-height: 30px;"/>
</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: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>
</div>
<div class="form-group" th:style="${badge.type == 2 ? '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>
</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:if="*{displayStatus == 1}">已发布</div>
<div class="form-control-static" th:if="*{displayStatus == 0}">下架</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="*{sort}"></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="*{shareText}"></div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
$(function() {
// 隐藏底部的确认按钮,只保留关闭按钮
var index = parent.layer.getFrameIndex(window.name);
if (index) {
var $layero = parent.$("#layui-layer" + index);
$layero.find(".layui-layer-btn0").hide(); // 隐藏确认按钮
}
});
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改徽章')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-badge-edit" th:object="${badge}">
<input name="badgeId" th:field="*{badgeId}" type="hidden">
<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>
</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>
</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>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">排序:</label>
<div class="col-sm-8">
<input name="sort" th:field="*{sort}" class="form-control" type="number" min="0" placeholder="数值越大越靠前">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">分享文案:</label>
<div class="col-sm-8">
<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;'}">
<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>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
var prefix = ctx + "adam/caomei/badge";
function typeChange(val) {
if (val == 2) {
$("#ticketTimesDiv").show();
$("#performanceId").prop("required", true);
} else {
$("#ticketTimesDiv").hide();
$("#performanceId").prop("required", false);
$("#performanceId").val("");
}
}
$("#form-badge-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-badge-edit').serialize());
}
}
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('草莓护照徽章列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
徽章名称:<input type="text" name="name"/>
</li>
<li>
徽章类型:<select name="type" th:with="type=${@dict.getType('adam_caomei_badge_type')}">
<option value="">所有</option>
<option value="1">护照纪念徽章</option>
<option value="2">演出纪念徽章</option>
<option value="3">特殊徽章</option>
</select>
</li>
<li>
状态:<select name="displayStatus">
<option value="">所有</option>
<option value="0">下架</option>
<option value="1">已发布</option>
</select>
</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>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="adam:caomei:badge:add">
<i class="fa fa-plus"></i> 新增
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var editFlag = [[${@permission.hasPermi('adam:caomei:badge:edit')}]];
var prefix = ctx + "adam/caomei/badge";
$(function() {
var options = {
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
modalName: "草莓护照徽章",
columns: [{
checkbox: true
},
{
field: 'mid',
title: '主键ID',
visible: false
},
{
field: 'badgeId',
title: '徽章ID'
},
{
field: 'icon',
title: '图标',
formatter: function(value, row, index) {
// 如果是图片链接,可以用 $.table.imageView(value)
// 如果是 emoji,直接返回
if(value && (value.startsWith('http') || value.startsWith('/'))) {
return $.table.imageView(value);
}
return '<span style="font-size: 24px;">' + value + '</span>';
}
},
{
field: 'name',
title: '名称'
},
{
field: 'type',
title: '类型',
formatter: function(value, row, index) {
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>';
return value;
}
},
{
field: 'createdAt',
title: '添加时间',
formatter: function(value, row, index) {
if (!value) return '-';
return value.length >= 10 ? value.substring(0, 10) : value;
}
},
{
field: 'claimedCount',
title: '领取人数'
},
{
field: 'sort',
title: '排序(越大越靠前)'
},
{
field: 'displayStatus',
title: '状态',
align: 'center',
formatter: function (value, row, index) {
if (value == 1) {
return '<span class="badge badge-primary" style="background-color: #e6f3ff; color: #1890ff; padding: 5px 10px; border-radius: 4px;">已发布</span>';
} else {
return '<span class="badge badge-default" style="background-color: #f5f5f5; color: #999; padding: 5px 10px; border-radius: 4px;">下架</span>';
}
}
},
{
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var actions = [];
// 详情按钮
actions.push('<a class="btn btn-info btn-sm" style="margin-right: 5px;" href="javascript:void(0)" onclick="detail(\'' + row.badgeId + '\')"><i class="fa fa-eye"></i>详情</a>');
// 仅下架状态可修改(编辑)
if (row.displayStatus == 0) {
actions.push('<a class="btn btn-success btn-sm ' + editFlag + '" style="margin-right: 5px;" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.badgeId + '\')"><i class="fa fa-edit"></i>编辑</a>');
}
// 下架/发布按钮
if (row.displayStatus == 1) {
actions.push('<a class="btn btn-danger btn-sm" style="margin-right: 5px;" href="javascript:void(0)" onclick="disable(\'' + row.badgeId + '\')"><i class="fa fa-arrow-down"></i>下架</a>');
} else {
actions.push('<a class="btn btn-primary btn-sm" style="margin-right: 5px;" href="javascript:void(0)" onclick="enable(\'' + row.badgeId + '\')"><i class="fa fa-upload"></i>发布</a>');
}
// 领取用户按钮
actions.push('<a class="btn btn-warning btn-sm" href="javascript:void(0)" onclick="claimedUsers(\'' + row.badgeId + '\')"><i class="fa fa-users"></i>领取用户</a>');
return actions.join('');
}
}]
};
$.table.init(options);
});
/* 徽章下架 */
function disable(badgeId) {
$.modal.confirm("确认要下架该徽章吗?", function() {
$.operate.post(prefix + "/changeStatus", { "badgeId": badgeId, "displayStatus": 0 });
});
}
/* 徽章发布 */
function enable(badgeId) {
$.modal.confirm("确认要发布该徽章吗?", function() {
$.operate.post(prefix + "/changeStatus", { "badgeId": badgeId, "displayStatus": 1 });
});
}
/* 详情 */
function detail(badgeId) {
$.modal.open("徽章详情", prefix + "/detail/" + badgeId);
}
/* 领取用户 */
function claimedUsers(badgeId) {
if (!badgeId) {
$.modal.msgError("未找到徽章ID");
return;
}
$.modal.open("领取用户", prefix + "/claimUsers/" + badgeId);
}
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('补签详情')" />
</head>
<body class="gray-bg">
<div class="container-div" th:if="${detail != null}">
<div class="row">
<div class="col-sm-12">
<div class="ibox">
<div class="ibox-title"><h5>申请信息</h5></div>
<div class="ibox-content">
<table class="table table-bordered table-striped">
<tbody>
<tr><td class="col-sm-2">申请ID</td><td th:text="${detail.applyRecordId}">-</td></tr>
<tr><td>申请账号</td><td th:text="${detail.userId}">-</td></tr>
<tr><td>用户名</td><td th:text="${detail.nickname != null && detail.nickname != '' ? detail.nickname : '-'}">-</td></tr>
<tr><td>姓名</td><td th:text="${detail.realName != null && detail.realName != '' ? detail.realName : '-'}">-</td></tr>
<tr><td>证件号</td><td th:text="${detail.idCard != null && detail.idCard != '' ? detail.idCard : '-'}">-</td></tr>
<tr><td>申请徽章</td><td th:text="${detail.badgeName != null && detail.badgeName != '' ? detail.badgeName : '-'}">-</td></tr>
<tr><td>关联演出</td><td th:text="${detail.performanceName != null && detail.performanceName != '' ? detail.performanceName : '-'}">-</td></tr>
<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;"/>
</a>
</td>
</tr>
<tr>
<td>审核状态</td>
<td>
<span th:if="${detail.auditStatus == 0}" class="label label-warning">待审核</span>
<span th:if="${detail.auditStatus == 1}" class="label label-success">已通过</span>
<span th:if="${detail.auditStatus == 2}" class="label label-danger">已驳回</span>
</td>
</tr>
<tr><td>申请时间</td><td th:text="${detail.createdAt != null ? #dates.format(detail.createdAt, 'yyyy-MM-dd HH:mm:ss') : '-'}">-</td></tr>
<tr><td>审核时间</td><td th:text="${detail.updatedAt != null ? #dates.format(detail.updatedAt, 'yyyy-MM-dd HH:mm:ss') : '-'}">-</td></tr>
<tr th:if="${detail.auditStatus == 2}">
<td>驳回理由</td>
<td th:text="${detail.rejectReason != null && detail.rejectReason != '' ? detail.rejectReason : '-'}">-</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="container-div" th:if="${detail == null}">
<div class="alert alert-warning">未找到申请信息</div>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
$(function() {
// 详情弹窗无需确认按钮,仅保留关闭
var index = parent.layer.getFrameIndex(window.name);
if (index) {
var $layero = parent.$("#layui-layer" + index);
$layero.find(".layui-layer-btn0").hide();
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('补签审核')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
用户名/姓名:<input type="text" name="userName" placeholder="昵称或姓名"/>
</li>
<li>
申请状态:
<select name="auditStatus">
<option value="">全部</option>
<option value="0">待审核</option>
<option value="1">已通过</option>
<option value="2">已驳回</option>
</select>
</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>
</li>
</ul>
</div>
</form>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var detailFlag = [[${@permission.hasPermi('adam:caomei:badgeApplyAudit:list')}]];
var passFlag = [[${@permission.hasPermi('adam:caomei:badgeApplyAudit:edit')}]];
var rejectFlag = [[${@permission.hasPermi('adam:caomei:badgeApplyAudit:edit')}]];
var prefix = ctx + "adam/caomei/badgeApplyAudit";
$(function() {
var options = {
url: prefix + "/list",
formId: "formId",
modalName: "补签审核",
columns: [
{ field: 'applyRecordId', title: '申请ID' },
{ field: 'userId', title: '申请账号' },
{ field: 'realName', title: '姓名', formatter: function(v) { return v || '-'; } },
{ field: 'idCard', title: '证件号', formatter: function(v) { return v || '-'; } },
{ field: 'badgeName', title: '申请徽章', formatter: function(v) { return v || '-'; } },
{ field: 'performanceName', title: '关联演出', formatter: function(v) { return v || '-'; } },
{
field: 'proofImageUrl',
title: '申请附件',
align: 'center',
formatter: function(value) {
if (!value) return '-';
return '<img src="' + value + '" style="height:32px;max-width:60px;cursor:pointer;border-radius:3px;" onclick=\'viewImage(' + JSON.stringify(value) + ')\' />';
}
},
{
field: 'auditStatus',
title: '审核状态',
align: 'center',
formatter: function(value) {
if (value === 0) return '<span class="label label-warning">待审核</span>';
if (value === 1) return '<span class="label label-success">已通过</span>';
if (value === 2) return '<span class="label label-danger">已驳回</span>';
return '-';
}
},
{
title: '操作',
align: 'center',
formatter: function(value, row) {
var actions = [];
actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick=\'detail(' + JSON.stringify(row.applyRecordId) + ')\'><i class="fa fa-eye"></i>详情</a> ');
if (row.auditStatus === 0) {
actions.push('<a class="btn btn-success btn-xs ' + passFlag + '" href="javascript:void(0)" onclick=\'passAudit(' + JSON.stringify(row.applyRecordId) + ')\'><i class="fa fa-check"></i>通过</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + rejectFlag + '" href="javascript:void(0)" onclick=\'rejectAudit(' + JSON.stringify(row.applyRecordId) + ')\'><i class="fa fa-close"></i>驳回</a>');
}
return actions.join('');
}
}
]
};
$.table.init(options);
});
function viewImage(url) {
window.open(url, "_blank");
}
function detail(applyRecordId) {
$.modal.open("补签详情", prefix + "/detail/" + encodeURIComponent(applyRecordId));
}
function passAudit(applyRecordId) {
$.modal.confirm("确认审核通过并为用户发放对应徽章吗?", function() {
$.operate.post(prefix + "/pass", { applyRecordId: applyRecordId });
});
}
function rejectAudit(applyRecordId) {
layer.prompt({
title: "请输入驳回理由",
formType: 2,
maxlength: 255
}, function(value, index) {
layer.close(index);
var reason = $.trim(value);
if (!reason) {
$.modal.msgError("驳回理由不能为空");
return;
}
$.operate.post(prefix + "/reject", { applyRecordId: applyRecordId, rejectReason: reason });
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('护照详情')" />
</head>
<body class="gray-bg">
<div class="container-div" th:if="${detail != null}">
<div class="row">
<div class="col-sm-12">
<div class="ibox">
<div class="ibox-title"><h5>护照信息</h5></div>
<div class="ibox-content">
<table class="table table-bordered table-striped">
<tbody>
<tr><td class="col-sm-2">护照编号</td><td th:text="${detail.passportNo}">-</td></tr>
<tr><td>绑定状态</td>
<td>
<span th:if="${detail.status == 1}" class="label label-info">已绑定</span>
<span th:if="${detail.status == 0}" class="label label-default">未绑定</span>
<span th:if="${detail.status == 2}" class="label label-warning">已作废</span>
</td>
</tr>
<tr><td>批次号</td><td th:text="${detail.batchNo != null ? detail.batchNo : '-'}">-</td></tr>
<tr><td>首次绑定时间</td><td th:text="${detail.boundAt != null ? #dates.format(detail.boundAt, 'yyyy-MM-dd HH:mm:ss') : '-'}">-</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-12" th:if="${detail.userId != null and detail.userId != ''}">
<div class="ibox">
<div class="ibox-title"><h5>关联用户信息</h5></div>
<div class="ibox-content">
<table class="table table-bordered table-striped">
<tbody>
<tr><td class="col-sm-2">用户ID</td><td th:text="${detail.userId}">-</td></tr>
<tr><td>昵称</td><td th:text="${detail.nickname}">-</td></tr>
<tr><td>手机号</td><td th:text="${detail.mobile}">-</td></tr>
<tr><td>真实姓名</td><td th:text="${detail.realName}">-</td></tr>
<tr><td>证件号</td><td th:text="${detail.idCard}">-</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-12" th:if="${detail.userId == null or detail.userId == ''}">
<div class="alert alert-info">当前护照未绑定用户</div>
</div>
<div class="col-sm-12" th:if="${detail.badges != null and !#lists.isEmpty(detail.badges)}">
<div class="ibox">
<div class="ibox-title"><h5>已领取徽章</h5></div>
<div class="ibox-content">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>徽章名称</th>
<th>图标</th>
<th>领取时间</th>
<th>获取途径</th>
</tr>
</thead>
<tbody>
<tr th:each="b : ${detail.badges}">
<td th:text="${b.badgeName}">-</td>
<td>
<span th:if="${b.icon != null and (b.icon.startsWith('http') or b.icon.startsWith('/'))}">
<img th:src="${b.icon}" style="max-height:28px;"/>
</span>
<span th:if="${b.icon != null and !(b.icon.startsWith('http') or b.icon.startsWith('/'))}" th:text="${b.icon}" style="font-size:22px;"></span>
</td>
<td th:text="${b.claimedAt != null ? #dates.format(b.claimedAt, 'yyyy-MM-dd HH:mm:ss') : '-'}">-</td>
<td>
<span th:if="${b.source == 1}">绑定护照自动发放</span>
<span th:if="${b.source == 2}">购票自动发放</span>
<span th:if="${b.source == 3}">补签审核通过</span>
<span th:if="${b.source == 4}">现场管理员手动发放</span>
<span th:if="${b.source == null or b.source &lt; 1 or b.source &gt; 4}">未知</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="container-div" th:if="${detail == null}">
<div class="alert alert-warning">未找到护照信息</div>
</div>
<th:block th:include="include :: footer" />
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('护照管理')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>护照编号:</label>
<input type="text" name="passportNo" placeholder="模糊匹配"/>
</li>
<li>
<label>绑定状态:</label>
<select name="bindStatus">
<option value="">全部</option>
<option value="1">已绑定</option>
<option value="0">未绑定</option>
<option value="2">已作废</option>
</select>
</li>
<li>
<label>用户昵称/姓名:</label>
<input type="text" name="userName" placeholder="昵称或真实姓名"/>
</li>
<li>
<label>手机号:</label>
<input type="text" name="mobile" placeholder="完整或片段"/>
</li>
<li>
<label>证件号:</label>
<input type="text" name="idCard" placeholder="完整或片段"/>
</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>
</li>
</ul>
</div>
</form>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var unbindFlag = [[${@permission.hasPermi('adam:caomei:passport:edit')}]];
var detailFlag = [[${@permission.hasPermi('adam:caomei:passport:list')}]];
var prefix = ctx + "adam/caomei/passport";
$(function() {
var options = {
url: prefix + "/list",
formId: "formId",
modalName: "护照",
columns: [
{ field: 'mid', title: '主键', visible: false },
{ field: 'passportNo', title: '护照ID' },
{
field: 'nickname',
title: '关联用户',
formatter: function(value, row, index) {
if (!value && row.bindStatus !== 1) {
return '<span class="text-muted">未绑定用户</span>';
}
return value || '-';
}
},
{ field: 'realName', title: '姓名', formatter: function(v) { return v || '-'; } },
{ field: 'mobile', title: '手机号', formatter: function(v) { return v || '-'; } },
{ field: 'idCard', title: '证件号', formatter: function(v) { return v || '-'; } },
{
field: 'bindStatus',
title: '绑定状态',
align: 'center',
formatter: function(value) {
if (value === 1) {
return '<span class="label label-info">已绑定</span>';
}
if (value === 0) {
return '<span class="label label-default">未绑定</span>';
}
if (value === 2) {
return '<span class="label label-warning">已作废</span>';
}
return value;
}
},
{ field: 'claimedBadgeCount', title: '已领取徽章数', align: 'center' },
{
title: '操作',
align: 'center',
formatter: function(value, row) {
var actions = [];
actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick=\'openDetail(' + JSON.stringify(row.passportNo) + ')\'><i class="fa fa-eye"></i>查看详情</a> ');
if (row.bindStatus === 1) {
actions.push('<a class="btn btn-warning btn-xs ' + unbindFlag + '" href="javascript:void(0)" onclick=\'unbind(' + JSON.stringify(row.passportNo) + ')\'><i class="fa fa-unlink"></i>解绑用户</a>');
}
return actions.join('');
}
}
]
};
$.table.init(options);
});
function openDetail(passportNo) {
$.modal.openTab("护照详情", prefix + "/detail/" + encodeURIComponent(passportNo));
}
function unbind(passportNo) {
$.modal.confirm("解绑后该护照将变为未绑定状态,用户需重新绑定。确认解绑?", function() {
$.operate.post(prefix + "/unbind", { passportNo: passportNo });
});
}
</script>
</body>
</html>
package com.liquidnet.client.admin.zhengzai.adam.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeClaimUserSearchParam;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeClaimUserVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
/**
* 草莓护照-徽章管理
*/
public interface IAdamCaomeiBadgeAdminService extends IService<AdamCaomeiBadge> {
/**
* 徽章管理(每个徽章领取人数)
* @param param
* @return
*/
PageInfo<AdamCaomeiBadgeVo> listWithClaimedCount(AdamCaomeiBadgeSearchParam param);
/**
* 徽章领取用户列表
* @param param
* @return
*/
PageInfo<AdamCaomeiBadgeClaimUserVo> listClaimUsers(AdamCaomeiBadgeClaimUserSearchParam param);
/**
* 清除用户端「上架草莓徽章列表」Redis 缓存,与 adam 侧 {@code AdamRdmService#delPublishedCaomeiBadges} 一致。
* 后台新增/编辑/上下架成功后应调用,避免用户端仍读到旧列表。
*/
void delPublishedCaomeiBadges();
}
package com.liquidnet.client.admin.zhengzai.adam.service;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyAuditSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyAuditDetailVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyAuditVo;
public interface IAdamCaomeiBadgeApplyAuditAdminService {
PageInfo<AdamCaomeiBadgeApplyAuditVo> listApplyAudits(AdamCaomeiBadgeApplyAuditSearchParam param);
AdamCaomeiBadgeApplyAuditDetailVo getApplyAuditDetail(String applyRecordId);
boolean passApplyAudit(String applyRecordId);
boolean rejectApplyAudit(String applyRecordId, String rejectReason);
}
package com.liquidnet.client.admin.zhengzai.adam.service;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.adam.dto.param.AdamCaomeiPassportSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportDetailVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportListVo;
/**
* 草莓护照-护照管理
*/
public interface IAdamCaomeiPassportAdminService {
PageInfo<AdamCaomeiPassportListVo> listPassports(AdamCaomeiPassportSearchParam param);
AdamCaomeiPassportDetailVo getPassportDetail(String passportNo);
boolean unbindPassport(String passportNo);
}
package com.liquidnet.client.admin.zhengzai.adam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.zhengzai.adam.service.IAdamCaomeiBadgeAdminService;
import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.service.adam.constant.AdamRedisConst;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeClaimCountDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeClaimUserDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeClaimUserSearchParam;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeSearchParam;
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 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.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class AdamCaomeiBadgeAdminServiceImpl extends ServiceImpl<AdamCaomeiBadgeMapper, AdamCaomeiBadge> implements IAdamCaomeiBadgeAdminService {
@Autowired
private RedisDataSourceUtil redisDataSourceUtil;
@Override
public void delPublishedCaomeiBadges() {
redisDataSourceUtil.getRedisAdamUtil().del(AdamRedisConst.INFO_CAOMEI_BADGE_PUBLISHED);
}
@Override
public boolean save(AdamCaomeiBadge entity) {
boolean ok = super.save(entity);
if (ok) {
delPublishedCaomeiBadges();
}
return ok;
}
@Override
public boolean updateById(AdamCaomeiBadge entity) {
boolean ok = super.updateById(entity);
if (ok) {
delPublishedCaomeiBadges();
}
return ok;
}
@Override
public PageInfo<AdamCaomeiBadgeVo> listWithClaimedCount(AdamCaomeiBadgeSearchParam param) {
LambdaQueryWrapper<AdamCaomeiBadge> queryWrapper = Wrappers.lambdaQuery(AdamCaomeiBadge.class);
if (StringUtils.isNotBlank(param.getName())) {
queryWrapper.like(AdamCaomeiBadge::getName, param.getName());
}
if (param.getType() != null) {
queryWrapper.eq(AdamCaomeiBadge::getType, param.getType());
}
if (param.getDisplayStatus() != null) {
queryWrapper.eq(AdamCaomeiBadge::getDisplayStatus, param.getDisplayStatus());
}
queryWrapper.orderByDesc(AdamCaomeiBadge::getSort, AdamCaomeiBadge::getUpdatedAt);
List<AdamCaomeiBadge> badges = this.list(queryWrapper);
PageInfo<AdamCaomeiBadge> entityPage = new PageInfo<>(badges);
List<String> badgeIds = entityPage.getList().stream()
.map(AdamCaomeiBadge::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
// key: 徽章ID,value: 领取人数
Map<String, Integer> countMap = badgeIds.isEmpty() ? Collections.emptyMap() :
baseMapper.selectClaimedCountByBadgeIds(badgeIds).stream()
.collect(Collectors.toMap(AdamCaomeiBadgeClaimCountDto::getBadgeId, AdamCaomeiBadgeClaimCountDto::getClaimedCount, (a, b) -> a));
List<AdamCaomeiBadgeVo> voList = entityPage.getList().stream().map(item -> {
AdamCaomeiBadgeVo vo = new AdamCaomeiBadgeVo();
BeanUtils.copyProperties(item, vo);
vo.setClaimedCount(countMap.getOrDefault(item.getBadgeId(), 0));
return vo;
}).collect(Collectors.toList());
PageInfo<AdamCaomeiBadgeVo> voPage = new PageInfo<>(voList);
voPage.setTotal(entityPage.getTotal());
return voPage;
}
@Override
public PageInfo<AdamCaomeiBadgeClaimUserVo> listClaimUsers(AdamCaomeiBadgeClaimUserSearchParam param) {
if (param == null || StringUtils.isBlank(param.getBadgeId())) {
return new PageInfo<>(Collections.emptyList());
}
List<AdamCaomeiBadgeClaimUserDto> list = baseMapper.selectClaimUsersByBadgeId(param.getBadgeId());
PageInfo<AdamCaomeiBadgeClaimUserDto> dtoPage = new PageInfo<>(list);
List<AdamCaomeiBadgeClaimUserVo> voList = dtoPage.getList().stream().map(item -> {
AdamCaomeiBadgeClaimUserVo vo = new AdamCaomeiBadgeClaimUserVo();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
PageInfo<AdamCaomeiBadgeClaimUserVo> voPage = new PageInfo<>(voList);
voPage.setTotal(dtoPage.getTotal());
return voPage;
}
}
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.service.adam.dto.AdamCaomeiBadgeApplyAuditDetailDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyAuditDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyAuditSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyAuditDetailVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyAuditVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class AdamCaomeiBadgeApplyAuditAdminServiceImpl implements IAdamCaomeiBadgeApplyAuditAdminService {
@Autowired
private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
@Override
public PageInfo<AdamCaomeiBadgeApplyAuditVo> listApplyAudits(AdamCaomeiBadgeApplyAuditSearchParam param) {
if (param == null) {
param = new AdamCaomeiBadgeApplyAuditSearchParam();
}
List<AdamCaomeiBadgeApplyAuditDto> rows = badgeApplyRecordMapper.selectAuditList(
StringUtils.trimToEmpty(param.getUserName()),
param.getAuditStatus()
);
PageInfo<AdamCaomeiBadgeApplyAuditDto> dtoPage = new PageInfo<>(rows);
List<AdamCaomeiBadgeApplyAuditVo> voList = dtoPage.getList().stream().map(item -> {
AdamCaomeiBadgeApplyAuditVo vo = new AdamCaomeiBadgeApplyAuditVo();
BeanUtils.copyProperties(item, vo);
vo.setIdCard(maskIdCard(item.getIdCard()));
return vo;
}).collect(Collectors.toList());
PageInfo<AdamCaomeiBadgeApplyAuditVo> voPage = new PageInfo<>(voList);
voPage.setTotal(dtoPage.getTotal());
return voPage;
}
@Override
public AdamCaomeiBadgeApplyAuditDetailVo getApplyAuditDetail(String applyRecordId) {
if (StringUtils.isBlank(applyRecordId)) {
return null;
}
AdamCaomeiBadgeApplyAuditDetailDto detail = badgeApplyRecordMapper.selectAuditDetailByApplyRecordId(applyRecordId.trim());
if (detail == null) {
return null;
}
AdamCaomeiBadgeApplyAuditDetailVo vo = new AdamCaomeiBadgeApplyAuditDetailVo();
BeanUtils.copyProperties(detail, vo);
vo.setIdCard(maskIdCard(detail.getIdCard()));
return vo;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean passApplyAudit(String applyRecordId) {
if (StringUtils.isBlank(applyRecordId)) {
return false;
}
AdamCaomeiBadgeApplyRecord record = badgeApplyRecordMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getApplyRecordId, applyRecordId.trim())
.last("limit 1")
);
if (record == null || record.getAuditStatus() == null || record.getAuditStatus() != 0) {
return false;
}
if (StringUtils.isBlank(record.getUserId()) || StringUtils.isBlank(record.getBadgeId())) {
return false;
}
int updated = badgeApplyRecordMapper.passAudit(applyRecordId.trim());
if (updated <= 0) {
return false;
}
return true;
}
@Override
public boolean rejectApplyAudit(String applyRecordId, String rejectReason) {
if (StringUtils.isBlank(applyRecordId) || StringUtils.isBlank(rejectReason)) {
return false;
}
return badgeApplyRecordMapper.rejectAudit(applyRecordId.trim(), rejectReason.trim()) > 0;
}
private static String maskIdCard(String idCard) {
if (StringUtils.isBlank(idCard)) {
// 未实名或无证件信息
return "";
}
if (idCard.length() > 8) {
return idCard.substring(0, 4) + "****" + idCard.substring(idCard.length() - 4);
}
return "****";
}
}
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.IAdamCaomeiPassportAdminService;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportListDto;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiPassportSearchParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportBadgeItemVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportDetailVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportListVo;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
import com.liquidnet.service.adam.entity.AdamRealName;
import com.liquidnet.service.adam.entity.AdamUser;
import com.liquidnet.service.adam.entity.AdamUserInfo;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.mapper.AdamRealNameMapper;
import com.liquidnet.service.adam.mapper.AdamUserInfoMapper;
import com.liquidnet.service.adam.mapper.AdamUserMapper;
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 org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class AdamCaomeiPassportAdminServiceImpl implements IAdamCaomeiPassportAdminService {
@Autowired
private AdamCaomeiPassportMapper adamCaomeiPassportMapper;
@Autowired
private AdamUserMapper adamUserMapper;
@Autowired
private AdamUserInfoMapper adamUserInfoMapper;
@Autowired
private AdamRealNameMapper adamRealNameMapper;
@Autowired
private AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
@Override
public PageInfo<AdamCaomeiPassportListVo> listPassports(AdamCaomeiPassportSearchParam param) {
if (param == null) {
param = new AdamCaomeiPassportSearchParam();
}
List<AdamCaomeiPassportListDto> rows = adamCaomeiPassportMapper.selectPassportAdminList(
param.getPassportNo(),
param.getUserName(),
param.getMobile(),
param.getIdCard(),
param.getBindStatus()
);
PageInfo<AdamCaomeiPassportListDto> dtoPage = new PageInfo<>(rows);
List<AdamCaomeiPassportListVo> voList = dtoPage.getList().stream().map(d -> {
AdamCaomeiPassportListVo vo = new AdamCaomeiPassportListVo();
BeanUtils.copyProperties(d, vo);
return vo;
}).collect(Collectors.toList());
PageInfo<AdamCaomeiPassportListVo> voPage = new PageInfo<>(voList);
voPage.setTotal(dtoPage.getTotal());
return voPage;
}
@Override
public AdamCaomeiPassportDetailVo getPassportDetail(String passportNo) {
if (StringUtils.isBlank(passportNo)) {
return null;
}
AdamCaomeiPassport passport = adamCaomeiPassportMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiPassport.class)
.eq(AdamCaomeiPassport::getPassportNo, passportNo.trim())
);
if (passport == null) {
return null;
}
AdamCaomeiPassportDetailVo vo = new AdamCaomeiPassportDetailVo();
vo.setMid(passport.getMid());
vo.setPassportNo(passport.getPassportNo());
vo.setStatus(passport.getStatus());
vo.setUserId(passport.getUserId());
vo.setBoundAt(passport.getBoundAt());
vo.setBatchNo(passport.getBatchNo());
String uid = passport.getUserId();
if (StringUtils.isBlank(uid)) {
vo.setBadges(Collections.emptyList());
return vo;
}
AdamUser user = adamUserMapper.selectOne(Wrappers.lambdaQuery(AdamUser.class).eq(AdamUser::getUid, uid));
AdamUserInfo userInfo = adamUserInfoMapper.selectOne(Wrappers.lambdaQuery(AdamUserInfo.class).eq(AdamUserInfo::getUid, uid));
vo.setNickname(userInfo != null ? StringUtils.defaultString(userInfo.getNickname()) : "");
vo.setMobile(user != null ? maskMobile(user.getMobile()) : "");
AdamRealName realName = adamRealNameMapper.selectOne(
Wrappers.lambdaQuery(AdamRealName.class)
.eq(AdamRealName::getUid, uid)
.eq(AdamRealName::getState, 1)
.orderByDesc(AdamRealName::getMid)
.last("limit 1")
);
if (realName != null) {
vo.setRealName(StringUtils.defaultString(realName.getName()));
vo.setIdCard(maskIdCard(realName.getIdCard()));
} else {
vo.setRealName("");
vo.setIdCard("");
}
List<AdamCaomeiPassportUserBadgeDto> badgeRows = adamCaomeiPassportMapper.selectUserBadgesByUid(uid);
vo.setBadges(badgeRows.stream().map(b -> {
AdamCaomeiPassportBadgeItemVo item = new AdamCaomeiPassportBadgeItemVo();
item.setBadgeId(b.getBadgeId());
item.setBadgeName(b.getBadgeName());
item.setIcon(b.getIcon());
item.setClaimedAt(b.getClaimedAt());
item.setSource(b.getSource());
return item;
}).collect(Collectors.toList()));
return vo;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean unbindPassport(String passportNo) {
if (StringUtils.isBlank(passportNo)) {
return false;
}
AdamCaomeiPassport passport = adamCaomeiPassportMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiPassport.class)
.eq(AdamCaomeiPassport::getPassportNo, passportNo.trim())
);
if (passport == null) {
return false;
}
if (passport.getStatus() == null || passport.getStatus() != 1) {
return false;
}
String uid = passport.getUserId();
Date now = new Date();
passport.setStatus(0);
passport.setUserId("");
passport.setUnboundAt(now);
passport.setUpdatedAt(now);
boolean updated = adamCaomeiPassportMapper.updateById(passport) > 0;
if (!updated) {
return false;
}
if (StringUtils.isNotBlank(uid)) {
// 解绑后,清理该用户由“绑定护照”自动发放的护照纪念徽章
adamCaomeiBadgeMapper.deletePassportTypeBadgesByUid(uid);
}
return true;
}
private static String maskMobile(String mobile) {
if (StringUtils.isBlank(mobile)) {
return "";
}
if (mobile.length() >= 11) {
return mobile.substring(0, 3) + "****" + mobile.substring(7);
}
return mobile;
}
private static String maskIdCard(String idCard) {
if (StringUtils.isBlank(idCard)) {
return "";
}
if (idCard.length() > 8) {
return idCard.substring(0, 4) + "****" + idCard.substring(idCard.length() - 4);
}
return "****";
}
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AdamCaomeiBadgeApplyAuditDetailDto {
private String applyRecordId;
private String userId;
private String nickname;
private String realName;
private String idCard;
private String badgeId;
private String badgeName;
private String performanceId;
private String performanceName;
private String proofImageUrl;
private Integer auditStatus;
private String rejectReason;
private Date createdAt;
private Date updatedAt;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AdamCaomeiBadgeApplyAuditDto {
private String applyRecordId;
private String userId;
private String nickname;
private String realName;
private String idCard;
private String badgeName;
private String performanceName;
private String proofImageUrl;
private Integer auditStatus;
private Date createdAt;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AdamCaomeiBadgeApplyRecordUserDto {
private String applyRecordId;
private String badgeName;
private Date applyTime;
private Integer auditStatus;
private String rejectReason;
private String proofImageUrl;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
@Data
public class AdamCaomeiBadgeClaimCountDto {
private String badgeId;
private Integer claimedCount;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AdamCaomeiBadgeClaimUserDto {
private String nickname;
private String mobile;
private Date claimedAt;
private Integer source;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
/**
* 护照管理列表行
*/
@Data
public class AdamCaomeiPassportListDto {
private Long mid;
private String passportNo;
private String nickname;
private String realName;
private String mobile;
private String idCard;
/** 0-未绑定 1-已绑定 2-已作废 */
private Integer bindStatus;
private Integer claimedBadgeCount;
}
package com.liquidnet.service.adam.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AdamCaomeiPassportUserBadgeDto {
private String badgeId;
private String badgeName;
private String icon;
private String shareText;
private Integer type;
private Date claimedAt;
private Integer source;
}
package com.liquidnet.service.adam.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 草莓护照-徽章配置表
*/
@Data
@TableName("adam_caomei_badge")
public class AdamCaomeiBadge implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
/**
* 徽章ID
*/
private String badgeId;
/**
* 徽章名称
*/
private String name;
/**
* 徽章图标 (Emoji字符或图片URL)
*/
private String icon;
/**
* 徽章类型: 1-护照纪念徽章, 2-演出纪念徽章, 3-特殊徽章
*/
private Integer type;
/**
* 关联演出ID (仅演出纪念徽章必填,其他类型为空)
*/
private String performanceId;
/**
* 上架状态: 0-下架(默认), 1-已发布
*/
private Integer displayStatus;
/**
* 排序值,数值越大越靠前
*/
private Integer sort;
/**
* 徽章分享文案
*/
private String shareText;
/**
* 添加时间
*/
private Date createdAt;
/**
* 更新时间
*/
private Date updatedAt;
}
package com.liquidnet.service.adam.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("adam_caomei_badge_apply_record")
public class AdamCaomeiBadgeApplyRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String applyRecordId;
private String userId;
private String badgeId;
private String performanceId;
private String proofImageUrl;
private Integer auditStatus;
private String rejectReason;
private Date createdAt;
private Date updatedAt;
}
package com.liquidnet.service.adam.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 草莓护照-实体编号表
*/
@Data
@TableName("adam_caomei_passport")
public class AdamCaomeiPassport implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
private String passportNo;
/**
* 绑定状态: 0-未绑定, 1-已绑定, 2-已作废
*/
private Integer status;
private String userId;
private Date boundAt;
private Date unboundAt;
private String batchNo;
private String remark;
private Date createdAt;
private Date updatedAt;
}
package com.liquidnet.service.adam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyAuditDetailDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyAuditDto;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyRecordUserDto;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AdamCaomeiBadgeApplyRecordMapper extends BaseMapper<AdamCaomeiBadgeApplyRecord> {
@Select({
"<script>",
"select ",
"ar.apply_record_id as applyRecordId,",
"ifnull(u.mobile, '') as userId,",
"ifnull(ui.nickname, '') as nickname,",
"ifnull(arn.name, '') as realName,",
"ifnull(arn.id_card, '') as idCard,",
"ifnull(b.name, '') as badgeName,",
"ifnull(kp.title, ar.performance_id) as performanceName,",
"ar.proof_image_url as proofImageUrl,",
"ar.audit_status as auditStatus,",
"ar.created_at as createdAt ",
"from adam_caomei_badge_apply_record ar ",
"left join adam_user u on ar.user_id = u.uid ",
"left join adam_user_info ui on ar.user_id = ui.uid ",
"left join (",
" select r.uid, r.name, r.id_card ",
" from adam_real_name r ",
" inner join (",
" select uid, max(mid) as max_mid from adam_real_name where state = 1 group by uid",
" ) rm on r.uid = rm.uid and r.mid = rm.max_mid and r.state = 1",
") arn on ar.user_id = arn.uid ",
"left join adam_caomei_badge b on ar.badge_id = b.badge_id ",
"left join kylin_performances kp on ar.performance_id = kp.performances_id ",
"where 1 = 1 ",
"<if test='userName != null and userName != \"\"'>",
"and (ui.nickname like concat('%', #{userName}, '%') or arn.name like concat('%', #{userName}, '%')) ",
"</if>",
"<if test='auditStatus != null'>",
"and ar.audit_status = #{auditStatus} ",
"</if>",
"order by case when ar.audit_status = 0 then 0 else 1 end asc, ar.created_at desc",
"</script>"
})
List<AdamCaomeiBadgeApplyAuditDto> selectAuditList(@Param("userName") String userName,
@Param("auditStatus") Integer auditStatus);
@Select({
"select ",
"ar.apply_record_id as applyRecordId,",
"ifnull(u.mobile, '') as userId,",
"ifnull(ui.nickname, '') as nickname,",
"ifnull(arn.name, '') as realName,",
"ifnull(arn.id_card, '') as idCard,",
"ar.badge_id as badgeId,",
"ifnull(b.name, '') as badgeName,",
"ar.performance_id as performanceId,",
"ifnull(kp.title, ar.performance_id) as performanceName,",
"ar.proof_image_url as proofImageUrl,",
"ar.audit_status as auditStatus,",
"ifnull(ar.reject_reason, '') as rejectReason,",
"ar.created_at as createdAt,",
"ar.updated_at as updatedAt ",
"from adam_caomei_badge_apply_record ar ",
"left join adam_user u on ar.user_id = u.uid ",
"left join adam_user_info ui on ar.user_id = ui.uid ",
"left join (",
" select r.uid, r.name, r.id_card ",
" from adam_real_name r ",
" inner join (select uid, max(mid) as max_mid from adam_real_name where state = 1 group by uid) rm ",
" on r.uid = rm.uid and r.mid = rm.max_mid and r.state = 1",
") arn on ar.user_id = arn.uid ",
"left join adam_caomei_badge b on ar.badge_id = b.badge_id ",
"left join kylin_performances kp on ar.performance_id = kp.performances_id ",
"where ar.apply_record_id = #{applyRecordId} ",
"limit 1"
})
AdamCaomeiBadgeApplyAuditDetailDto selectAuditDetailByApplyRecordId(@Param("applyRecordId") String applyRecordId);
@Update({
"update adam_caomei_badge_apply_record ",
"set audit_status = 1, reject_reason = null, updated_at = now() ",
"where apply_record_id = #{applyRecordId} and audit_status = 0"
})
int passAudit(@Param("applyRecordId") String applyRecordId);
@Update({
"update adam_caomei_badge_apply_record ",
"set audit_status = 2, reject_reason = #{rejectReason}, updated_at = now() ",
"where apply_record_id = #{applyRecordId} and audit_status = 0"
})
int rejectAudit(@Param("applyRecordId") String applyRecordId, @Param("rejectReason") String rejectReason);
@Insert({
"insert ignore into adam_caomei_user_badge (user_id, badge_id, source, created_at) ",
"values (#{userId}, #{badgeId}, 3, now())"
})
int issueBadgeIfAbsent(@Param("userId") String userId, @Param("badgeId") String badgeId);
@Select({
"select ",
"ar.apply_record_id as applyRecordId,",
"ifnull(b.name, '') as badgeName,",
"ar.created_at as applyTime,",
"ar.audit_status as auditStatus,",
"ifnull(ar.reject_reason, '') as rejectReason,",
"proof_image_url as proofImageUrl",
"from adam_caomei_badge_apply_record ar ",
"left join adam_caomei_badge b on ar.badge_id = b.badge_id ",
"where ar.user_id = #{uid} ",
"order by ar.created_at desc"
})
List<AdamCaomeiBadgeApplyRecordUserDto> selectUserApplyRecordsByUid(@Param("uid") String uid);
}
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.entity.AdamCaomeiBadge;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List;
public interface AdamCaomeiBadgeMapper extends BaseMapper<AdamCaomeiBadge> {
@Select({
"<script>",
"select badge_id as badgeId, count(1) as claimedCount",
"from adam_caomei_user_badge",
"where badge_id in",
"<foreach collection='badgeIds' item='badgeId' open='(' separator=',' close=')'>",
"#{badgeId}",
"</foreach>",
"group by badge_id",
"</script>"
})
List<AdamCaomeiBadgeClaimCountDto> selectClaimedCountByBadgeIds(@Param("badgeIds") List<String> badgeIds);
@Select({
"select ",
"ifnull(ui.nickname,'') as nickname,",
"case ",
"when u.mobile is null then '' ",
"when length(u.mobile) >= 11 then concat(substring(u.mobile,1,3),'****',substring(u.mobile,8,4)) ",
"else u.mobile end as mobile,",
"ub.created_at as claimedAt,",
"ub.source as source ",
"from adam_caomei_user_badge ub ",
"left join adam_user_info ui on ub.user_id = ui.uid ",
"left join adam_user u on ub.user_id = u.uid ",
"where ub.badge_id = #{badgeId} ",
"order by ub.created_at desc"
})
List<AdamCaomeiBadgeClaimUserDto> selectClaimUsersByBadgeId(@Param("badgeId") String badgeId);
@Delete({
"delete ub from adam_caomei_user_badge ub ",
"inner join adam_caomei_badge b on ub.badge_id = b.badge_id ",
"where ub.user_id = #{uid} and ub.source = 1 and b.type = 1"
})
int deletePassportTypeBadgesByUid(@Param("uid") String uid);
/**
* 绑定护照成功后,为已上架的「护照纪念徽章」(type=1) 批量发放 source=1(与解绑删除规则对称)
*/
@Insert({
"insert ignore into adam_caomei_user_badge (user_id, badge_id, source, created_at) ",
"select #{userId}, b.badge_id, 1, now() ",
"from adam_caomei_badge b ",
"where b.type = 1 and b.display_status = 1"
})
int insertPassportBindingBadgesForUser(@Param("userId") String userId);
@Insert({
"<script>",
"insert ignore into adam_caomei_user_badge (user_id, badge_id, source, created_at) values ",
"<foreach collection='badgeIds' item='badgeId' separator=','>",
"(#{userId}, #{badgeId}, #{source}, #{createdAt})",
"</foreach>",
"</script>"
})
int insertUserBadgesBatch(@Param("userId") String userId,
@Param("badgeIds") List<String> badgeIds,
@Param("source") Integer source,
@Param("createdAt") Date createdAt);
/**
* 根据身份证号查询用户已支付的演出ID列表
*/
@Select({
"select distinct performance_id ",
"from kylin_order_ticket_entities ",
"where enter_id_code = #{idCard} and is_payment = 1"
})
List<String> selectPaidPerformanceIdsByIdCard(@Param("idCard") String idCard);
@Select("select count(1) from adam_caomei_user_badge where user_id = #{userId} and badge_id = #{badgeId}")
int checkUserBadgeExists(@Param("userId") String userId, @Param("badgeId") String badgeId);
@Insert({
"insert into adam_caomei_user_badge (user_id, badge_id, source, created_at) ",
"values (#{userId}, #{badgeId}, #{source}, now())"
})
int insertUserBadge(@Param("userId") String userId, @Param("badgeId") String badgeId, @Param("source") Integer source);
}
package com.liquidnet.service.adam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportListDto;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AdamCaomeiPassportMapper extends BaseMapper<AdamCaomeiPassport> {
List<AdamCaomeiPassportListDto> selectPassportAdminList(@Param("passportNo") String passportNo,
@Param("userName") String userName,
@Param("mobile") String mobile,
@Param("idCard") String idCard,
@Param("bindStatus") Integer bindStatus);
List<AdamCaomeiPassportUserBadgeDto> selectUserBadgesByUid(@Param("uid") String uid);
/**
* 原子操作:仅当护照状态为 0(未绑定)时,更新为 1(已绑定)
*/
@Update("update adam_caomei_passport set status = 1, user_id = #{userId}, bound_at = now(), updated_at = now() where passport_no = #{passportNo} and status = 0")
int bindPassportAtomic(@Param("passportNo") String passportNo, @Param("userId") String userId);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper">
<select id="selectPassportAdminList" resultType="com.liquidnet.service.adam.dto.AdamCaomeiPassportListDto">
SELECT
p.mid AS mid,
p.passport_no AS passportNo,
IFNULL(ui.nickname, '') AS nickname,
IFNULL(arn.name, '') AS realName,
CASE
WHEN u.mobile IS NULL OR u.mobile = '' THEN ''
WHEN CHAR_LENGTH(u.mobile) &gt;= 11 THEN CONCAT(SUBSTRING(u.mobile, 1, 3), '****', SUBSTRING(u.mobile, 8, 4))
ELSE u.mobile
END AS mobile,
CASE
WHEN arn.id_card IS NULL OR arn.id_card = '' THEN ''
WHEN CHAR_LENGTH(arn.id_card) &gt; 8 THEN CONCAT(SUBSTRING(arn.id_card, 1, 4), '****', SUBSTRING(arn.id_card, CHAR_LENGTH(arn.id_card) - 3, 4))
ELSE '****'
END AS idCard,
p.status AS bindStatus,
IFNULL(bc.cnt, 0) AS claimedBadgeCount
FROM adam_caomei_passport p
LEFT JOIN adam_user u ON p.user_id = u.uid AND p.user_id &lt;&gt; ''
LEFT JOIN adam_user_info ui ON p.user_id = ui.uid
LEFT JOIN (
SELECT r.uid, r.name, r.id_card
FROM adam_real_name r
INNER JOIN (
SELECT uid, MAX(mid) AS max_mid
FROM adam_real_name
WHERE state = 1
GROUP BY uid
) rm ON r.uid = rm.uid AND r.mid = rm.max_mid AND r.state = 1
) arn ON p.user_id = arn.uid
LEFT JOIN (
SELECT user_id, COUNT(1) AS cnt
FROM adam_caomei_user_badge
GROUP BY user_id
) bc ON p.user_id = bc.user_id
<where>
<if test="passportNo != null and passportNo != ''">
AND p.passport_no LIKE CONCAT('%', #{passportNo}, '%')
</if>
<if test="userName != null and userName != ''">
AND (ui.nickname LIKE CONCAT('%', #{userName}, '%') OR arn.name LIKE CONCAT('%', #{userName}, '%'))
</if>
<if test="mobile != null and mobile != ''">
AND u.mobile LIKE CONCAT('%', #{mobile}, '%')
</if>
<if test="idCard != null and idCard != ''">
AND arn.id_card LIKE CONCAT('%', #{idCard}, '%')
</if>
<if test="bindStatus != null">
AND p.status = #{bindStatus}
</if>
</where>
<!-- 已绑定优先展示,避免仅按更新时间导致已绑定记录长期沉底 -->
ORDER BY (p.status = 1) DESC, p.updated_at DESC, p.mid DESC
</select>
<select id="selectUserBadgesByUid" resultType="com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto">
SELECT
ub.badge_id AS badgeId,
IFNULL(b.name, '') AS badgeName,
IFNULL(b.icon, '') AS icon,
IFNULL(b.share_text, '') AS shareText,
IFNULL(b.type, 0) AS type,
ub.created_at AS claimedAt,
ub.source AS source
FROM adam_caomei_user_badge ub
LEFT JOIN adam_caomei_badge b ON ub.badge_id = b.badge_id
WHERE ub.user_id = #{uid}
ORDER BY ub.created_at DESC
</select>
</mapper>
package com.liquidnet.service.adam.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyParam;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeClaimParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyRecordUserVo;
import com.liquidnet.service.adam.service.IAdamCaomeiBadgeUserService;
import com.liquidnet.service.base.ResponseDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import javax.validation.Valid;
import java.util.List;
@ApiSupport(order = 10046)
@Api(tags = "草莓徽章(用户端)")
@Slf4j
@Validated
@RestController
@RequestMapping("caomei/badge")
public class AdamCaomeiBadgeUserController {
@Autowired
private IAdamCaomeiBadgeUserService adamCaomeiBadgeUserService;
@ApiOperationSupport(order = 1)
@ApiOperation("认领徽章")
@PostMapping("claim")
public ResponseDto<String> claim(@Valid @RequestBody AdamCaomeiBadgeClaimParam param) {
String uid = CurrentUtil.getCurrentUid();
return adamCaomeiBadgeUserService.claimBadge(param.getBadgeId(), uid);
}
@ApiOperationSupport(order = 2)
@ApiOperation("补签记录列表")
@GetMapping("apply/list")
public ResponseDto<List<AdamCaomeiBadgeApplyRecordUserVo>> applyList() {
String uid = CurrentUtil.getCurrentUid();
return adamCaomeiBadgeUserService.getApplyRecords(uid);
}
@ApiOperationSupport(order = 3)
@ApiOperation("发起补签申请(驳回后可重新上传)")
@PostMapping("apply")
public ResponseDto<String> apply(@Valid @RequestBody AdamCaomeiBadgeApplyParam param) {
String uid = CurrentUtil.getCurrentUid();
return adamCaomeiBadgeUserService.applyBadge(param, uid);
}
}
package com.liquidnet.service.adam.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.service.adam.dto.param.AdamCaomeiPassportNoParam;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportHomeVo;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiPassportUserClaimedBadgeVo;
import com.liquidnet.service.adam.service.IAdamCaomeiPassportUserService;
import com.liquidnet.service.base.ResponseDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@ApiSupport(order = 10045)
@Api(tags = "草莓护照(用户端)")
@Slf4j
@Validated
@RestController
@RequestMapping("caomei/passport")
public class AdamCaomeiPassportUserController {
@Autowired
private IAdamCaomeiPassportUserService adamCaomeiPassportUserService;
@ApiOperationSupport(order = 1)
@ApiOperation("绑定实体护照(内含·编号有效性、是否可绑等校验)")
@PostMapping("bind")
public ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bind(@Valid @RequestBody AdamCaomeiPassportNoParam param) {
return adamCaomeiPassportUserService.bindPassport(param.getPassportNo());
}
@ApiOperationSupport(order = 2)
@ApiOperation("护照首页聚合数据")
@GetMapping("home")
public ResponseDto<AdamCaomeiPassportHomeVo> home() {
return adamCaomeiPassportUserService.getPassportHome();
}
}
......@@ -12,6 +12,7 @@ import com.liquidnet.commons.lang.util.SensitizeUtil;
import com.liquidnet.service.adam.constant.AdamConst;
import com.liquidnet.service.adam.dto.AdamThirdPartParam;
import com.liquidnet.service.adam.dto.AdamUserInfoParam;
import com.liquidnet.service.adam.dto.param.AdamIdentityParam;
import com.liquidnet.service.adam.dto.vo.*;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamUserInfoService;
......@@ -535,6 +536,71 @@ public class AdamUserController {
return ResponseDto.success(adamRdmService.getUserInfoVoByUid(uid));
}
@ApiOperationSupport(order = 15)
@ApiOperation(value = "实名认证V2(二要素)", notes = "支持手动填写(name+idCard) 或 选择观演人(entersId)")
@PostMapping(value = {"identity/v2"})
public ResponseDto<AdamRealInfoVo> identityV2(@RequestBody AdamIdentityParam param) {
String currentUid = CurrentUtil.getCurrentUid();
// 1. 检查当前登录用户是否已实名
AdamRealInfoVo realInfoVo = adamRdmService.getRealInfoVoByUidPlain(currentUid);
if (realInfoVo != null && realInfoVo.getState() != null && realInfoVo.getState() == 1) {
// 已实名,直接返回
realInfoVo.setName(SensitizeUtil.chineseName(realInfoVo.getName()));
realInfoVo.setIdCard(SensitizeUtil.custom(realInfoVo.getIdCard(), 3, 2));
return ResponseDto.success(realInfoVo);
}
String name = param.getName();
String idCard = param.getIdCard();
// 2. 如果传了 entersId,则根据 entersId 获取观演人的姓名和身份证号
if (StringUtils.isNotBlank(param.getEntersId())) {
List<AdamEntersVo> entersList = adamRdmService.getEntersVoByUid(currentUid);
if (!CollectionUtils.isEmpty(entersList)) {
AdamEntersVo entersVo = entersList.stream()
.filter(e -> param.getEntersId().equals(e.getEntersId()))
.findFirst()
.orElse(null);
if (entersVo == null) {
return ResponseDto.failure(ErrorMapping.get("10105")); // 观演人不存在
}
// 只同步身份证类型的观演人信息 (type=1 大陆身份证)
if (entersVo.getType() == null || entersVo.getType() != 1) {
return ResponseDto.failure("10101", "仅支持大陆身份证类型的观演人进行实名认证");
}
name = entersVo.getName();
idCard = entersVo.getIdCard();
} else {
return ResponseDto.failure(ErrorMapping.get("10105")); // 观演人不存在
}
}
// 3. 校验姓名和身份证号不能为空
if (StringUtils.isBlank(name) || StringUtils.isBlank(idCard)) {
return ResponseDto.failure(ErrorMapping.get("10101")); // 姓名或身份证件号无效
}
// 4. 格式校验
if (!java.util.regex.Pattern.matches(LnsRegex.Valid.CN_HANZI, name)) {
return ResponseDto.failure(ErrorMapping.get("10103"));
}
if (!java.util.regex.Pattern.matches(LnsRegex.Valid.CN_ID_CARD_REF, idCard)) {
return ResponseDto.failure(ErrorMapping.get("10104"));
}
AdamRealInfoVo vo = adamUserService.verifyTwoElements(currentUid, name, idCard);
if (null == vo) {
log.error("[identityV2] 二要素认证失败, param: {}", JsonUtils.toJson(param));
return ResponseDto.failure(ErrorMapping.get("10101"));
}
vo.setName(SensitizeUtil.chineseName(vo.getName()));
vo.setIdCard(SensitizeUtil.custom(vo.getIdCard(), 3, 2));
return ResponseDto.success(vo);
}
/* ---------------------------- Internal Method ---------------------------- */
private static final String PHP_API_SMS_CODE_VALID = "/smsValidation";
......
......@@ -9,10 +9,14 @@ import com.liquidnet.commons.lang.util.IdentityUtils;
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.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.AdamUserMember;
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.AdamUserMapper;
import com.liquidnet.service.adam.mapper.AdamUserMemberMapper;
......@@ -46,6 +50,10 @@ public class AdamRdmService {
AdamUserMemberMapper adamUserMemberMapper;
@Autowired
AdamEntersMapper adamEntersMapper;
@Autowired
AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
@Autowired
AdamCaomeiPassportMapper adamCaomeiPassportMapper;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Switch config */
......@@ -630,6 +638,73 @@ public class AdamRdmService {
redisUtil.del(AdamRedisConst.INFO_ADDRESSES.concat(uid));
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 草莓徽章缓存 */
public boolean setPublishedCaomeiBadges(List<AdamCaomeiBadge> badges) {
return redisUtil.set(AdamRedisConst.INFO_CAOMEI_BADGE_PUBLISHED, badges, 300);
}
/**
* 获取所有已上架的徽章列表
* @return
*/
public List<AdamCaomeiBadge> getPublishedCaomeiBadges() {
String rk = AdamRedisConst.INFO_CAOMEI_BADGE_PUBLISHED;
List<AdamCaomeiBadge> badges = (List<AdamCaomeiBadge>) redisUtil.get(rk);
if (CollectionUtils.isEmpty(badges)) {
badges = adamCaomeiBadgeMapper.selectList(
new QueryWrapper<AdamCaomeiBadge>().lambda()
.eq(AdamCaomeiBadge::getDisplayStatus, 1)
.orderByAsc(AdamCaomeiBadge::getType)
.orderByDesc(AdamCaomeiBadge::getSort)
.orderByDesc(AdamCaomeiBadge::getMid)
);
if (!CollectionUtils.isEmpty(badges)) {
setPublishedCaomeiBadges(badges);
}
}
return badges;
}
public void delPublishedCaomeiBadges() {
redisUtil.del(AdamRedisConst.INFO_CAOMEI_BADGE_PUBLISHED);
}
/**
* 设置用户领取徽章redis
* @param uid
* @param badges
* @return
*/
public boolean setUserCaomeiBadgesByUid(String uid, List<AdamCaomeiPassportUserBadgeDto> badges) {
return redisUtil.set(AdamRedisConst.INFO_CAOMEI_BADGE_USER.concat(uid), badges, 300);
}
/**
* 获取用户徽章
* @param uid
* @return
*/
public List<AdamCaomeiPassportUserBadgeDto> getUserCaomeiBadgesByUid(String uid) {
String rk = AdamRedisConst.INFO_CAOMEI_BADGE_USER.concat(uid);
List<AdamCaomeiPassportUserBadgeDto> badges = (List<AdamCaomeiPassportUserBadgeDto>) redisUtil.get(rk);
if (CollectionUtils.isEmpty(badges)) {
badges = adamCaomeiPassportMapper.selectUserBadgesByUid(uid);
if (!CollectionUtils.isEmpty(badges)) {
setUserCaomeiBadgesByUid(uid, badges);
}
}
return badges;
}
/**
* 删除用户徽章redis
* @param uid
*/
public void delUserCaomeiBadgesByUid(String uid) {
redisUtil.del(AdamRedisConst.INFO_CAOMEI_BADGE_USER.concat(uid));
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | List<购买会员黑名单UID> */
public boolean setBlacklistForMember(List<String> uids) {
......
package com.liquidnet.service.adam.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.commons.lang.util.IDGenerator;
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;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
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.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
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.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserService {
@Autowired
private AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
@Autowired
private AdamCaomeiPassportMapper adamCaomeiPassportMapper;
@Autowired
private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
@Autowired
private AdamRdmService adamRdmService;
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseDto<String> claimBadge(String badgeId, String uid) {
if (StringUtils.isBlank(uid)) {
return ResponseDto.failure(ErrorMapping.get("10001"));
}
// 1. 查询徽章配置
AdamCaomeiBadge badge = adamCaomeiBadgeMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class)
.eq(AdamCaomeiBadge::getBadgeId, badgeId)
.eq(AdamCaomeiBadge::getDisplayStatus, 1)
);
if (badge == null) {
log.error("[claimBadge] 徽章不存在或未上架, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10607"));
}
// 2. 检查用户是否已经领取过该徽章
int count = adamCaomeiBadgeMapper.checkUserBadgeExists(uid, badgeId);
if (count > 0) {
log.info("[claimBadge] 用户已领取过该徽章, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10608"));
}
int type = badge.getType() == null ? 0 : badge.getType();
// 3. 根据徽章类型执行不同的认领逻辑
if (type == 1) {
// 护照纪念徽章:需要绑定了护照才能领取
AdamCaomeiPassport bound = adamCaomeiPassportMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiPassport.class)
.eq(AdamCaomeiPassport::getUserId, uid)
.eq(AdamCaomeiPassport::getStatus, 1)
.last("limit 1")
);
if (bound == null) {
log.error("[claimBadge] 认领护照纪念徽章需先绑定护照, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10609"));
}
// 发放徽章 (source: 1-绑定护照自动发放/护照徽章认领)
adamCaomeiBadgeMapper.insertUserBadge(uid, badgeId, 1);
adamRdmService.delUserCaomeiBadgesByUid(uid);
log.info("[claimBadge] 护照纪念徽章认领成功, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.success(badgeId);
} else if (type == 2) {
// 演出纪念徽章:需要实名认证 +(购票记录 或 补签审核已通过)
AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
if (real == null || real.getState() == null || real.getState() != 1 || StringUtils.isBlank(real.getIdCard())) {
log.error("[claimBadge] 认领演出徽章需先实名, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10610"));
}
String idCard = real.getIdCard();
List<String> paidPerformanceIds = adamCaomeiBadgeMapper.selectPaidPerformanceIdsByIdCard(idCard);
// 有支付记录
boolean hasPaidRecord = paidPerformanceIds != null && paidPerformanceIds.contains(badge.getPerformanceId());
Integer passedApplyCount = badgeApplyRecordMapper.selectCount(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.eq(AdamCaomeiBadgeApplyRecord::getBadgeId, badgeId)
.eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 1)
);
boolean hasPassedApply = passedApplyCount != null && passedApplyCount > 0;
if (!hasPaidRecord && !hasPassedApply) {
log.error("[claimBadge] 无购票记录且无通过的补签申请,无法认领, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10611"));
}
// 自动发放该演出所有关联徽章
// List<AdamCaomeiBadge> performanceBadges = adamCaomeiBadgeMapper.selectList(
// Wrappers.lambdaQuery(AdamCaomeiBadge.class)
// .eq(AdamCaomeiBadge::getPerformanceId, badge.getPerformanceId())
// .eq(AdamCaomeiBadge::getDisplayStatus, 1)
// .eq(AdamCaomeiBadge::getType, 2)
// );
// for (AdamCaomeiBadge perfBadge : performanceBadges) {
// 发放徽章:2-购票自动发放/演出徽章认领,3-补签审核通过后认领
int source = hasPaidRecord ? 2 : 3;
adamCaomeiBadgeMapper.insertUserBadge(uid, badgeId, source);
adamRdmService.delUserCaomeiBadgesByUid(uid);
// }
log.info("[claimBadge] 演出纪念徽章认领成功, uid: {}, badgeId: {}, 发放数量: {}", uid, badgeId, 1);
return ResponseDto.success(badgeId);
} else if (type == 3) {
// 特殊徽章不可自助领取
log.error("[claimBadge] 特殊徽章不可自助领取, uid: {}, badgeId: {}", uid, badgeId);
return ResponseDto.failure(ErrorMapping.get("10612"));
}
log.error("[claimBadge] 未知的徽章类型, uid: {}, badgeId: {}, type: {}", uid, badgeId, type);
return ResponseDto.failure(ErrorMapping.get("10613"));
}
@Override
public ResponseDto<List<AdamCaomeiBadgeApplyRecordUserVo>> getApplyRecords(String uid) {
if (StringUtils.isBlank(uid)) {
return ResponseDto.failure(ErrorMapping.get("10001"));
}
List<AdamCaomeiBadgeApplyRecordUserDto> rows = badgeApplyRecordMapper.selectUserApplyRecordsByUid(uid);
if (rows == null) {
rows = Collections.emptyList();
}
List<AdamCaomeiBadgeApplyRecordUserVo> list = rows.stream().map(r -> {
AdamCaomeiBadgeApplyRecordUserVo v = new AdamCaomeiBadgeApplyRecordUserVo();
v.setApplyRecordId(r.getApplyRecordId());
v.setBadgeName(StringUtils.defaultString(r.getBadgeName()));
v.setApplyTime(r.getApplyTime());
v.setAuditStatus(r.getAuditStatus());
v.setRejectReason(StringUtils.defaultString(r.getRejectReason()));
v.setCanReupload(r.getAuditStatus() != null && r.getAuditStatus() == 2);
v.setProofImageUrl(r.getProofImageUrl());
return v;
}).collect(Collectors.toList());
return ResponseDto.success(list);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseDto<String> applyBadge(AdamCaomeiBadgeApplyParam param, String uid) {
if (StringUtils.isBlank(uid)) {
return ResponseDto.failure(ErrorMapping.get("10001"));
}
if (param == null || StringUtils.isBlank(param.getBadgeId()) || StringUtils.isBlank(param.getProofImageUrl())) {
return ResponseDto.failure("参数错误");
}
String badgeId = StringUtils.trimToEmpty(param.getBadgeId());
String proofImageUrl = StringUtils.trimToEmpty(param.getProofImageUrl());
AdamCaomeiBadge badge = adamCaomeiBadgeMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiBadge.class)
.eq(AdamCaomeiBadge::getBadgeId, badgeId)
.last("limit 1")
);
if (badge == null) {
return ResponseDto.failure(ErrorMapping.get("10607"));
}
if (badge.getType() == null || badge.getType() != 2) {
return ResponseDto.failure("仅演出纪念徽章支持补签申请");
}
int claimedCount = adamCaomeiBadgeMapper.checkUserBadgeExists(uid, badgeId);
if (claimedCount > 0) {
return ResponseDto.failure(ErrorMapping.get("10608"));
}
Integer pendingCount = badgeApplyRecordMapper.selectCount(
Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
.eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
.eq(AdamCaomeiBadgeApplyRecord::getBadgeId, badgeId)
.eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 0)
);
if (pendingCount != null && pendingCount > 0) {
return ResponseDto.failure("您已有待审核的补签申请,请勿重复提交");
}
Date now = new Date();
AdamCaomeiBadgeApplyRecord record = new AdamCaomeiBadgeApplyRecord();
record.setApplyRecordId(IDGenerator.nextSnowId());
record.setUserId(uid);
record.setBadgeId(badgeId);
record.setPerformanceId(StringUtils.defaultString(badge.getPerformanceId()));
record.setProofImageUrl(proofImageUrl);
record.setAuditStatus(0);
record.setRejectReason("");
record.setCreatedAt(now);
record.setUpdatedAt(now);
int inserted = badgeApplyRecordMapper.insert(record);
if (inserted <= 0) {
return ResponseDto.failure();
}
return ResponseDto.success(record.getApplyRecordId());
}
}
package com.liquidnet.service.adam.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.commons.lang.util.CurrentUtil;
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.AdamCaomeiBadgeMapper;
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.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
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.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUserService {
@Autowired
private AdamCaomeiPassportMapper adamCaomeiPassportMapper;
@Autowired
private AdamCaomeiBadgeMapper adamCaomeiBadgeMapper;
@Autowired
private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
@Autowired
private AdamRdmService adamRdmService;
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bindPassport(String passportNo) {
String uid = CurrentUtil.getCurrentUid();
// 1. 先校验护照信息是否满足绑定条件
AdamCaomeiPassport passport = adamCaomeiPassportMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiPassport.class)
.eq(AdamCaomeiPassport::getPassportNo, passportNo.trim()));
if (passport == null) {
log.error("[bindPassport] 未查询到护照信息, passportSn: {}", passportNo);
return ResponseDto.failure(ErrorMapping.get("10600"));
}
Integer st = passport.getStatus();
if (st != null && st == 2) {
log.error("[bindPassport] 护照已作废, passportSn: {}", passportNo);
return ResponseDto.failure(ErrorMapping.get("10601"));
}
if (st != null && st == 1) {
if (uid.equals(StringUtils.trimToEmpty(passport.getUserId()))) {
return ResponseDto.failure(ErrorMapping.get("10606")); // 提示无需重复绑定
}
log.error("[bindPassport] 护照已被其他账号绑定, passportSn: {}", passportNo);
return ResponseDto.failure(ErrorMapping.get("10602"));
}
// 2. 校验当前用户是否已经绑定过其他护照
AdamCaomeiPassport userExistsPassport = findBoundPassportForUser(uid);
if (userExistsPassport != null && !passportNo.equals(userExistsPassport.getPassportNo())) {
log.error("[bindPassport] 已绑定护照, uid: {}, existsPassportSn: {}, newPassportSn: {}", uid, userExistsPassport.getPassportNo(), passportNo);
return ResponseDto.failure(ErrorMapping.get("10603"));
}
// 3. 尝试原子更新绑定状态 (仅当 status = 0 时才能更新成功)
int updatedRows = adamCaomeiPassportMapper.bindPassportAtomic(passportNo.trim(), uid);
if (updatedRows > 0) {
// 4. 优先复用已上架徽章缓存,再筛选护照纪念徽章(type=1),用于批量发放和返回给前端弹窗
List<AdamCaomeiBadge> publishedBadges = adamRdmService.getPublishedCaomeiBadges();
if (publishedBadges == null) {
publishedBadges = new ArrayList<>();
}
List<AdamCaomeiBadge> passportTypeBadges = publishedBadges.stream()
.filter(b -> b.getType() != null && b.getType() == 1)
.collect(Collectors.toList());
List<String> badgeIds = passportTypeBadges.stream()
.map(AdamCaomeiBadge::getBadgeId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
Date grantAt = new Date();
if (!badgeIds.isEmpty()) {
adamCaomeiBadgeMapper.insertUserBadgesBatch(uid, badgeIds, 1, grantAt);
}
// 清理用户徽章缓存,下次读取首页时回源并重建
adamRdmService.delUserCaomeiBadgesByUid(uid);
// 5. 返回本次绑定场景下的护照类型徽章列表(用于前端弹窗)
List<AdamCaomeiPassportUserClaimedBadgeVo> grantedBadges = passportTypeBadges.stream().map(b -> {
AdamCaomeiPassportUserClaimedBadgeVo v = new AdamCaomeiPassportUserClaimedBadgeVo();
v.setBadgeId(b.getBadgeId());
v.setName(StringUtils.defaultString(b.getName()));
v.setIcon(StringUtils.defaultString(b.getIcon()));
v.setShareText(StringUtils.defaultString(b.getShareText()));
v.setType(b.getType());
v.setClaimedAt(grantAt);
v.setSource(1);
return v;
}).collect(Collectors.toList());
log.info("[bindPassport] 护照绑定成功, uid: {}, passportSn: {}", uid, passportNo);
return ResponseDto.success(grantedBadges);
}
// 6. 如果原子性修改未成功,说明在并发情况下被别人抢先绑定了
log.error("[bindPassport] 并发绑定失败,护照已被抢占, passportSn: {}", passportNo);
return ResponseDto.failure(ErrorMapping.get("10602"));
}
@Override
public ResponseDto<AdamCaomeiPassportHomeVo> getPassportHome() {
String uid = CurrentUtil.getCurrentUid();
log.info("[getPassportHome] 开始获取护照首页数据, uid: {}", uid);
// 4. 获取用户当前绑定的护照信息
AdamCaomeiPassport bound = findBoundPassportForUser(uid);
if (null == bound) {
log.error("[getPassportHome] 用户未绑定护照, uid: {}", uid);
return ResponseDto.failure(ErrorMapping.get("10604")); // 未绑定护照
}
// 1. 初始化返回对象
AdamCaomeiPassportHomeVo home = new AdamCaomeiPassportHomeVo();
AdamCaomeiPassportUserCardVo card = buildUserCard(uid, bound);
home.setUserCard(card);
// 3. 获取用户实名认证信息
AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
String idCard = null;
boolean isRealNameVerified = false;
if (real != null && real.getState() != null && real.getState() == 1 && real.getType() == 1) {
isRealNameVerified = true;
idCard = real.getIdCard();
}
card.setRealNameVerified(isRealNameVerified);
log.info("[getPassportHome] 实名认证状态, uid: {}, 是否已实名: {}, 是否有身份证号: {}",
uid, isRealNameVerified, StringUtils.isNotBlank(idCard));
// 5. TODO 优化点
final List<String> paidPerformanceIds = StringUtils.isNotBlank(idCard)
? adamCaomeiBadgeMapper.selectPaidPerformanceIdsByIdCard(idCard)
: new ArrayList<>();
log.info("[getPassportHome] 用户已支付的演出订单数量, uid: {}, 数量: {}", uid, paidPerformanceIds.size());
ApplyBadgeStatus applyBadgeStatus = loadApplyBadgeStatus(uid);
// 6. 查询用户已认领的所有徽章记录 (用于展示徽章墙)
List<AdamCaomeiPassportUserBadgeDto> rows = adamRdmService.getUserCaomeiBadgesByUid(uid);
if (rows == null) {
rows = new ArrayList<>();
}
List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows);
home.setClaimedBadges(claimed);
log.info("[getPassportHome] 用户已认领的徽章数量, uid: {}, 数量: {}", uid, claimed.size());
// 转换为 Map 方便后续匹配货架上的徽章是否已认领
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap = toClaimedBadgeMap(rows);
// 7. 查询所有已上架的徽章配置,并按类型升序、排序值降序、ID降序排序
List<AdamCaomeiBadge> published = adamRdmService.getPublishedCaomeiBadges();
if (published == null) {
published = new ArrayList<>();
}
log.info("[getPassportHome] 系统已上架的徽章数量, uid: {}, 数量: {}", uid, published.size());
// 8. 组装全部上架徽章列表 (扁平结构,前端按 type 筛选展示)
List<AdamCaomeiPassportBadgeShelfItemVo> allBadges = toShelfItems(
published,
claimedMap,
paidPerformanceIds,
applyBadgeStatus.passedApplyBadgeIds,
applyBadgeStatus.pendingApplyBadgeIds
);
home.setAllBadges(allBadges);
log.info("[getPassportHome] 获取护照首页数据成功, uid: {}", uid);
return ResponseDto.success(home);
}
private AdamCaomeiPassportUserCardVo buildUserCard(String uid, AdamCaomeiPassport bound) {
AdamCaomeiPassportUserCardVo card = new AdamCaomeiPassportUserCardVo();
card.setPassportBound(true);
card.setPassportNo(bound.getPassportNo());
card.setPassportClaimedAt(bound.getBoundAt());
AdamUserInfoVo userInfo = adamRdmService.getUserInfoVoByUid(uid);
if (userInfo != null) {
card.setAvatar(StringUtils.defaultString(userInfo.getAvatar()));
card.setNickname(StringUtils.defaultString(userInfo.getNickname()));
} else {
card.setAvatar("");
card.setNickname("");
}
return card;
}
private ApplyBadgeStatus loadApplyBadgeStatus(String uid) {
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<>();
for (AdamCaomeiBadgeApplyRecord r : applyRecords) {
if (r == null || StringUtils.isBlank(r.getBadgeId()) || r.getAuditStatus() == null) {
continue;
}
if (r.getAuditStatus() == 1) {
passedApplyBadgeIds.add(r.getBadgeId());
} else if (r.getAuditStatus() == 0) {
pendingApplyBadgeIds.add(r.getBadgeId());
}
}
return new ApplyBadgeStatus(passedApplyBadgeIds, pendingApplyBadgeIds);
}
private static List<AdamCaomeiPassportUserClaimedBadgeVo> toClaimedBadgeVos(List<AdamCaomeiPassportUserBadgeDto> rows) {
return rows.stream().map(r -> {
AdamCaomeiPassportUserClaimedBadgeVo v = new AdamCaomeiPassportUserClaimedBadgeVo();
v.setBadgeId(r.getBadgeId());
v.setName(StringUtils.defaultString(r.getBadgeName()));
v.setIcon(StringUtils.defaultString(r.getIcon()));
v.setShareText(StringUtils.defaultString(r.getShareText()));
v.setType(r.getType());
v.setClaimedAt(r.getClaimedAt());
v.setSource(r.getSource());
return v;
}).collect(Collectors.toList());
}
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 static List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
Set<String> passedApplyBadgeIds,
Set<String> pendingApplyBadgeIds) {
return published.stream()
.map(b -> toShelfItem(b, claimedMap, paidPerformanceIds, passedApplyBadgeIds, pendingApplyBadgeIds))
.collect(Collectors.toList());
}
/**
* @param b 徽章信息
* @param claimedMap 用户已领取的徽章
* @param paidPerformanceIds 当前账号实名身份证号码购买的演出IDs
* @param passedApplyBadgeIds 补签审核已通过的徽章ID集合
* @param pendingApplyBadgeIds 补签待审核的徽章ID集合
* @return
*/
private static AdamCaomeiPassportBadgeShelfItemVo toShelfItem(AdamCaomeiBadge b,
Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
List<String> paidPerformanceIds,
Set<String> passedApplyBadgeIds,
Set<String> pendingApplyBadgeIds) {
AdamCaomeiPassportBadgeShelfItemVo v = new AdamCaomeiPassportBadgeShelfItemVo();
v.setBadgeId(b.getBadgeId());
v.setName(StringUtils.defaultString(b.getName()));
v.setIcon(StringUtils.defaultString(b.getIcon()));
v.setShareText(StringUtils.defaultString(b.getShareText()));
v.setType(b.getType());
v.setPerformanceId(StringUtils.defaultString(b.getPerformanceId()));
v.setApplyPending(false);
// 判断当前徽章是否已认领
AdamCaomeiPassportUserBadgeDto got = claimedMap.get(b.getBadgeId());
boolean claimed = got != null;
v.setClaimed(claimed);
v.setClaimedAt(claimed ? got.getClaimedAt() : null);
int type = b.getType() == null ? 0 : b.getType();
// 针对未认领的徽章,根据类型判断是否可认领 (claimable)
if (!claimed) {
if (type == 1) {
// 护照纪念徽章:只要绑定了护照,就可认领(通常是绑定时漏发或后来新上架的)
v.setClaimable(true);
} else if (type == 2) {
// 演出纪念徽章:有购票记录或补签审核通过即可认领
boolean canClaimByPaid = paidPerformanceIds != null && paidPerformanceIds.contains(b.getPerformanceId());
boolean canClaimByApply = passedApplyBadgeIds != null && passedApplyBadgeIds.contains(b.getBadgeId());
v.setClaimable(canClaimByPaid || canClaimByApply);
v.setApplyPending(pendingApplyBadgeIds != null && pendingApplyBadgeIds.contains(b.getBadgeId()));
} else if (type == 3) {
// 特殊徽章:不可自助领取,需要提示用户
v.setClaimable(false);
}
} else {
// 已认领的徽章,可认领状态置为 false
v.setClaimable(false);
}
return v;
}
private AdamCaomeiPassport findBoundPassportForUser(String uid) {
if (StringUtils.isBlank(uid)) {
return null;
}
return adamCaomeiPassportMapper.selectOne(
Wrappers.lambdaQuery(AdamCaomeiPassport.class)
.eq(AdamCaomeiPassport::getUserId, uid)
.eq(AdamCaomeiPassport::getStatus, 1)
.last("limit 1")
);
}
private static final class ApplyBadgeStatus {
private final Set<String> passedApplyBadgeIds;
private final Set<String> pendingApplyBadgeIds;
private ApplyBadgeStatus(Set<String> passedApplyBadgeIds, Set<String> pendingApplyBadgeIds) {
this.passedApplyBadgeIds = passedApplyBadgeIds;
this.pendingApplyBadgeIds = pendingApplyBadgeIds;
}
}
}
......@@ -509,4 +509,32 @@ public class AdamUserServiceImpl implements IAdamUserService {
log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);
return vo;
}
@Override
public AdamRealInfoVo verifyTwoElements(String uid, String name, String idCard) {
try {
adamRdmService.identityHandler1(uid, name, idCard);
AdamRealName realName = new AdamRealName();
realName.setRealNameId(IDGenerator.nextSnowId() + "");
realName.setUid(uid);
realName.setType(1);
realName.setNode(2);
realName.setName(name);
realName.setIdCard(idCard);
realName.setState(1);
realName.setCreatedAt(LocalDateTime.now());
adamRealNameService.add(realName);
AdamRealInfoVo vo = AdamRealInfoVo.getNew().copy(realName);
long s = System.currentTimeMillis();
adamRdmService.setRealInfoVoByUid(uid, vo);
log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);
return vo;
}catch (Exception e) {
log.error("error", e);
return null;
}
}
}
......@@ -86,6 +86,22 @@
10504=\u4F1A\u5458\u8BA2\u5355\u56DE\u8C03\u5904\u7406\u5931\u8D25\uFF0C\u4F1A\u5458\u4EF7\u683C\u4FE1\u606F\u4E0D\u5B58\u5728
10505=\u4F1A\u5458\u8BA2\u5355\u56DE\u8C03\u5904\u7406\u5F02\u5E38
# \u8349\u8393\u62A4\u7167\uFF08C\u7AEF\u7ED1\u5B9A/\u9996\u9875\uFF09
10600=\u62A4\u7167\u7F16\u53F7\u65E0\u6548\u6216\u4E0D\u5B58\u5728
10601=\u8BE5\u62A4\u7167\u5DF2\u4F5C\u5E9F
10602=\u8BE5\u62A4\u7167\u5DF2\u88AB\u5176\u4ED6\u8D26\u53F7\u7ED1\u5B9A
10603=\u60A8\u5DF2\u7ED1\u5B9A\u5176\u4ED6\u62A4\u7167\uFF0C\u65E0\u6CD5\u91CD\u590D\u7ED1\u5B9A
10604=\u672A\u7ED1\u5B9A\u62A4\u7167
10605=\u62A4\u7167\u7ED1\u5B9A\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5
10606=\u8BE5\u62A4\u7167\u5DF2\u4E0E\u60A8\u7ED1\u5B9A\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C
10607=\u5FBD\u7AE0\u4E0D\u5B58\u5728\u6216\u672A\u4E0A\u67B6
10608=\u60A8\u5DF2\u9886\u53D6\u8FC7\u8BE5\u5FBD\u7AE0
10609=\u8BA4\u9886\u62A4\u7167\u7EAA\u5FF5\u5FBD\u7AE0\u9700\u5148\u7ED1\u5B9A\u62A4\u7167
10610=\u8BA4\u9886\u6F14\u51FA\u5FBD\u7AE0\u9700\u5148\u5B9E\u540D
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
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