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

Commit 796e07c5 authored by 姜秀龙's avatar 姜秀龙

Merge remote-tracking branch 'origin/master' into jxl_20240313_prod

# Conflicts:
#	liquidnet-bus-service/liquidnet-service-goblin/liquidnet-service-goblin-impl/src/main/java/com/liquidnet/service/goblin/service/impl/GoblinOrderAppServiceImpl.java
parents e1b7cf44 8d25b760
-- 艺人关联商品表
CREATE TABLE IF NOT EXISTS `kylin_artist_product` (
`mid` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`relation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联记录ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人ID',
`spu_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品SPU ID',
`sort` int DEFAULT 0 COMMENT '排序权重,越大越靠前',
`status` tinyint DEFAULT 1 COMMENT '1启用 0禁用',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_relation_id` (`relation_id`),
UNIQUE KEY `uk_artist_spu` (`artist_id`, `spu_id`),
KEY `idx_artist_id` (`artist_id`),
KEY `idx_spu_id` (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='艺人关联商品';
ALTER TABLE `smile_volunteers`
ADD COLUMN `nation` varchar(16) NOT NULL DEFAULT '' COMMENT '民族' AFTER `sex`;
-- 动态表单报名活动 - 字典配置
-- dict_value:英文活动标识,与 C 端提交 activityName、库表 sweet_form_submission.activity_name 一致
-- dict_label:后台展示中文名
-- 可按实际活动增删下方字典数据;重复执行不会重复插入(按 dict_type / dict_value 判重)
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '动态表单报名活动', 'sweet_form_activity', '0', 'admin', NOW(), '动态表单报名活动标识与中文名映射'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'sweet_form_activity');
-- 示例字典数据(请按实际活动修改或追加)
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '2026春季达人招募', 'talent_2026_spring', 'sweet_form_activity', NULL, NULL, 'N', '0', 'admin', NOW(), '示例'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'sweet_form_activity' AND dict_value = 'talent_2026_spring');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '音乐节志愿者报名', 'music_fest_volunteer', 'sweet_form_activity', NULL, NULL, 'N', '0', 'admin', NOW(), '示例'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'sweet_form_activity' AND dict_value = 'music_fest_volunteer');
-- 动态表单报名活动管理 - 菜单与权限
-- 挂载在「正在映画」顶级菜单下(按 menu_name 查询,兼容线上/测试 menu_id 不一致)
SET @zhengzaiRootId = (
SELECT menu_id
FROM sys_menu
WHERE menu_name = '正在映画'
AND parent_id = 0
AND menu_type = 'M'
LIMIT 1
);
INSERT INTO sys_menu (menu_name, parent_id, order_num, url, target, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES ('报名活动管理', @zhengzaiRootId, 10, '#', 'menuItem', 'M', '0', NULL, 'fa fa-list-alt', 'admin', NOW(), '', NULL, '动态表单报名活动管理');
SET @parentId = LAST_INSERT_ID();
INSERT INTO sys_menu (menu_name, parent_id, order_num, url, target, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES ('活动列表', @parentId, 1, '/sweet/formSubmission/listView', 'menuItem', 'C', '0', 'sweet:formSubmission:list', '#', 'admin', NOW(), '', NULL, '报名活动列表');
SET @menuId = LAST_INSERT_ID();
INSERT INTO sys_menu (menu_name, parent_id, order_num, url, target, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES ('导出', @menuId, 1, '#', '', 'F', '0', 'sweet:formSubmission:export', '#', 'admin', NOW(), '', NULL, '');
......@@ -6,6 +6,7 @@ CREATE TABLE `kylin_artist` (
`mid` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人ID',
`artist_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人名称',
`pinyin` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '姓名拼音',
`artist_type` tinyint(4) NOT NULL COMMENT '艺人类型 1音乐人 2艺术家 3厂牌 4品牌方',
`avatar_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人头像',
`introduction` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人简介',
......
package com.liquidnet.service.adam.constant;
public final class AdamTpaConst {
private AdamTpaConst() {
}
/** 国家网络身份认证 */
public static final String PLATFORM_NIA = "NIA";
}
package com.liquidnet.service.adam.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
@ApiModel(value = "AdamNiaLoginParam", description = "国家网络身份认证登录入参(R01 凭证认证)")
@Data
public class AdamNiaLoginParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(position = 1, required = true, value = "业务序列号,须与前端 SDK bizSeq 一致[32]")
@NotBlank(message = "bizSeq不能为空")
@Size(min = 32, max = 32, message = "bizSeq长度须为32位")
private String bizSeq;
@ApiModelProperty(position = 2, required = true, value = "认证请求数据 idCardAuthData")
@NotBlank(message = "idCardAuthData不能为空")
private String idCardAuthData;
}
......@@ -22,7 +22,7 @@ public class AdamThirdPartParam implements Serializable {
@ApiModelProperty(position = 13, required = true, value = "头像[255]", example = "http://pic.zhengzai.tv/default/avatar.png")
@Size(max = 255, message = "已超出头像链接长度限制")
private String avatar;
@ApiModelProperty(position = 14, required = true, value = "平台类型[255]", allowableValues = "WEIBO,WECHAT,QQ")
@ApiModelProperty(position = 14, required = true, value = "平台类型[255]", allowableValues = "WEIBO,WECHAT,QQ,NIA")
@Pattern(regexp = LnsRegex.Valid.TRIPLE_PF_FOR_ULGOIN, message = "平台类型无效")
@NotBlank(message = "平台类型不能为空")
private String platform;
......
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Getter;
import java.io.Serializable;
/**
* 网证认证成功返回,仅做身份认证不判断绑定。
* 前端拿到 openId 后自行调用 login/tpa 完成登录/注册绑定判断。
*/
@Getter
@Builder
@ApiModel(value = "AdamNiaAuthVo", description = "网证认证结果")
public class AdamNiaAuthVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "平台类型,固定 NIA")
private final String platform;
@ApiModelProperty(value = "网证用户唯一标识(40字节标识的标准Base64,56字符),作为 login/tpa 的 openId 使用")
private final String openId;
}
......@@ -455,6 +455,12 @@ public class GoblinRedisConst {
*/
public static final String SQB_GOBLIN_ORDER_SN_KEY = PREFIX.concat("sqb:orderSn:");
/**
* 订单退款分布式锁
* {goblin:refund:lock:${orderId}, 1}
*/
public static final String REFUND_ORDER_LOCK = PREFIX.concat("refund:lock:");
/**
* 收钱吧 用户订单列表
*/
......
......@@ -83,6 +83,8 @@ public class GoblinBackOrderVo implements Serializable, Cloneable {
private String auditAt;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "发起方[1-用户|2-商家]")
private Integer operationType;
@ApiModelProperty(value = "过期时间")
private String expireAt;
......
package com.liquidnet.service.goblin.dto.vo;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 店铺退款列表接口返回体:分页数据 + 各审核状态 Tab 数量。
*/
@ApiModel("店铺退款列表分页结果")
@Data
public class GoblinStoreBackOrderListPageVo {
/** 当前页列表及分页信息 */
@ApiModelProperty("分页数据")
private PageInfo<GoblinStoreBackOrderListVo> pageInfo;
/** 顶部审核 Tab 角标数量,不受当前 status 筛选影响 */
@ApiModelProperty("各审核状态数量")
private GoblinStoreBackOrderStatusCountVo statusCount;
}
......@@ -20,11 +20,23 @@ public class GoblinStoreBackOrderListVo implements Cloneable {
private Integer type;
@ApiModelProperty(value = "退款/退货状态[0-商铺发起退款|1-退款申请(用户发送退款请求)|2-退款成功(商家同意退款)|3-退款拒绝(商家拒绝退款)|4-退货申请(用户发起退货请求)|5-退货拒绝(商家拒绝退货)|6-退货审核通过等待用户填写物流(商家审核通过,等待用户寄回商品)|7-待收货(用户已确认)|8-退货完成(商家收货并且同意退款给用户)|9-退货失败(商家不同意退款)|10-退款失败|11-取消退款")
private Integer status;
@ApiModelProperty(value = " 退款金额")
@ApiModelProperty(value = "退款范围[1-整单退款|2-部分退款]")
private Integer refundScope;
@ApiModelProperty(value = "原订单实付金额")
private BigDecimal priceActual;
@ApiModelProperty(value = "实际退款金额")
private BigDecimal realBackPrice;
@ApiModelProperty(value = " 创建时间")
@ApiModelProperty(value = "发起方[1-用户发起|2-商家发起]")
private Integer operationType;
@ApiModelProperty(value = "申请时间")
private String createdAt;
@ApiModelProperty(value = "退款sku")
@ApiModelProperty(value = "审核时间")
private String auditAt;
@ApiModelProperty(value = "退款完成时间")
private String refundAt;
@ApiModelProperty(value = "处理时间(优先退款完成时间,其次审核时间,最后拒绝时间)")
private String processedAt;
@ApiModelProperty(value = "退款商品明细")
private List<GoblinBackOrderSkuVo> backOrderSkuVos;
......
package com.liquidnet.service.goblin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Map;
/**
* 店铺退款列表 - 各 status 数量。
* status 含义见 {@link GoblinBackOrderVo#getStatus()},前端自行组合 Tab。
*/
@ApiModel("店铺退款列表-各审核状态数量")
@Data
public class GoblinStoreBackOrderStatusCountVo {
@ApiModelProperty("全部")
private long all;
@ApiModelProperty("商铺发起退款[status=0]")
private long storeInitiated;
@ApiModelProperty("退款申请[status=1]")
private long refundApply;
@ApiModelProperty("退款成功[status=2]")
private long refundSuccess;
@ApiModelProperty("退款拒绝[status=3]")
private long refundReject;
@ApiModelProperty("退货申请[status=4]")
private long returnApply;
@ApiModelProperty("退货拒绝[status=5]")
private long returnReject;
@ApiModelProperty("退货审核通过待填物流[status=6]")
private long returnWaitLogistics;
@ApiModelProperty("待收货[status=7]")
private long waitReceive;
@ApiModelProperty("退货完成[status=8]")
private long returnCompleted;
@ApiModelProperty("退货失败[status=9]")
private long returnFailed;
@ApiModelProperty("退款失败[status=10]")
private long refundFailed;
@ApiModelProperty("取消退款[status=11]")
private long refundCancelled;
/**
* 从 Mongo 按 status 分组统计结果组装 VO。
*/
public static GoblinStoreBackOrderStatusCountVo of(long all, Map<Integer, Long> grouped) {
GoblinStoreBackOrderStatusCountVo vo = new GoblinStoreBackOrderStatusCountVo();
vo.all = all;
vo.storeInitiated = grouped.getOrDefault(0, 0L);
vo.refundApply = grouped.getOrDefault(1, 0L);
vo.refundSuccess = grouped.getOrDefault(2, 0L);
vo.refundReject = grouped.getOrDefault(3, 0L);
vo.returnApply = grouped.getOrDefault(4, 0L);
vo.returnReject = grouped.getOrDefault(5, 0L);
vo.returnWaitLogistics = grouped.getOrDefault(6, 0L);
vo.waitReceive = grouped.getOrDefault(7, 0L);
vo.returnCompleted = grouped.getOrDefault(8, 0L);
vo.returnFailed = grouped.getOrDefault(9, 0L);
vo.refundFailed = grouped.getOrDefault(10, 0L);
vo.refundCancelled = grouped.getOrDefault(11, 0L);
return vo;
}
}
package com.liquidnet.service.goblin.dto.vo;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 店铺订单列表接口返回体:分页数据 + 各状态 Tab 数量。
*/
@ApiModel("店铺订单列表分页结果")
@Data
public class GoblinStoreOrderListPageVo {
/** 当前页列表及分页信息 */
@ApiModelProperty("分页数据")
private PageInfo<GoblinStoreOrderListVo> pageInfo;
/** 顶部状态 Tab 角标数量,不受当前 status 筛选影响 */
@ApiModelProperty("各状态数量")
private GoblinStoreOrderStatusCountVo statusCount;
}
......@@ -64,6 +64,15 @@ public class GoblinStoreOrderListVo implements Cloneable {
@ApiModelProperty(value = " sku相关")
private List<GoblinStoreOrderListSkuVo> storeOrderListSkuVoList;
@ApiModelProperty(value = " 下单账号-UID")
private String userId;
@ApiModelProperty(value = " 下单账号-昵称")
private String userName;
@ApiModelProperty(value = " 下单账号-手机号")
private String userMobile;
private static final GoblinStoreOrderListVo obj = new GoblinStoreOrderListVo();
......
package com.liquidnet.service.goblin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Map;
/**
* 店铺订单列表 - 各状态 Tab 数量。
* 对应前端 statusTabs:全部 / 待支付(0) / 待发货(2) / 已发货(3) / 已完成(4) / 已退款(6)
*/
@ApiModel("店铺订单列表-各状态数量")
@Data
public class GoblinStoreOrderStatusCountVo {
/** 全部(含其他 status,如取消单等) */
@ApiModelProperty("全部")
private long all;
/** 待支付 status=0 */
@ApiModelProperty("待支付[status=0]")
private long pendingPay;
/** 待发货 status=2 */
@ApiModelProperty("待发货[status=2]")
private long pendingShip;
/** 已发货 status=3 */
@ApiModelProperty("已发货[status=3]")
private long shipped;
/** 已完成 status=4 */
@ApiModelProperty("已完成[status=4]")
private long completed;
/** 已退款 status=6 */
@ApiModelProperty("已退款[status=6]")
private long refunded;
/**
* 从 Mongo 按 status 分组统计结果组装 VO。
* 新增 Tab 时在此补充 getOrDefault 即可。
*/
public static GoblinStoreOrderStatusCountVo of(long all, Map<Integer, Long> grouped) {
GoblinStoreOrderStatusCountVo vo = new GoblinStoreOrderStatusCountVo();
vo.all = all;
vo.pendingPay = grouped.getOrDefault(0, 0L);
vo.pendingShip = grouped.getOrDefault(2, 0L);
vo.shipped = grouped.getOrDefault(3, 0L);
vo.completed = grouped.getOrDefault(4, 0L);
vo.refunded = grouped.getOrDefault(6, 0L);
return vo;
}
}
package com.liquidnet.service.goblin.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class GoblinStoreOrderRefundParam {
@ApiModelProperty(value = "订单id")
@NotNull(message = "订单id不能为空")
private String orderId;
@ApiModelProperty(value = "退款orderSkuId列表[发货后可选;不传为整单退款;发货前强制整单退款]")
private List<String> orderSkuIds;
@ApiModelProperty(value = "退款原因")
private String reason;
@ApiModelProperty(value = "详细描述")
private String describes;
}
......@@ -38,8 +38,8 @@ public interface IGoblinSqbService {
ResponseDto<Boolean> syncCouponStatus(String userId, String orderId);
/**
* 演出结束自动退款(定时任务调用)
* SQL 已联表过滤:收钱吧扩展单、主单已支付(2)、演出已结束、扩展未核销;循环内仅调 refund。
* 自动退款(定时任务调用)
* SQL 已联表过滤:收钱吧扩展单、未核销,且(主单已支付(2)+演出已结束)或(退款中(61)+超 8 小时);循环内调 refund。
*
* @param performancesId 演出ID,传 null 或空则不按演出缩小(全库符合条件候选,慎用)
* @return 处理结果摘要(成功/失败笔数)
......
package com.liquidnet.service.goblin.service.manage;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderDetailsVo;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import com.liquidnet.service.goblin.param.GoblinStoreOrderRefundParam;
import java.math.BigDecimal;
public interface IGoblinStoreBackOrderService {
ResponseDto<PageInfo<GoblinStoreBackOrderListVo>> orderBackList(Integer page,
ResponseDto<GoblinStoreBackOrderListPageVo> orderBackList(Integer page,
String orderBackCode,
Integer type,
String cst,
String cet,
String orderCode,
String spuName,
Integer status);
String status);
ResponseDto<GoblinBackOrderDetailsVo> orderDetails(String backOrderId);
......@@ -28,5 +26,7 @@ public interface IGoblinStoreBackOrderService {
ResponseDto<Boolean> refusedRefund(String backOrderId);
ResponseDto<Boolean> refundOrder(GoblinStoreOrderRefundParam param);
ResponseDto<Boolean> changeSkuRefund(String backOrderId, BigDecimal refundPrice, String orderSkuId);
}
package com.liquidnet.service.goblin.service.manage;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinOrderLogVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
public interface IGoblinStoreOrderService {
ResponseDto<PageInfo<GoblinStoreOrderListVo>> orderList(Integer page,
ResponseDto<GoblinStoreOrderListPageVo> orderList(Integer page,
String orderCode,
Integer type,
String cst,
......@@ -20,6 +17,14 @@ public interface IGoblinStoreOrderService {
String phone,
Integer status);
void exportOrderList(HttpServletResponse response,
String orderCode,
String cst,
String cet,
String expressContacts,
String phone,
Integer status);
ResponseDto<GoblinStoreOrderListVo> orderDetails(String orderId);
ResponseDto<Boolean> orderCancel(String orderId);
......@@ -32,8 +37,8 @@ public interface IGoblinStoreOrderService {
ResponseDto<Boolean> refundOrderSku(String orderId, String orderSkuId, BigDecimal price);
ResponseDto<Boolean> express(String orderId, String orderSkuIds,String mailNo,String uid,String orderCode);
ResponseDto<Boolean> express(String orderId, String orderSkuIds, String mailNo, String uid, String orderCode);
ResponseDto<Boolean> changeExpressMailNo(String orderId, String mailId,String mailNo);
ResponseDto<Boolean> changeExpressMailNo(String orderId, String mailId, String mailNo);
}
......@@ -6,6 +6,10 @@ public class KylinRedisConst {
public static final String PERFORMANCES_INVOICE_REMINDER = "kylin:performances:invoice_reminder:id:";
public static final String PERFORMANCES_NOTICE_REMIND_STATUS = "kylin:performances:noticeRemindStatus:id:";
public static final String PERFORMANCES_ARTISTS = "kylin:performances:artists:id:";
public static final String ARTIST_DETAIL = "kylin:artist:detail:id:";
public static final String ARTIST_ALBUM = "kylin:artist:album:id:";
public static final String ARTIST_PERFORMANCES = "kylin:artist:performances:id:";
public static final String ARTIST_PRODUCTS = "kylin:artist:products:id:";
public static final String PERFORMANCES_TRUE_NAME = "kylin:performances_true_name:id:";
public static final String PERFORMANCES_LIST_CITY = "kylin:performances:city:";
public static final String PERFORMANCES_LIST_SYSTEM_RECOMMEND = "kylin:performances:systemRecommend";
......
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("艺人关联商品保存参数")
public class ArtistProductSaveParam {
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("商品SPU ID列表")
private List<String> spuIds;
}
package com.liquidnet.service.kylin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 艺人关联商品搜索选项
*/
@Data
@ApiModel("艺人关联商品搜索选项")
public class ArtistGoodsOptionVo {
@ApiModelProperty("商品SPU ID")
private String spuId;
@ApiModelProperty("商品编码")
private String spuNo;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("封面图")
private String coverPic;
@ApiModelProperty("售价")
private BigDecimal sellPrice;
@ApiModelProperty("上架状态文案")
private String shelfStatusLabel;
@ApiModelProperty("是否可选择")
private Boolean selectable;
}
......@@ -40,9 +40,6 @@ public class ArtistVo {
@ApiModelProperty(value = "关联演出")
private List<PerformanceVo> performanceVoList;
@ApiModelProperty(value = "关联商品")
private List<ProductVo> productVoList;
@ApiModelProperty(value = "排序权重")
private Integer sort;
......@@ -79,7 +76,27 @@ public class ArtistVo {
@Data
@ApiModel("商品VO")
private static class ProductVo {
public static class ProductVo {
@ApiModelProperty("商品SPU ID")
private String spuId;
@ApiModelProperty("商品编码")
private String productCode;
@ApiModelProperty("商品名称")
private String productName;
@ApiModelProperty("商品头图")
private String imageUrl;
@ApiModelProperty("商品分类")
private String category;
@ApiModelProperty("商品售价")
private String price;
@ApiModelProperty("上架状态 1已上架 0未上架")
private Integer status;
}
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
@ApiModel("C端艺人详情")
public class KylinArtistDetailFrontVo implements Serializable {
@ApiModelProperty("艺人ID")
private String artistId;
@ApiModelProperty("艺人名称")
private String artistName;
@ApiModelProperty("艺人类型 1音乐人 2艺术家 3厂牌 4品牌方")
private Integer artistType;
@ApiModelProperty("艺人类型名称")
private String artistTypeName;
@ApiModelProperty("艺人头像")
private String avatarUrl;
@ApiModelProperty("艺人简介")
private String introduction;
@ApiModelProperty("艺人相册")
private List<String> albumImages;
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel("C端艺人关联演出")
public class KylinArtistPerformanceFrontVo implements Serializable {
@ApiModelProperty("演出ID")
private String performanceId;
@ApiModelProperty("演出名称")
private String title;
@ApiModelProperty("封面")
private String coverPic;
@ApiModelProperty("演出开始时间")
private String timeStart;
@ApiModelProperty("场地名称(演出地点)")
private String fieldName;
@ApiModelProperty("价格")
private String price;
@ApiModelProperty("状态 6购买 8售罄 9未开始")
private Integer appStatus;
@ApiModelProperty("状态文案")
private String statusName;
}
package com.liquidnet.service.kylin.dto.vo.returns;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ApiModel("C端艺人关联商品")
public class KylinArtistProductFrontVo implements Serializable {
@ApiModelProperty("商品SPU ID")
private String spuId;
@ApiModelProperty("商品类型 0-常规 1-数字藏品 2-券类商品")
private Integer spuType;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("封面图")
private String coverPic;
@ApiModelProperty("售价")
private BigDecimal sellPrice;
@ApiModelProperty("排序")
private Integer sort;
}
package com.liquidnet.service.kylin.service;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistDetailFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistPerformanceFrontVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinArtistProductFrontVo;
import java.util.List;
/**
* C端艺人详情
*/
public interface IKylinArtistFrontService {
KylinArtistDetailFrontVo getArtistDetail(String artistId);
List<String> getArtistAlbum(String artistId);
List<KylinArtistPerformanceFrontVo> getArtistPerformances(String artistId);
List<KylinArtistProductFrontVo> getArtistProducts(String artistId);
}
......@@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.dto.param.ArtistParam;
import com.liquidnet.service.kylin.dto.param.ArtistSearchParam;
import com.liquidnet.service.kylin.dto.vo.ArtistGoodsOptionVo;
import com.liquidnet.service.kylin.dto.vo.ArtistVo;
import com.liquidnet.service.kylin.entity.KylinArtist;
......@@ -32,4 +33,10 @@ public interface IKylinArtistService extends IService<KylinArtist> {
Boolean checkArtistNameExists(String artistName, String artistId);
List<ArtistGoodsOptionVo> searchGoods(String keyword, String artistId, List<String> excludeSpuIds);
List<ArtistVo.ProductVo> listArtistProducts(String artistId);
Boolean saveArtistProducts(String artistId, List<String> spuIds);
}
package com.liquidnet.service.goblin.constant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 中国民族(GB/T 3304),汉族置顶便于前端默认选中。
*/
public final class NationConst {
private NationConst() {
}
public static final List<String> NATIONS = Collections.unmodifiableList(Arrays.asList(
"汉族", "蒙古族", "回族", "藏族", "维吾尔族", "苗族", "彝族", "壮族", "布依族", "朝鲜族",
"满族", "侗族", "瑶族", "白族", "土家族", "哈尼族", "哈萨克族", "傣族", "黎族", "傈僳族",
"佤族", "畲族", "高山族", "拉祜族", "水族", "东乡族", "纳西族", "景颇族", "柯尔克孜族", "土族",
"达斡尔族", "仫佬族", "羌族", "布朗族", "撒拉族", "毛南族", "仡佬族", "锡伯族", "阿昌族", "普米族",
"塔吉克族", "怒族", "乌孜别克族", "俄罗斯族", "鄂温克族", "德昂族", "保安族", "裕固族", "京族", "塔塔尔族",
"独龙族", "鄂伦春族", "赫哲族", "门巴族", "珞巴族", "基诺族"
));
private static final Set<String> NATION_SET = Collections.unmodifiableSet(new HashSet<>(NATIONS));
public static boolean isValid(String nation) {
return nation != null && NATION_SET.contains(nation.trim());
}
}
......@@ -28,6 +28,8 @@ public class SmileVolunteersDetailsVo implements Cloneable {
private String idCard;
@ApiModelProperty(value = "性别", example = "")
private Integer sex;
@ApiModelProperty(value = "民族", example = "")
private String nation;
@ApiModelProperty(value = "审核状态", example = "")
private Integer status;
@ApiModelProperty(value = "学校", example = "")
......@@ -65,6 +67,7 @@ public class SmileVolunteersDetailsVo implements Cloneable {
this.setImg(source.getImg());
this.setIdCard(source.getIdCard());
this.setSex(source.getSex());
this.setNation(source.getNation());
this.setStatus(source.getStatus());
this.setSchool(source.getSchool());
this.setSchoolAddress(source.getSchoolAddress());
......
......@@ -36,6 +36,8 @@ public class SmileVolunteersApplyParam implements Serializable {
private String idCard;
@ApiModelProperty(value = "性别[0-未知|1-男|2-女]")
private Integer sex;
@ApiModelProperty(value = "民族,参见 /volunteers/nations")
private String nation;
@ApiModelProperty(value = "学校名称")
private String school;
@ApiModelProperty(value = "学校地址")
......
......@@ -18,4 +18,6 @@ public interface SmileVolunteersService {
ResponseDto<SmileVProjectVo> projectDetails(String uid, String projectId);
ResponseDto<Boolean> apply(SmileVolunteersApplyParam param);
ResponseDto<List<String>> nations();
}
package com.liquidnet.service.sweet.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.Map;
@ApiModel(value = "SweetFormSubmissionParam", description = "动态表单报名入参")
@Data
public class SweetFormSubmissionParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(position = 1, required = true, value = "活动名称/标识", example = "talent_2026_spring")
@NotBlank(message = "请填写活动名称")
private String activityName;
@ApiModelProperty(position = 2, required = true, value = "报名字段与值,key 为字段名")
@NotEmpty(message = "请填写报名信息")
private Map<String, Object> data;
}
package com.liquidnet.service.sweet.dto.param.admin;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "SweetFormSubmissionActivityListParam", description = "动态表单报名活动列表查询")
public class SweetFormSubmissionActivityListParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动标识(英文)")
private String activityName;
}
package com.liquidnet.service.sweet.dto.param.admin;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "SweetFormSubmissionExportParam", description = "动态表单报名导出")
public class SweetFormSubmissionExportParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动标识(英文)", required = true)
private String activityName;
@ApiModelProperty(value = "开始时间 yyyy-MM-dd HH:mm:ss")
private String beginTime;
@ApiModelProperty(value = "结束时间 yyyy-MM-dd HH:mm:ss")
private String endTime;
}
......@@ -4,16 +4,11 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.sweet.entity.SweetArtists;
import com.liquidnet.service.sweet.entity.SweetArtistsList;
import com.liquidnet.service.sweet.entity.SweetArtistsUrl;
import com.liquidnet.service.sweet.param.SweetArtistsListParam;
import com.liquidnet.service.sweet.vo.SweetArtistsVo;
import java.util.List;
/**
* <p>
* 艺人表 服务类
* 艺人表 服务类(读 kylin_artist,写已迁移至 admin)
* </p>
*
* @author liquidnet
......@@ -23,12 +18,6 @@ public interface ISweetArtistsService extends IService<SweetArtists> {
ResponseDto<PageInfo<SweetArtists>> getList(int page, int size, String name);
ResponseDto<Boolean> add(SweetArtistsListParam sweetArtistsList);
ResponseDto<SweetArtistsVo> detail(String artistsId);
ResponseDto<Boolean> change(String artistsId, SweetArtistsListParam sweetArtistsList);
ResponseDto<SweetArtists> del(String artistsId);
}
package com.liquidnet.service.sweet.service;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.sweet.dto.param.SweetFormSubmissionParam;
/**
* 动态表单报名
*/
public interface ISweetFormSubmissionService {
ResponseDto<Boolean> submit(SweetFormSubmissionParam param);
}
......@@ -47,7 +47,13 @@ public class CommonController
{
throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
}
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
int underscoreIndex = fileName.indexOf("_");
String realFileName;
if (underscoreIndex > 0) {
realFileName = System.currentTimeMillis() + fileName.substring(underscoreIndex + 1);
} else {
realFileName = fileName;
}
String filePath = RuoYiConfig.getDownloadPath() + fileName;
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
......
......@@ -11,7 +11,9 @@ import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.dto.param.ArtistParam;
import com.liquidnet.service.kylin.dto.param.ArtistProductSaveParam;
import com.liquidnet.service.kylin.dto.param.ArtistSearchParam;
import com.liquidnet.service.kylin.dto.vo.ArtistGoodsOptionVo;
import com.liquidnet.service.kylin.dto.vo.ArtistVo;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
import com.liquidnet.service.kylin.service.admin.IKylinArtistPerformanceService;
......@@ -24,6 +26,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
......@@ -305,4 +308,66 @@ public class KylinArtistController extends BaseController {
}
}
/**
* 关联商品页面
*/
@RequiresPermissions("kylin:artist:update")
@GetMapping("/products/{artistId}")
public String productsPage(@PathVariable("artistId") String artistId, ModelMap mmap) {
ArtistVo artistVo = kylinArtistService.detail(artistId);
mmap.put("artistId", artistId);
mmap.put("artistName", artistVo != null ? artistVo.getArtistName() : "");
return prefix + "/products";
}
/**
* 查询艺人已关联商品
*/
@RequiresPermissions("kylin:artist:update")
@GetMapping("/products/list")
@ResponseBody
public AjaxResult productsList(@RequestParam String artistId) {
return AjaxResult.success(kylinArtistService.listArtistProducts(artistId));
}
/**
* 保存艺人关联商品
*/
@Log(title = "保存艺人关联商品", businessType = BusinessType.UPDATE)
@RequiresPermissions("kylin:artist:update")
@PostMapping("/products/save")
@ResponseBody
public AjaxResult productsSave(@RequestBody ArtistProductSaveParam param) {
try {
Boolean result = kylinArtistService.saveArtistProducts(param.getArtistId(), param.getSpuIds());
if (result) {
return success("保存成功");
}
return error("艺人不存在");
} catch (IllegalArgumentException e) {
return error(e.getMessage());
}
}
/**
* 搜索可选商品(按名称)
*/
@GetMapping("/searchGoods")
@ResponseBody
public AjaxResult searchGoods(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "artistId", required = false) String artistId,
@RequestParam(value = "excludeSpuIds", required = false) String excludeSpuIds) {
List<String> excludeList = new ArrayList<>();
if (excludeSpuIds != null && !excludeSpuIds.isEmpty()) {
for (String spuId : excludeSpuIds.split(",")) {
if (spuId != null && !spuId.trim().isEmpty()) {
excludeList.add(spuId.trim());
}
}
}
List<ArtistGoodsOptionVo> list = kylinArtistService.searchGoods(keyword, artistId, excludeList);
return AjaxResult.success(list);
}
}
package com.liquidnet.client.admin.web.controller.zhengzai.sweet;
import com.github.pagehelper.PageHelper;
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.PageDomain;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.core.page.TableSupport;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.client.admin.common.utils.StringUtils;
import com.liquidnet.client.admin.zhengzai.sweet.service.ISweetFormSubmissionAdminService;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionActivityListParam;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionExportParam;
import com.liquidnet.service.sweet.dto.admin.SweetFormSubmissionActivityVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Api(tags = "动态表单报名活动管理")
@Controller
@RequestMapping("/sweet/formSubmission")
public class SweetFormSubmissionAdminController extends BaseController {
private String prefix = "zhengzai/sweet/formSubmission";
@Autowired
private ISweetFormSubmissionAdminService sweetFormSubmissionAdminService;
@GetMapping("listView")
@ApiOperation(value = "报名活动列表页面")
public String listView() {
return prefix + "/list";
}
@Log(title = "报名活动列表", businessType = BusinessType.LIST)
@RequiresPermissions("sweet:formSubmission:list")
@PostMapping("list")
@ApiOperation(value = "报名活动列表数据")
@ResponseBody
public TableDataInfo list(SweetFormSubmissionActivityListParam param) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (StringUtils.isNull(pageNum)) {
pageNum = 1;
}
if (StringUtils.isNull(pageSize)) {
pageSize = 10;
}
// 聚合查询排序在 SQL 中固定,忽略前端 orderByColumn 避免 PageHelper 追加无效字段
PageHelper.startPage(pageNum, pageSize);
List<SweetFormSubmissionActivityVo> list = sweetFormSubmissionAdminService.activityList(param);
return getDataTable(list);
}
@Log(title = "报名数据导出", businessType = BusinessType.EXPORT)
@RequiresPermissions("sweet:formSubmission:export")
@PostMapping("export")
@ApiOperation(value = "报名数据导出")
@ResponseBody
public AjaxResult export(SweetFormSubmissionExportParam param) {
return sweetFormSubmissionAdminService.export(param);
}
}
......@@ -3,8 +3,10 @@ package com.liquidnet.client.admin.web.controller.zhengzai.tools;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.utils.poi.ExcelUtil;
import com.liquidnet.client.admin.zhengzai.goblin.service.IGoblinCommonService;
import com.liquidnet.client.admin.zhengzai.kylin.dto.*;
import com.liquidnet.client.admin.zhengzai.kylin.service.IExportService;
import com.liquidnet.service.goblin.dto.GoblinStoreSearchDto;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
......@@ -13,7 +15,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.*;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("tools/export")
......@@ -22,6 +25,9 @@ public class ExportDataController extends BaseController {
@Autowired
private IExportService exportService;
@Autowired
private IGoblinCommonService goblinCommonService;
private String prefix = "zhengzai/financial";
......@@ -30,6 +36,17 @@ public class ExportDataController extends BaseController {
return prefix + "/export";
}
/**
* 店铺列表(财务导出用)
*/
@GetMapping("/store/list")
@ResponseBody
public AjaxResult storeList(String storeName) {
List<String> status = Arrays.asList("3", "4", "5");
List<GoblinStoreSearchDto> list = goblinCommonService.storeSearch(storeName, status);
return AjaxResult.success(list);
}
/**
* 导出订单明细
*
......@@ -77,7 +94,12 @@ public class ExportDataController extends BaseController {
}
private boolean timeIsNotNull(String beginTime, String endTime) {
return StringUtils.isNotBlank(beginTime) && StringUtils.isNotBlank(endTime) ? true : false;
return StringUtils.isNotBlank(beginTime) && StringUtils.isNotBlank(endTime);
}
private boolean hasHalfTime(String beginTime, String endTime) {
return (StringUtils.isNotBlank(beginTime) && StringUtils.isBlank(endTime))
|| (StringUtils.isBlank(beginTime) && StringUtils.isNotBlank(endTime));
}
......@@ -104,18 +126,20 @@ public class ExportDataController extends BaseController {
/**
* 导出商品订单
*
* @param beginTime
* @param endTime
* @return
* 店铺 / 下单时间 填了的条件全部 AND
*/
@PostMapping("/export/commodityOrder")
@ResponseBody
public AjaxResult exportCommodityOrder(String beginTime, String endTime) {
if (!timeIsNotNull(beginTime, endTime)) {
return error("开始时间和结束时间不能为空!");
public AjaxResult exportCommodityOrder(String beginTime, String endTime, String storeId) {
boolean hasStoreId = StringUtils.isNotBlank(storeId);
boolean hasOrderTime = timeIsNotNull(beginTime, endTime);
if (!hasStoreId && !hasOrderTime) {
return error("请至少填写店铺或下单时间其中一个条件!");
}
if (hasHalfTime(beginTime, endTime)) {
return error("下单时间请填写完整的开始和结束时间!");
}
List<OrderCommodityExportVo> list = exportService.exportCommodityOrder(beginTime, endTime);
List<OrderCommodityExportVo> list = exportService.exportCommodityOrder(beginTime, endTime, storeId);
if (list.size() == 0) {
return error("查无信息");
}
......
......@@ -127,6 +127,7 @@
var actions = [];
actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="$.operate.detail(\'' + row.artistId + '\')"><i class="fa fa-eye"></i>详情</a> ');
actions.push('<a class="btn btn-success btn-xs ' + updateFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.artistId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-primary btn-xs ' + updateFlag + '" href="javascript:void(0)" onclick="openProductModal(\'' + row.artistId + '\')"><i class="fa fa-shopping-bag"></i>关联商品</a> ');
actions.push('<a class="btn btn-warning btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="showOperationLog(\'' + row.artistId + '\')"><i class="fa fa-history"></i>操作日志</a> ');
return actions.join('');
}
......@@ -171,6 +172,21 @@
var url = prefix + '/operationLog/' + artistId;
$.modal.openTab("艺人操作记录", url);
}
function openProductModal(artistId) {
var url = prefix + '/products/' + artistId;
$.modal.openOptions({
title: '关联商品',
url: url,
width: '900',
height: '600',
btn: ['保存', '关闭'],
yes: function (index, layero) {
var iframeWin = layero.find('iframe')[0];
iframeWin.contentWindow.submitHandler(index, layero);
}
});
}
</script>
</body>
</html>
......@@ -282,54 +282,6 @@
暂无演出信息
</div>
</div>
<!-- 关联商品信息 -->
<div>
<div class="section-title">
艺人关联商品信息
<span th:if="*{productVoList != null}" style="font-size: 14px; color: #666; margin-left: 10px;">
(共<span th:text="*{productVoList.size()}">2</span>条)
</span>
</div>
<div class="table-container" th:if="*{productVoList != null and !productVoList.isEmpty()}">
<table class="custom-table">
<thead>
<tr>
<th width="15%">商品编码</th>
<th width="20%">商品名称</th>
<th width="15%">商品头图</th>
<th width="15%">商品分类</th>
<th width="15%">商品售价</th>
<th width="15%">上架状态</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${ArtistVo.productVoList}">
<td th:text="${product.productCode} ?: '--'">goods001</td>
<td th:text="${product.productName} ?: '--'">摩登天空T恤</td>
<td>
<div class="product-image" th:if="${product.imageUrl}">
<img th:src="${product.imageUrl}" alt="商品图片">
</div>
<span th:unless="${product.imageUrl}">--</span>
</td>
<td th:text="${product.category} ?: '--'">服饰</td>
<td th:text="${product.price} ?: '--'">99.00元</td>
<td>
<span th:if="${product.status == 1}" class="status-badge status-on">已上架</span>
<span th:if="${product.status == 0}" class="status-badge status-off">未上架</span>
<span th:if="${product.status != 1 and product.status != 0}" th:text="${product.status}">--</span>
</td>
</tr>
</tbody>
</table>
</div>
<div th:if="*{productVoList == null or productVoList.isEmpty()}" class="empty-data">
暂无关联商品信息
</div>
</div>
</div>
</div>
</div>
......
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('关联商品')"/>
<link rel="stylesheet" th:href="@{/ajax/libs/select2/select2.min.css}"/>
<style>
body { background: #fff; }
.page-wrap { padding: 16px; }
.section-title { font-weight: bold; margin: 16px 0 8px; }
.img-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
.select2-container { min-width: 380px; }
.add-row { margin-bottom: 16px; display: flex; align-items: center; gap: 10px; }
</style>
</head>
<body class="white-bg">
<div class="page-wrap">
<div class="section-title">
艺人:<span th:text="${artistName}">--</span>
<span style="font-size:12px;color:#999;margin-left:8px;" th:text="'ID: ' + ${artistId}"></span>
</div>
<div class="add-row">
<select id="goodsSelect" style="width:400px;">
<option value="">默认最近20条,输入名称搜索全部匹配</option>
</select>
<span style="color:#999;font-size:12px;">选中商品后自动添加到列表</span>
</div>
<div class="section-title">已关联商品</div>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>商品SPU ID</th>
<th>头图</th>
<th>商品名称</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="linkedProductsBody">
<tr id="linkedProductsEmptyRow">
<td colspan="5" style="text-align:center;color:#999;">暂无关联商品</td>
</tr>
</tbody>
</table>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:src="@{/ajax/libs/select2/select2.min.js}"></script>
<script th:inline="javascript">
var artistId = /*[[${artistId}]]*/ '';
var prefix = ctx + 'kylin/artist';
var linkedProducts = [];
$(function () {
initSelect2();
loadLinkedProducts();
});
function initSelect2() {
$('#goodsSelect').select2({
allowClear: true,
placeholder: '默认最近20条,输入名称搜索全部匹配',
minimumInputLength: 0,
ajax: {
url: prefix + '/searchGoods',
dataType: 'json',
delay: 300,
data: function (params) {
return {
keyword: params.term || '',
artistId: artistId,
excludeSpuIds: linkedProducts.map(function (item) { return item.spuId; }).join(',')
};
},
processResults: function (resp) {
var list = (resp && resp.data) ? resp.data : [];
return { results: list.map(function (item) {
var text = item.name || item.spuId;
if (item.spuNo) {
text += ' (' + item.spuNo + ')';
}
return { id: item.spuId, text: text, disabled: item.selectable === false, _raw: item };
})};
}
}
}).on('select2:select', function (e) {
var raw = e.params.data._raw;
if (raw && raw.selectable === false) {
$.modal.alertWarning('该商品不可关联');
$(this).val(null).trigger('change');
return;
}
if (raw) {
addLinkedProduct(raw);
}
$(this).val(null).trigger('change');
});
}
function loadLinkedProducts() {
$.get(prefix + '/products/list', { artistId: artistId }, function (resp) {
if (resp.code === 0 && resp.data) {
linkedProducts = resp.data.map(function (item) {
return {
spuId: item.spuId,
name: item.productName,
coverPic: item.imageUrl,
shelfStatusLabel: item.status === 1 ? '已上架' : '未上架'
};
});
renderLinkedProducts();
}
});
}
function renderLinkedProducts() {
var $body = $('#linkedProductsBody');
$body.empty();
if (!linkedProducts.length) {
$body.append('<tr><td colspan="5" style="text-align:center;color:#999;">暂无关联商品</td></tr>');
return;
}
linkedProducts.forEach(function (item, index) {
$body.append('<tr>' +
'<td>' + (item.spuId || '--') + '</td>' +
'<td>' + (item.coverPic ? '<img class="img-thumb" src="' + item.coverPic + '">' : '--') + '</td>' +
'<td>' + (item.name || '--') + '</td>' +
'<td>' + (item.shelfStatusLabel || '--') + '</td>' +
'<td><button type="button" class="btn btn-danger btn-xs" onclick="removeLinkedProduct(' + index + ')">移除</button></td>' +
'</tr>');
});
}
function addLinkedProduct(raw) {
if (!raw || !raw.spuId) {
return;
}
if (linkedProducts.some(function (item) { return item.spuId === raw.spuId; })) {
$.modal.alertWarning('该商品已在列表中');
return;
}
linkedProducts.push({
spuId: raw.spuId,
name: raw.name,
coverPic: raw.coverPic,
shelfStatusLabel: raw.shelfStatusLabel
});
renderLinkedProducts();
}
function removeLinkedProduct(index) {
linkedProducts.splice(index, 1);
renderLinkedProducts();
}
function submitHandler(index, layero) {
var spuIds = linkedProducts.map(function (item) { return item.spuId; });
$.ajax({
url: prefix + '/products/save',
type: 'post',
contentType: 'application/json',
data: JSON.stringify({ artistId: artistId, spuIds: spuIds }),
success: function (resp) {
if (resp.code === 0) {
if (parent && parent.$ && parent.$.modal) {
parent.$.modal.msgSuccess('保存成功');
if (parent.$.table) {
parent.$.table.refresh();
}
} else {
$.modal.msgSuccess('保存成功');
}
if (typeof index !== 'undefined') {
parent.layer.close(index);
} else {
$.modal.close();
}
} else {
$.modal.alertError(resp.msg || '保存失败');
}
}
});
}
</script>
</body>
</html>
......@@ -699,8 +699,10 @@
$('#selected-artists-container').html('');
// 打开弹窗
$('#associateArtistModal').modal('show');
// 加载初始艺人列表(含已关联状态)
loadAllArtistsForModal(timesId, true);
// 已绑定艺人从 getSessionArtists 初始化(含逻辑删除但仍关联的);可选列表走 getAllArtists
initSelectedArtistsFromSession(timesId, function () {
loadAllArtistsForModal(timesId);
});
}
// 防抖
......@@ -715,15 +717,45 @@
const debouncedSearch = debounce(function () {
const timesId = $('#associateArtistModal').data('timesId');
loadAllArtistsForModal(timesId, false);
loadAllArtistsForModal(timesId);
}, 300);
/** 从本场次已绑定阵容初始化已选艺人(不按 status 过滤) */
function initSelectedArtistsFromSession(timesId, callback) {
$.ajax({
url: ctx + "kylin/artist/getSessionArtists",
type: "GET",
data: {
performancesId: performancesId,
timesId: timesId
},
success: function (response) {
if (response.code === 0) {
selectedArtists = (response.data || [])
.map(function (a) {
return {
artistId: a.artistId,
artistName: a.artistName,
avatarUrl: a.avatarUrl,
sort: a.sort || 0
};
})
.sort(function (a, b) { return b.sort - a.sort; });
updateSelectedArtistsView();
}
if (callback) callback();
},
error: function () {
if (callback) callback();
}
});
}
/**
* 从后端加载艺人列表
* 从后端加载可选艺人列表(仅 status=1)
* @param {string} timesId
* @param {boolean} isInitialLoad 是否首次加载(用于初始化已选艺人)
*/
function loadAllArtistsForModal(timesId, isInitialLoad) {
function loadAllArtistsForModal(timesId) {
const keyword = $('#artist-search-input').val().trim();
// 显示加载中
......@@ -742,7 +774,7 @@
success: function (response) {
if (response.code === 0) {
currentModalArtists = response.data || [];
renderArtistsInModal(currentModalArtists, isInitialLoad);
renderArtistsInModal(currentModalArtists);
} else {
$('#modal-artist-list').html(
'<div class="modal-empty">加载艺人列表失败,请重试</div>'
......@@ -760,9 +792,8 @@
/**
* 渲染搜索结果列表
* @param {Array} artists
* @param {boolean} isInitialLoad
*/
function renderArtistsInModal(artists, isInitialLoad) {
function renderArtistsInModal(artists) {
const listContainer = $('#modal-artist-list');
if (!artists || artists.length === 0) {
......@@ -771,14 +802,6 @@
return;
}
// 首次加载时,从后端的 associated 字段初始化已选列表,并按 sort 降序排列(sort 越大越靠前)
if (isInitialLoad) {
selectedArtists = artists
.filter(a => a.associated)
.map(a => ({ artistId: a.artistId, artistName: a.artistName, avatarUrl: a.avatarUrl, sort: a.sort || 0 }))
.sort((a, b) => b.sort - a.sort);
}
let html = '';
artists.forEach(function (artist) {
const isSelected = selectedArtists.some(a => a.artistId === artist.artistId);
......
......@@ -47,6 +47,12 @@
<input name="sex" class="form-control" type="text" th:if="*{sex==2}" th:value="女" readonly required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">民族:</label>
<div class="col-sm-8">
<input name="nation" class="form-control" type="text" th:value="*{nation}" readonly required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">学校:</label>
<div class="col-sm-8">
......
......@@ -107,6 +107,10 @@
field: 'sex',
title: '性别'
},
{
field: 'nation',
title: '民族'
},
{
field: 'phone',
title: '手机号'
......
<!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>
活动:<select name="activityName" th:with="type=${@dict.getType('sweet_form_activity')}">
<option value="">全部</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}"
th:value="${dict.dictValue}"></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-bordered">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:inline="javascript">
var exportFlag = [[${@permission.hasPermi('sweet:formSubmission:export')}]];
var prefix = ctx + "sweet/formSubmission";
function formatDateTime(value) {
return $.common.sprintf("<span>%s</span>", null != value ? value.replace("T", " ").substring(0, 19) : value);
}
function exportAll(activityName) {
doExport({activityName: activityName});
}
function exportByTime(activityName) {
var html = '<div style="padding: 20px;">' +
'<div class="form-group">' +
'<label>开始时间:</label>' +
'<input type="text" class="time-input form-control" id="exportBeginTime" placeholder="yyyy-MM-dd HH:mm:ss"/>' +
'</div>' +
'<div class="form-group">' +
'<label>结束时间:</label>' +
'<input type="text" class="time-input form-control" id="exportEndTime" placeholder="yyyy-MM-dd HH:mm:ss"/>' +
'</div>' +
'</div>';
layer.open({
type: 1,
title: '按时间导出',
area: ['420px', '260px'],
content: html,
btn: ['导出', '取消'],
success: function () {
layui.use('laydate', function () {
var laydate = layui.laydate;
laydate.render({elem: '#exportBeginTime', type: 'datetime', trigger: 'click'});
laydate.render({elem: '#exportEndTime', type: 'datetime', trigger: 'click'});
});
},
yes: function (index) {
var beginTime = $('#exportBeginTime').val();
var endTime = $('#exportEndTime').val();
if (!beginTime && !endTime) {
$.modal.msgWarning('请至少填写一个时间');
return;
}
layer.close(index);
doExport({activityName: activityName, beginTime: beginTime, endTime: endTime});
}
});
}
function doExport(data) {
$.modal.loading("正在导出数据,请稍后...");
$.post(prefix + "/export", data, function (result) {
$.modal.closeLoading();
if (result.code == web_status.SUCCESS) {
window.location.href = ctx + "common/download?fileName=" + encodeURI(result.msg) + "&delete=" + true;
} else {
$.modal.alertError(result.msg);
}
});
}
$(function () {
var options = {
url: prefix + "/list",
modalName: "报名活动",
sortable: false,
columns: [
{
field: 'activityName',
title: '活动标识(英文)'
},
{
field: 'activityLabel',
title: '活动名称(中文)',
formatter: function (value, row) {
return value || row.activityName;
}
},
{
field: 'submissionCount',
title: '报名数量'
},
{
field: 'firstCreatedAt',
title: '最早提交',
formatter: function (value) {
return formatDateTime(value);
}
},
{
field: 'lastCreatedAt',
title: '最新提交',
formatter: function (value) {
return formatDateTime(value);
}
},
{
title: '操作',
align: 'center',
formatter: function (value, row) {
var actions = [];
actions.push('<a class="btn btn-warning btn-xs ' + exportFlag + '" href="javascript:void(0)" onclick="exportAll(\'' + row.activityName + '\')"><i class="fa fa-download"></i> 全部导出</a>');
actions.push('<a class="btn btn-info btn-xs ' + exportFlag + '" href="javascript:void(0)" onclick="exportByTime(\'' + row.activityName + '\')"><i class="fa fa-clock-o"></i> 按时间导出</a>');
return actions.join(' ');
}
}
]
};
$.table.init(options);
});
</script>
</body>
</html>
......@@ -46,10 +46,15 @@ public class GoblinCommonServiceImpl implements IGoblinCommonService {
@Override
public List<GoblinStoreSearchDto> storeSearch(String name, List<String> status) {
List<GoblinStoreInfo> list = goblinStoreInfoMapper.selectList(Wrappers.lambdaQuery(GoblinStoreInfo.class)
.like(GoblinStoreInfo::getStoreName, name)
LambdaQueryWrapper<GoblinStoreInfo> queryWrapper = Wrappers.lambdaQuery(GoblinStoreInfo.class);
if (StringUtil.isNotBlank(name)) {
queryWrapper.like(GoblinStoreInfo::getStoreName, name);
}
queryWrapper.eq(GoblinStoreInfo::getDelFlg, "0")
.in(GoblinStoreInfo::getStatus, status)
.select(GoblinStoreInfo::getStoreId, GoblinStoreInfo::getStoreName, GoblinStoreInfo::getStatus));
.orderByAsc(GoblinStoreInfo::getStoreName)
.select(GoblinStoreInfo::getStoreId, GoblinStoreInfo::getStoreName, GoblinStoreInfo::getStatus);
List<GoblinStoreInfo> list = goblinStoreInfoMapper.selectList(queryWrapper);
List<GoblinStoreSearchDto> dtoList = new ArrayList<>();
for (GoblinStoreInfo item : list) {
GoblinStoreSearchDto dto = GoblinStoreSearchDto.getNew();
......
......@@ -11,6 +11,8 @@ public class OrderCommodityExportVo implements Serializable, Cloneable {
@Excel(name = "商户订单号", cellType = Excel.ColumnType.STRING)
private String code;
@Excel(name = "店铺名称", cellType = Excel.ColumnType.STRING)
private String storeName;
@Excel(name = "微信/支付宝订单号", cellType = Excel.ColumnType.STRING)
private String paymentId;
@Excel(name = "商品名称", cellType = Excel.ColumnType.STRING)
......@@ -53,6 +55,7 @@ public class OrderCommodityExportVo implements Serializable, Cloneable {
this.setPriceRefund(source.getPriceRefund());
this.setStatus(source.getStatus());
this.setRefundAt(source.getRefundAt());
this.setStoreName(source.getStoreName());
return this;
}
......
......@@ -23,5 +23,5 @@ public interface IExportService {
List<OrderMemberExportVo> exportMemberOrder(String beginTime, String endTime);
//导出商品订单信息
List<OrderCommodityExportVo> exportCommodityOrder(String beginTime, String endTime);
List<OrderCommodityExportVo> exportCommodityOrder(String beginTime, String endTime, String storeId);
}
......@@ -8,13 +8,13 @@ import com.liquidnet.service.kylin.dao.MemberOrderExportDao;
import com.liquidnet.service.kylin.dao.OrderExportDao;
import com.liquidnet.service.kylin.mapper.KylinPerformancesMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
......@@ -110,12 +110,12 @@ public class ExportServiceImpl implements IExportService {
}
@Override
public List<OrderCommodityExportVo> exportCommodityOrder(String beginTime, String endTime) {
public List<OrderCommodityExportVo> exportCommodityOrder(String beginTime, String endTime, String storeId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date beginDate = sdf.parse(beginTime);
Date endDate = sdf.parse(endTime);
List<CommodityOrderExportDao> list = performancesMapper.exportCommodityOrder(beginDate, endDate);
Date beginDate = parseDate(sdf, beginTime);
Date endDate = parseDate(sdf, endTime);
List<CommodityOrderExportDao> list = performancesMapper.exportCommodityOrder(beginDate, endDate, storeId);
List<OrderCommodityExportVo> voList = new ArrayList();
for (CommodityOrderExportDao item : list) {
voList.add(OrderCommodityExportVo.getNew().copyCommodityOrderExportVo(item));
......@@ -126,4 +126,11 @@ public class ExportServiceImpl implements IExportService {
throw new BusinessException("导出Excel失败,请联系网站管理员!");
}
}
private Date parseDate(SimpleDateFormat sdf, String time) throws ParseException {
if (StringUtils.isBlank(time)) {
return null;
}
return sdf.parse(time);
}
}
......@@ -14,10 +14,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.HashMap;
import java.util.stream.Collectors;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
......@@ -79,6 +79,7 @@ public class KylinArtistPerformanceServiceImpl extends ServiceImpl<KylinArtistPe
// 删除缓存演出阵容
dataUtils.delPerformanceArtists(performanceId);
dataUtils.delArtistPerformancesCache(artistId);
int result = artistPerformanceMapper.deleteById(mid);
// 记录删除后的关联数量
......@@ -95,8 +96,9 @@ public class KylinArtistPerformanceServiceImpl extends ServiceImpl<KylinArtistPe
@Override
public List<KylinArtistAssociationStatusDto> getAllArtists(String performancesId, String timesId, String keyword) {
// 1. 获取所有艺人
// 1. 可选艺人:仅启用中(已逻辑删除的不出现在选艺人列表;已绑定展示走 getSessionArtists)
List<KylinArtist> allArtists = artistMapper.selectList(new QueryWrapper<KylinArtist>()
.eq("status", 1)
.like(keyword != null && !keyword.isEmpty(), "artist_name", keyword)
.orderByDesc("created_at"));
......@@ -108,7 +110,7 @@ public class KylinArtistPerformanceServiceImpl extends ServiceImpl<KylinArtistPe
.collect(Collectors.toMap(
KylinArtistPerformanceDao::getArtistId,
KylinArtistPerformanceDao::getSort,
(existing, replacement) -> existing // 重复 key 保留已有值
(existing, replacement) -> existing
));
// 3. 组装返回结果
......@@ -190,6 +192,9 @@ public class KylinArtistPerformanceServiceImpl extends ServiceImpl<KylinArtistPe
// 删除缓存演出阵容
dataUtils.delPerformanceArtists(performancesId);
for (String artistId : affectedArtistIds) {
dataUtils.delArtistPerformancesCache(artistId);
}
return true;
}
}
......@@ -631,4 +631,39 @@ public class DataUtils {
final String redisKey = KylinRedisConst.PERFORMANCES_ARTISTS + performancesId;
redisDataSourceUtil.getRedisKylinUtil().del(redisKey);
}
/**
* 删除艺人 C 端缓存(详情/相册/演出/商品)
*/
public void delArtistCache(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return;
}
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_DETAIL + artistId);
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_ALBUM + artistId);
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_PERFORMANCES + artistId);
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_PRODUCTS + artistId);
}
public void delArtistDetailAndAlbumCache(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return;
}
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_DETAIL + artistId);
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_ALBUM + artistId);
}
public void delArtistPerformancesCache(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return;
}
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_PERFORMANCES + artistId);
}
public void delArtistProductsCache(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return;
}
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.ARTIST_PRODUCTS + artistId);
}
}
package com.liquidnet.client.admin.zhengzai.kylin.utils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.service.sweet.constant.SweetConstant;
import com.liquidnet.service.sweet.entity.SweetManualArtists;
import com.liquidnet.service.sweet.entity.SweetManualArtistsFive;
import com.liquidnet.service.sweet.entity.SweetManualArtistsMdsk;
import com.liquidnet.service.sweet.entity.SweetManualArtistsTfc;
import com.liquidnet.service.sweet.mapper.SweetManualArtistsFiveMapper;
import com.liquidnet.service.sweet.mapper.SweetManualArtistsMapper;
import com.liquidnet.service.sweet.mapper.SweetManualArtistsMdskMapper;
import com.liquidnet.service.sweet.mapper.SweetManualArtistsTfcMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* admin 修改 kylin 艺人后,失效 sweet 小程序相关 Redis 缓存
*/
@Slf4j
@Component
public class SweetArtistCacheUtils {
@Autowired
private RedisDataSourceUtil redisDataSourceUtil;
@Autowired
private SweetManualArtistsMapper sweetManualArtistsMapper;
@Autowired
private SweetManualArtistsMdskMapper sweetManualArtistsMdskMapper;
@Autowired
private SweetManualArtistsTfcMapper sweetManualArtistsTfcMapper;
@Autowired
private SweetManualArtistsFiveMapper sweetManualArtistsFiveMapper;
public void invalidateByArtistId(String artistId) {
if (artistId == null || artistId.isEmpty()) {
return;
}
try {
redisDataSourceUtil.getRedisSweetUtil().del(SweetConstant.REDIS_KEY_SWEET_ARTISTS_DETAILS.concat(artistId));
invalidateManualTimeList(
sweetManualArtistsMapper.selectList(
Wrappers.lambdaQuery(SweetManualArtists.class).eq(SweetManualArtists::getArtistsId, artistId)
).stream().map(SweetManualArtists::getManualId).collect(Collectors.toSet()),
SweetConstant.REDIS_KEY_SWEET_MANUAL_TIME_LIST
);
invalidateManualTimeList(
sweetManualArtistsMdskMapper.selectList(
Wrappers.lambdaQuery(SweetManualArtistsMdsk.class).eq(SweetManualArtistsMdsk::getArtistsId, artistId)
).stream().map(SweetManualArtistsMdsk::getManualId).collect(Collectors.toSet()),
SweetConstant.REDIS_KEY_SWEET_MDSK_MANUAL_TIME_LIST
);
invalidateManualTimeList(
sweetManualArtistsTfcMapper.selectList(
Wrappers.lambdaQuery(SweetManualArtistsTfc.class).eq(SweetManualArtistsTfc::getArtistsId, artistId)
).stream().map(SweetManualArtistsTfc::getManualId).collect(Collectors.toSet()),
SweetConstant.REDIS_KEY_SWEET_TFC_MANUAL_TIME_LIST
);
invalidateManualTimeList(
sweetManualArtistsFiveMapper.selectList(
Wrappers.lambdaQuery(SweetManualArtistsFive.class).eq(SweetManualArtistsFive::getArtistsId, artistId)
).stream().map(SweetManualArtistsFive::getManualId).collect(Collectors.toSet()),
SweetConstant.REDIS_KEY_SWEET_FIVE_MANUAL_TIME_LIST
);
} catch (Exception e) {
log.error("invalidate sweet artist cache failed, artistId={}", artistId, e);
}
}
private void invalidateManualTimeList(Set<String> manualIds, String keyPrefix) {
if (manualIds == null || manualIds.isEmpty()) {
return;
}
for (String manualId : new HashSet<>(manualIds)) {
if (manualId != null && !manualId.isEmpty()) {
redisDataSourceUtil.getRedisSweetUtil().del(keyPrefix.concat(manualId));
}
}
}
}
......@@ -16,6 +16,8 @@ public class VolunteersExportVo implements Serializable, Cloneable{
private String idCard;
@Excel(name = "性别", cellType = Excel.ColumnType.STRING)
private String sex;
@Excel(name = "民族", cellType = Excel.ColumnType.STRING)
private String nation;
@Excel(name = "审核状态", cellType = Excel.ColumnType.STRING)
private String status;
@Excel(name = "学校", cellType = Excel.ColumnType.STRING)
......@@ -56,6 +58,7 @@ public class VolunteersExportVo implements Serializable, Cloneable{
this.setName(source.getName());
this.setIdCard(source.getIdCard());
this.setSex(source.getSex());
this.setNation(source.getNation());
this.setStatus(source.getStatus());
this.setSchool(source.getSchool());
// this.setSchoolAddress(source.getSchoolAddress());
......
package com.liquidnet.client.admin.zhengzai.sweet.service;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionActivityListParam;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionExportParam;
import com.liquidnet.service.sweet.dto.admin.SweetFormSubmissionActivityVo;
import java.util.List;
public interface ISweetFormSubmissionAdminService {
List<SweetFormSubmissionActivityVo> activityList(SweetFormSubmissionActivityListParam param);
AjaxResult export(SweetFormSubmissionExportParam param);
}
package com.liquidnet.client.admin.zhengzai.sweet.service.impl;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.domain.entity.SysDictData;
import com.liquidnet.client.admin.common.utils.DictUtils;
import com.liquidnet.client.admin.common.utils.StringUtils;
import com.liquidnet.client.admin.zhengzai.sweet.service.ISweetFormSubmissionAdminService;
import com.liquidnet.client.admin.zhengzai.sweet.utils.SweetFormSubmissionExcelUtils;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionActivityListParam;
import com.liquidnet.service.sweet.dto.param.admin.SweetFormSubmissionExportParam;
import com.liquidnet.service.sweet.dto.admin.SweetFormSubmissionActivityVo;
import com.liquidnet.service.sweet.entity.SweetFormSubmission;
import com.liquidnet.service.sweet.mapper.SweetFormSubmissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SweetFormSubmissionAdminServiceImpl implements ISweetFormSubmissionAdminService {
public static final String DICT_TYPE_SWEET_FORM_ACTIVITY = "sweet_form_activity";
@Autowired
private SweetFormSubmissionMapper sweetFormSubmissionMapper;
@Override
public List<SweetFormSubmissionActivityVo> activityList(SweetFormSubmissionActivityListParam param) {
String activityName = StringUtils.isEmpty(param.getActivityName()) ? null : param.getActivityName();
List<SweetFormSubmissionActivityVo> list = sweetFormSubmissionMapper.selectActivityGroupList(activityName);
for (SweetFormSubmissionActivityVo vo : list) {
vo.setActivityLabel(getActivityLabel(vo.getActivityName()));
}
return list;
}
private String getActivityLabel(String activityName) {
if (StringUtils.isEmpty(activityName)) {
return "";
}
List<SysDictData> datas = DictUtils.getDictCache(DICT_TYPE_SWEET_FORM_ACTIVITY);
if (datas != null) {
for (SysDictData dict : datas) {
if (activityName.equals(dict.getDictValue())) {
return dict.getDictLabel();
}
}
}
return activityName;
}
@Override
public AjaxResult export(SweetFormSubmissionExportParam param) {
if (StringUtils.isEmpty(param.getActivityName())) {
return AjaxResult.error("请选择活动");
}
String beginTime = StringUtils.isEmpty(param.getBeginTime()) ? null : param.getBeginTime();
String endTime = StringUtils.isEmpty(param.getEndTime()) ? null : param.getEndTime();
List<SweetFormSubmission> submissions = sweetFormSubmissionMapper.selectForExport(
param.getActivityName(), beginTime, endTime);
String activityLabel = getActivityLabel(param.getActivityName());
String exportName = SweetFormSubmissionExcelUtils.buildExportName(activityLabel, beginTime, endTime);
String filename = SweetFormSubmissionExcelUtils.export(submissions, exportName);
return AjaxResult.success(filename);
}
}
package com.liquidnet.client.admin.zhengzai.sweet.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.liquidnet.client.admin.common.config.RuoYiConfig;
import com.liquidnet.client.admin.common.exception.BusinessException;
import com.liquidnet.client.admin.common.utils.StringUtils;
import com.liquidnet.service.sweet.entity.SweetFormSubmission;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileOutputStream;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 动态表单报名 Excel 导出
*/
public class SweetFormSubmissionExcelUtils {
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final String COL_CREATED_AT = "提交时间";
private SweetFormSubmissionExcelUtils() {
}
public static String export(List<SweetFormSubmission> submissions, String exportName) {
if (submissions == null || submissions.isEmpty()) {
throw new BusinessException("查无报名信息");
}
List<Map<String, String>> rows = new ArrayList<>();
Set<String> dataKeys = new LinkedHashSet<>();
for (SweetFormSubmission submission : submissions) {
Map<String, String> row = new LinkedHashMap<>();
row.put(COL_CREATED_AT, submission.getCreatedAt() == null ? "" : submission.getCreatedAt().format(DTF));
parseDataJson(submission.getData(), row, dataKeys);
rows.add(row);
}
List<String> headers = new ArrayList<>();
headers.add(COL_CREATED_AT);
headers.addAll(dataKeys);
String sheetName = sanitizeSheetName(exportName);
XSSFWorkbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet(sheetName);
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.size(); i++) {
headerRow.createCell(i).setCellValue(headers.get(i));
}
int rowNum = 1;
for (Map<String, String> rowData : rows) {
Row row = sheet.createRow(rowNum++);
for (int i = 0; i < headers.size(); i++) {
String value = rowData.get(headers.get(i));
row.createCell(i).setCellValue(value == null ? "" : value);
}
}
String filename = sanitizeFilename(exportName) + ".xlsx";
String absolutePath = RuoYiConfig.getDownloadPath() + filename;
File desc = new File(absolutePath);
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
try (FileOutputStream out = new FileOutputStream(desc)) {
workbook.write(out);
workbook.close();
} catch (Exception e) {
throw new BusinessException("导出Excel失败,请联系网站管理员!");
}
return filename;
}
public static String buildExportName(String activityLabel, String beginTime, String endTime) {
StringBuilder name = new StringBuilder(activityLabel).append("报名数据");
if (StringUtils.isNotEmpty(beginTime) || StringUtils.isNotEmpty(endTime)) {
name.append("_");
if (StringUtils.isNotEmpty(beginTime)) {
name.append(normalizeTimeForName(beginTime));
}
name.append("-");
if (StringUtils.isNotEmpty(endTime)) {
name.append(normalizeTimeForName(endTime));
}
}
return name.toString();
}
private static String normalizeTimeForName(String time) {
return time.trim().replace(":", "").replace(" ", "_");
}
private static String sanitizeSheetName(String name) {
if (StringUtils.isEmpty(name)) {
return "报名数据";
}
String sanitized = name.replaceAll("[\\\\/?*\\[\\]:]", "_");
return sanitized.length() > 31 ? sanitized.substring(0, 31) : sanitized;
}
private static String sanitizeFilename(String name) {
if (StringUtils.isEmpty(name)) {
return "报名数据";
}
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
}
private static void parseDataJson(String dataJson, Map<String, String> row, Set<String> dataKeys) {
if (dataJson == null || dataJson.trim().isEmpty()) {
return;
}
try {
JSONObject jsonObject = JSON.parseObject(dataJson, Feature.OrderedField);
if (jsonObject == null) {
return;
}
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
String key = entry.getKey();
if (!dataKeys.contains(key)) {
dataKeys.add(key);
}
row.put(key, formatValue(entry.getValue()));
}
} catch (Exception ignored) {
if (!dataKeys.contains("data")) {
dataKeys.add("data");
}
row.put("data", dataJson);
}
}
private static String formatValue(Object value) {
if (value == null) {
return "";
}
if (value instanceof Map || value instanceof List) {
return JSON.toJSONString(value);
}
return String.valueOf(value);
}
}
......@@ -119,7 +119,7 @@ public class LnsRegex {
/**
* 支持的第三方账号平台类型(用户中心:登录注册)
*/
public static final String TRIPLE_PF_FOR_ULGOIN = "\\b(WEIBO|WECHAT|QQ)\\b";
public static final String TRIPLE_PF_FOR_ULGOIN = "\\b(WEIBO|WECHAT|QQ|NIA)\\b";
/**
* 支持的支付终端
*/
......
......@@ -57,6 +57,9 @@ public class RedisKeyExpireConst {
// 演出关联阵容缓存过期时间
public static final long PERFORMANCES_ARTISTS_EXPIRE = 30 * 24 * 60 * 60;
// C端艺人详情相关缓存过期时间
public static final long ARTIST_FRONT_CACHE_EXPIRE = 30 * 24 * 60 * 60;
/**
* 演出关联收钱吧商品缓存过期时间 (30天)
*/
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-third</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>liquidnet-common-third-secure-access</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-base</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.anicert.module</groupId>
<artifactId>anicert-sign-bouncycastle</artifactId>
<version>3.2.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/anicert-sign-bouncycastle-3.2.0.jar</systemPath>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-setting</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-log</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SARequestPackageVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object bizPackage;
private String customerNo;
private String forwardUrl;
public SARequestPackageVO() {
}
public SARequestPackageVO(Object bizPackage, String customerNo, String forwardUrl) {
this.bizPackage = bizPackage;
this.customerNo = customerNo;
this.forwardUrl = forwardUrl;
}
public Object getBizPackage() {
return bizPackage;
}
public void setBizPackage(Object bizPackage) {
this.bizPackage = bizPackage;
}
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getForwardUrl() {
return forwardUrl;
}
public void setForwardUrl(String forwardUrl) {
this.forwardUrl = forwardUrl;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SARequestVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object requestPackage;
private String requestSign;
public SARequestVO() {
}
public SARequestVO(Object requestPackage, String requestSign) {
this.requestPackage = requestPackage;
this.requestSign = requestSign;
}
public Object getRequestPackage() {
return requestPackage;
}
public void setRequestPackage(Object requestPackage) {
this.requestPackage = requestPackage;
}
public String getRequestSign() {
return requestSign;
}
public void setRequestSign(String requestSign) {
this.requestSign = requestSign;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SAResponseVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object responsePackage;
private String responseSign;
public SAResponseVO() {
}
public SAResponseVO(Object responsePackage, String responseSign) {
this.responsePackage = responsePackage;
this.responseSign = responseSign;
}
public Object getResponsePackage() {
return responsePackage;
}
public void setResponsePackage(Object responsePackage) {
this.responsePackage = responsePackage;
}
public String getResponseSign() {
return responseSign;
}
public void setResponseSign(String responseSign) {
this.responseSign = responseSign;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SASignRequestPackageVO implements Serializable {
private static final long serialVersionUID = 1L;
private String customerNo;
private String originalData;
public SASignRequestPackageVO() {
}
public SASignRequestPackageVO(String customerNo, String originalData) {
this.customerNo = customerNo;
this.originalData = originalData;
}
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getOriginalData() {
return originalData;
}
public void setOriginalData(String originalData) {
this.originalData = originalData;
}
}
package cn.anicert.secure.access.config;
import cn.anicert.secure.access.config.bean.AccessConfig;
import cn.anicert.secure.access.enums.Configs;
import cn.anicert.secure.access.exceptions.SecureAccessConfigException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import cn.hutool.setting.Setting;
import cn.hutool.setting.SettingUtil;
import java.util.Arrays;
public class ConfigReader {
private final static String CONFIG_FILE_NAME = "secure-access.setting";
private static Setting setting;
public static AccessConfig readConfig(String... filePath) {
if (filePath == null || filePath.length == 0) {
setting = SettingUtil.getFirstFound(CONFIG_FILE_NAME);
} else {
setting = SettingUtil.getFirstFound(filePath);
}
StaticLog.info("[安全接入平台]当前配置文件路径为:{}", setting.getSettingPath());
StaticLog.info("[安全接入平台]开始读取配置文件…………");
AccessConfig accessConfig = new AccessConfig();
Arrays.stream(Configs.values()).forEach(config -> {
if (config.isNecessary()) {
if (setting.containsKey(config.getKey())) {
String value = setting.getStr(config.getKey());
if (StrUtil.isBlank(value)) {
StaticLog.error("[安全接入平台]{}未配置", config.getName());
throwConfigException();
}
} else {
StaticLog.error("[安全接入平台]{}未配置", config.getName());
throwConfigException();
}
}
});
setting.autoLoad(true);
setting.toBean(accessConfig);
StaticLog.info("[安全接入平台]读取配置文件结束");
return accessConfig;
}
public static AccessConfig writeConfig(String key, String value) {
setting.set(key, value);
setting.store();
AccessConfig accessConfig = new AccessConfig();
return setting.toBean(accessConfig);
}
private static void throwConfigException() {
throw new SecureAccessConfigException("[安全接入平台]读取配置文件失败");
}
}
package cn.anicert.secure.access.config.bean;
public class AccessConfig {
//配置部分
private String orgId;
private String customerNo;
private String accessUrl;
private String signPrivateKey;
private String signVerifyPublicKey;
private String dataEncryptKey;
//请求部分
private int connectionRequestTimeout = 50000;
private int responseTimeout = 30000;
private int poolMaxConn = 100;
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getAccessUrl() {
return accessUrl;
}
public void setAccessUrl(String accessUrl) {
this.accessUrl = accessUrl;
}
public String getSignPrivateKey() {
return signPrivateKey;
}
public void setSignPrivateKey(String signPrivateKey) {
this.signPrivateKey = signPrivateKey;
}
public String getSignVerifyPublicKey() {
return signVerifyPublicKey;
}
public void setSignVerifyPublicKey(String signVerifyPublicKey) {
this.signVerifyPublicKey = signVerifyPublicKey;
}
public String getDataEncryptKey() {
return dataEncryptKey;
}
public void setDataEncryptKey(String dataEncryptKey) {
this.dataEncryptKey = dataEncryptKey;
}
public int getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public int getResponseTimeout() {
return responseTimeout;
}
public void setResponseTimeout(int responseTimeout) {
this.responseTimeout = responseTimeout;
}
public int getPoolMaxConn() {
return poolMaxConn;
}
public void setPoolMaxConn(int poolMaxConn) {
this.poolMaxConn = poolMaxConn;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
}
package cn.anicert.secure.access.constants;
public class InterfaceConstant {
private static final String BASE_URL = "/secureaccess/interf";
public static final String UNIFIED_AUTH_URL = BASE_URL + "/unified_auth/request";
public static final String P7_SIGN_URL = BASE_URL + "/pkcs7/signDataByP7";
}
package cn.anicert.secure.access.core;
import cn.anicert.secure.access.bean.SAResponseVO;
import cn.anicert.secure.access.constants.InterfaceConstant;
import cn.anicert.secure.access.utils.ConfigUtil;
import cn.anicert.secure.access.utils.HttpUtil;
import cn.anicert.secure.access.utils.JSONUtil;
import cn.anicert.secure.access.utils.SignUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
public class SecureAccess {
public static void init(String... configPath) {
ConfigUtil.initConfig(configPath);
}
public static String unified_auth(String bizSeq, String reqStr) {
String url = ConfigUtil.getConfig().getAccessUrl() + InterfaceConstant.UNIFIED_AUTH_URL;
String responseStr = sendReq(bizSeq, url, reqStr);
if (StrUtil.isNotBlank(responseStr) && verifySign(bizSeq, responseStr)) {
return dealResult(bizSeq, responseStr);
}
return "";
}
public static String pkcs7Sign(String bizSeq, String reqStr) {
String url = ConfigUtil.getConfig().getAccessUrl() + InterfaceConstant.P7_SIGN_URL;
String responseStr = sendReq(bizSeq, url, reqStr);
if (StrUtil.isNotBlank(responseStr) && verifySign(bizSeq, responseStr)) {
return dealResult(bizSeq, responseStr);
}
return "";
}
private static String sendReq(String bizSeq, String url, String reqStr) {
try {
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求开始", bizSeq);
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求数据--[{}]", bizSeq, reqStr);
String responseStr = HttpUtil.getInstance().postRequest(url, reqStr);
if (StrUtil.isNotBlank(responseStr)) {
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求结果--[{}]", bizSeq, responseStr);
return responseStr;
} else {
StaticLog.error("[安全接入平台],bizSeq--[{}]--请求返回结果为空", bizSeq);
}
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--请求失败", bizSeq);
}
return "";
}
private static boolean verifySign(String bizSeq, String result) {
try {
SAResponseVO responseVO = JSONUtil.json2Object(result, SAResponseVO.class);
if (StrUtil.isNotBlank(responseVO.getResponseSign())) {
String signStr = JSONUtil.toJson(responseVO.getResponsePackage());
boolean verifyResult = SignUtil.verifySign(signStr, responseVO.getResponseSign());
if (!verifyResult) {
StaticLog.error("[安全接入平台],bizSeq--[{}]--验签不匹配", bizSeq);
}
return verifyResult;
} else {
StaticLog.error("[安全接入平台],bizSeq--[{}]--验签报文体为空", bizSeq);
return false;
}
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--验签异常", bizSeq);
return false;
}
}
private static String dealResult(String bizSeq, String result) {
try {
SAResponseVO responseVO = JSONUtil.json2Object(result, SAResponseVO.class);
return JSONUtil.toJson(responseVO.getResponsePackage());
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--处理返回报文异常", bizSeq);
return "";
}
}
}
package cn.anicert.secure.access.crypto.service;
import javax.security.auth.Subject;
/**
* @Description
* @Date 2024/5/29 15:30
* @Author yourongbin
**/
public interface ISoftCryptoService {
/**
* 数字信封加密
* @param cert 证书的base64编码
* @param data 加密数据
* @return
*/
public String encodeEnvelop(String cert, String data) throws Exception ;
/**
* 数字信封加密
* @param cert 证书的base64编码
* @param dataByte
* @return
*/
public String encodeEnvelop(String cert, byte[] dataByte) throws Exception;
/**
* 数字信封解密 返回明文
* @param priKey
* @param envelop
* @return
* @throws Exception
*/
public String decodeEnvelop(String priKey, String envelop) throws Exception;
/**
* 数字信封解密 返回明文
* @param priKey
* @param envelop
* @return
* @throws Exception
*/
public String decodeEnvelop(String priKey, byte[] envelop) throws Exception;
/**
* sm2加密
* @param publicKey
* @param data
* @return
* @throws Exception
*/
public String encrypt(String publicKey, String data) throws Exception;
/**
* sm2解密
* @param privateKey
* @param encryptedData
* @return
* @throws Exception
*/
public String decrypt(String privateKey, String encryptedData) throws Exception;
/**
* p7加签
* @param prikey 私钥
* @param cert 证书
* @param oriData 数据
* @return
* @throws Exception
*/
public String p7Sign(String prikey, String cert ,String oriData) throws Exception;
/**
* p7验签
* @param sign 签名值
* @param source 原文
* @return
* @throws Exception
*/
public Boolean p7Verify(String sign,String source) throws Exception;
/**
* p1加签
* @param prikey
* @param oriData
* @return
* @throws Exception
*/
public String p1Sign(String prikey,String oriData) throws Exception;
/**
* p1验签
* @param publicKey
* @param data
* @param signature
* @return
* @throws Exception
*/
public boolean verify(String publicKey, String data, String signature) throws Exception;
/**
* sm2加签
* @param prikey
* @param oriData
* @return
* @throws Exception
*/
public String sm2Sign(String prikey,String oriData) throws Exception;
/**
* sm2验签
* @param publicKey
* @param data
* @param signature
* @return
* @throws Exception
*/
public boolean sm2SignVerify(String publicKey, String data, String signature) throws Exception;
/**
* p1验签
* @param publicKey
* @param data
* @param charset
* @param signature
* @return
* @throws Exception
*/
public boolean verify(String publicKey, String data, String charset, String signature) throws Exception;
/**
* 获取业务站点号
* @param signature
* @return
* @throws Exception
*/
public String getCommonNameFromP7Sign(String signature) throws Exception;
/**
* sm3算法
* @param source
* @return
* @throws Exception
*/
public String sm3Hash(String source) throws Exception;
}
package cn.anicert.secure.access.crypto.service.impl;
import cn.anicert.module.sign.bc.ICryptoService;
import cn.anicert.module.sign.bc.mix.MixCryptoFactory;
import cn.anicert.module.sign.bc.std.Asymmetric;
import cn.anicert.module.sign.bc.std.Symmetric;
import cn.anicert.secure.access.crypto.util.CertUtils;
import cn.anicert.secure.access.crypto.util.SM2Utils;
import cn.anicert.secure.access.crypto.util.SM3Utils;
import cn.anicert.secure.access.crypto.service.ISoftCryptoService;
import cn.hutool.core.io.FileUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @Description
* @Date 2024/5/29 15:35
* @Author yourongbin
**/
public class SoftCryptoServiceImpl implements ISoftCryptoService {
private ICryptoService signService = MixCryptoFactory.getInstance(Asymmetric.SM2, Symmetric.SM4);
@Override
public String encodeEnvelop(String cert, String data) throws Exception {
return encodeEnvelop(cert, data.getBytes());
}
@Override
public String encodeEnvelop(String cert, byte[] dataByte) throws Exception {
return Base64.getEncoder().encodeToString(SM2Utils.encodeEnvelop(cert, dataByte));
}
@Override
public String decodeEnvelop(String priKey, String envelop) throws Exception {
return decodeEnvelop(priKey, Base64.getDecoder().decode(envelop));
}
@Override
public String decodeEnvelop(String priKey, byte[] envelop) throws Exception {
return new String(SM2Utils.decodeEnvelop(priKey, envelop), StandardCharsets.UTF_8);
}
@Override
public String encrypt(String publicKey, String data) throws Exception {
return SM2Utils.encrypt(publicKey, data);
}
@Override
public String decrypt(String privateKey, String encryptedData) throws Exception {
return SM2Utils.decrypt(privateKey, encryptedData);
}
@Override
public String p7Sign(String prikey, String cert, String oriData) throws Exception {
return SM2Utils.pkcs7SignDetach(prikey, cert, oriData);
}
@Override
public Boolean p7Verify(String sign, String source) throws Exception {
return SM2Utils.pkcs7Verify(Base64.getDecoder().decode(sign), source.getBytes());
}
@Override
public String p1Sign(String prikey, String oriData) throws Exception {
return SM2Utils.sign(prikey, oriData);
}
@Override
public boolean verify(String publicKey, String data, String signature) throws Exception {
return verify(publicKey, data, "utf-8", signature);
}
@Override
public String sm2Sign(String prikey, String oriData) throws Exception {
return Base64.getEncoder().encodeToString(
signService.sign(prikey, oriData.getBytes(StandardCharsets.UTF_8)));
}
@Override
public boolean sm2SignVerify(String publicKey, String data, String signature) throws Exception {
byte[] byteSign = Base64.getDecoder().decode(signature);
byte[] byteBody = data.getBytes(StandardCharsets.UTF_8);
return signService.verifySign(publicKey, byteSign, byteBody);
}
@Override
public boolean verify(String publicKey, String data, String charset, String signature) throws Exception {
return SM2Utils.verify(publicKey, data, charset, signature);
}
@Override
public String getCommonNameFromP7Sign(String signature) throws Exception {
return CertUtils.getSubjectDn(signature);
}
@Override
public String sm3Hash(String source) throws Exception {
return SM3Utils.sm3Hash(source.getBytes("UTF-16LE"));
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
import java.io.IOException;
/**
* @Description
* @Date 2024/5/30 17:18
* @Author yourongbin
**/
public class ASN1Util {
public static ASN1Object checkAndGetASN1Object(byte[] in)
throws IllegalArgumentException
{
ASN1InputStream din = null;
ASN1Primitive pkcs;
try
{
din = new ASN1InputStream(in);
pkcs = din.readObject();
} catch (IOException var11) {
throw new IllegalArgumentException("failed to construct sequence from byte[]:" + var11.getMessage());
} finally {
if (din != null) {
try {
din.close();
} catch (Exception var10) {
var10.printStackTrace();
}
}
}
return pkcs;
}
}
package cn.anicert.secure.access.crypto.util;
public class Base64 {
public Base64() {
}
public static String encode(byte[] data) {
return org.apache.commons.codec.binary.Base64.encodeBase64String(data);
}
public static byte[] decode(String data) {
return org.apache.commons.codec.binary.Base64.decodeBase64(data);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
/**
* @company cn.anicert
* @author: wangpengyuan
* @create: 2024-03-07
* @description 密码学基础类
**/
public class BaseCryptoUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
}
\ No newline at end of file
package cn.anicert.secure.access.crypto.util;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;
public class SM2KeyUtil {
private static final X9ECParameters ecParameters;
private static final ECNamedCurveSpec sm2Spec;
private static final BouncyCastleProvider bcProvider;
private static final KeyPairGenerator keyGenerator;
private static final KeyFactory keyFactory;
private static final ECCurve curve;
public SM2KeyUtil() {
}
public static KeyPair genKeyPair() {
return keyGenerator.generateKeyPair();
}
public static byte[] encodeWithPoint(BCECPublicKey pub) {
return pub.getQ().getEncoded(false);
}
public static BCECPublicKey decodeWithPoint(byte[] data) throws InvalidKeySpecException {
ECPoint point = EC5Util.convertPoint(curve.decodePoint(data));
return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
}
public static String encodeStrWithPoint(BCECPublicKey pub) {
return Base64.encode(encodeWithPoint(pub));
}
public static BCECPublicKey decodeWithPoint(String base64) throws InvalidKeySpecException {
return decodeWithPoint(Base64.decode(base64));
}
public static String getX64(BCECPublicKey pub) {
return Base64.encode(pub.getQ().getXCoord().getEncoded());
}
public static String getY64(BCECPublicKey pub) {
return Base64.encode(pub.getQ().getYCoord().getEncoded());
}
public static BCECPublicKey fromXY(String x64, String y64) throws InvalidKeySpecException {
return fromXY(Base64.decode(x64), Base64.decode(y64));
}
public static byte[] getX(BCECPublicKey pub) {
return pub.getQ().getXCoord().getEncoded();
}
public static byte[] getY(BCECPublicKey pub) {
return pub.getQ().getYCoord().getEncoded();
}
public static BCECPublicKey fromXY(byte[] x, byte[] y) throws InvalidKeySpecException {
ECPoint point = EC5Util.convertPoint(curve.createPoint(new BigInteger(1, x), new BigInteger(1, y)));
return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
}
public static byte[] encodeWithBigInteger(BCECPrivateKey pri) {
return pri.getD().toByteArray();
}
public static BCECPrivateKey decodeWithBigInteger(byte[] data) throws InvalidKeySpecException {
return (BCECPrivateKey)keyFactory.generatePrivate(new ECPrivateKeySpec(new BigInteger(1, data), sm2Spec));
}
public static String encodeStrWithBigInteger(BCECPrivateKey pri) {
return Base64.encode(encodeWithBigInteger(pri));
}
public static BCECPrivateKey decodeWithBigInteger(String base64) throws InvalidKeySpecException {
return decodeWithBigInteger(Base64.decode(base64));
}
public static byte[] encodeWithKey(Key key) {
return key.getEncoded();
}
public static BCECPublicKey decodeWithX509(byte[] data) throws InvalidKeySpecException {
return (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(data));
}
public static BCECPrivateKey decodeWithPKCS8(byte[] data) throws InvalidKeySpecException {
return (BCECPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(data));
}
public static String encodeStrWithKey(Key key) {
return Base64.encode(encodeWithKey(key));
}
public static BCECPublicKey decodeWithX509(String base64) throws InvalidKeySpecException {
return decodeWithX509(Base64.decode(base64.replaceAll("-----.*-----", "")));
}
public static BCECPrivateKey decodeWithPKCS8(String base64) throws InvalidKeySpecException {
return decodeWithPKCS8(Base64.decode(base64.replaceAll("-----.*-----", "")));
}
public static CipherParameters forParameters(Key key) throws InvalidKeyException {
if (key instanceof PublicKey) {
return ECUtil.generatePublicKeyParameter((PublicKey)key);
} else if (key instanceof PrivateKey) {
return ECUtil.generatePrivateKeyParameter((PrivateKey)key);
} else {
throw new InvalidKeyException("not expect key type");
}
}
static {
try {
ecParameters = GMNamedCurves.getByName("sm2p256v1");
sm2Spec = new ECNamedCurveSpec(GMObjectIdentifiers.sm2p256v1.toString(), ecParameters.getCurve(), ecParameters.getG(), ecParameters.getN());
bcProvider = new BouncyCastleProvider();
keyGenerator = KeyPairGenerator.getInstance("EC", bcProvider);
keyGenerator.initialize(sm2Spec, new SecureRandom());
keyFactory = KeyFactory.getInstance("EC", bcProvider);
curve = ecParameters.getCurve();
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException var1) {
throw new RuntimeException("无法提供SM2算法", var1);
}
}
public static void main(String[] args) {
KeyPair smKeyPair = SM2KeyUtil.genKeyPair();
String publicKey = Base64.encode(smKeyPair.getPublic().getEncoded());
String privateKey = Base64.encode(smKeyPair.getPrivate().getEncoded());
System.out.println("public key:"+ publicKey);
System.out.println("private key:"+ privateKey);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
import java.util.Arrays;
public class SM3Utils extends BaseCryptoUtil {
/**
* 计算SM3摘要值
*
* @param srcData 原文
* @return 摘要值,对于SM3算法来说是32字节
*/
public static String sm3Hash(byte[] srcData) {
SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
String result = Hex.toHexString(hash);
return result.toUpperCase();
}
/**
* 计算两项身份信息hash
*
* @param idno 身份证号
* @param name 姓名
* @return 摘要值,对于SM3算法来说是32字节
*/
public static String sm3Hash(String idno, String name) {
String str = idno + name;
byte[] srcData = str.getBytes();
return sm3Hash(srcData);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.crypto.macs.GMac;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class SM4Utils extends BaseCryptoUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static final String ALGORITHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding";
public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding";
/**
* SM4算法目前只支持128位(即密钥16字节)
*/
public static final int DEFAULT_KEY_SIZE = 16;
public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
return generateKey(DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
// KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
// kg.init(keySize, new SecureRandom());
SecureRandom secureRandom = new SecureRandom();
byte[] random = new byte[keySize];
secureRandom.nextBytes(random);
return random;
// return kg.generateKey().getEncoded();
}
public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(data);
}
public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(data);
}
public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(cipherText);
}
public static byte[] doCMac(byte[] key, byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeyException {
Key keyObj = new SecretKeySpec(key, ALGORITHM_NAME);
return doMac("SM4-CMAC", keyObj, data);
}
public static byte[] doGMac(byte[] key, byte[] iv, int tagLength, byte[] data) {
org.bouncycastle.crypto.Mac mac = new GMac(new GCMBlockCipher(new SM4Engine()), tagLength * 8);
return doMac(mac, key, iv, data);
}
/**
* 默认使用PKCS7Padding/PKCS5Padding填充的CBCMAC
*
* @param key
* @param iv
* @param data
* @return
*/
public static byte[] doCBCMac(byte[] key, byte[] iv, byte[] data) {
SM4Engine engine = new SM4Engine();
org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, new PKCS7Padding());
return doMac(mac, key, iv, data);
}
/**
* @param key
* @param iv
* @param padding 可以传null,传null表示NoPadding,由调用方保证数据必须是BlockSize的整数倍
* @param data
* @return
* @throws Exception
*/
public static byte[] doCBCMac(byte[] key, byte[] iv, BlockCipherPadding padding, byte[] data) throws Exception {
SM4Engine engine = new SM4Engine();
if (padding == null) {
if (data.length % engine.getBlockSize() != 0) {
throw new Exception("if no padding, data length must be multiple of SM4 BlockSize");
}
}
org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, padding);
return doMac(mac, key, iv, data);
}
private static byte[] doMac(org.bouncycastle.crypto.Mac mac, byte[] key, byte[] iv, byte[] data) {
CipherParameters cipherParameters = new KeyParameter(key);
mac.init(new ParametersWithIV(cipherParameters, iv));
mac.update(data, 0, data.length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return result;
}
private static byte[] doMac(String algorithmName, Key key, byte[] data) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
mac.init(key);
mac.update(data);
return mac.doFinal();
}
private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidKeyException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(mode, sm4Key, ivParameterSpec);
return cipher;
}
}
package cn.anicert.secure.access.enums;
public enum Configs {
customerNo("customerNo", "业务站点号", true),
accessUrl("accessUrl", "安全接入平台地址", true),
signPrivateKey("signPrivateKey", "签名私钥", true),
signVerifyPublicKey("signVerifyPublicKey", "验签公钥", true),
dataEncryptKey("dataEncryptKey", "数据加密秘钥", false);
private String key;
private String name;
private boolean necessary;
Configs(String key, String name) {
this(key, name, false);
}
Configs(String key, String name, boolean necessary) {
this.key = key;
this.name = name;
this.necessary = necessary;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isNecessary() {
return necessary;
}
public void setNecessary(boolean necessary) {
this.necessary = necessary;
}
}
package cn.anicert.secure.access.exceptions;
public class SecureAccessConfigException extends RuntimeException {
public SecureAccessConfigException() {
super();
}
public SecureAccessConfigException(String message) {
super(message);
}
}
package cn.anicert.secure.access.utils;
import cn.anicert.secure.access.config.ConfigReader;
import cn.anicert.secure.access.config.bean.AccessConfig;
import cn.anicert.secure.access.enums.Configs;
public class ConfigUtil {
private static AccessConfig accessConfig;
public static synchronized void initConfig(String... filePath) {
accessConfig = ConfigReader.readConfig(filePath);
}
public static synchronized void initConfig(AccessConfig config) {
accessConfig = config;
}
public static AccessConfig getConfig() {
return accessConfig;
}
public static void updateConfigDataEncryptKey(String newKey) {
accessConfig=ConfigReader.writeConfig(Configs.dataEncryptKey.getKey(), newKey);
}
}
package cn.anicert.secure.access.utils;
import cn.hutool.log.StaticLog;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.Timeout;
import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
public class HttpUtil {
private final PoolingHttpClientConnectionManager connPoolMng;
private final RequestConfig requestConfig;
private final SSLContext sslContext;
private volatile static HttpUtil httpUtilInstance;
/**
* 私有构造方法
* 单例中连接池初始化一次
*/
private HttpUtil() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
//初始化http连接池
sslContext = SSLContextBuilder.create()
.loadTrustMaterial(new TrustAllStrategy())
.build();
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https",new SSLConnectionSocketFactory(sslContext))
.build();
connPoolMng = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
connPoolMng.setMaxTotal(ConfigUtil.getConfig().getPoolMaxConn());
connPoolMng.setDefaultMaxPerRoute(ConfigUtil.getConfig().getPoolMaxConn());
//初始化请求超时控制参数
requestConfig = RequestConfig.custom()
//从线程池中获取线程超时时间
.setConnectionRequestTimeout(Timeout.ofMilliseconds(ConfigUtil.getConfig().getConnectionRequestTimeout()))
//设置数据读取超时时间
.setResponseTimeout(Timeout.ofMilliseconds(ConfigUtil.getConfig().getResponseTimeout()))
.build();
}
/**
* 单例模式
* 使用双检锁机制,线程安全且在多线程情况下能保持高性能
*
* @return HttpUtil
*/
public static HttpUtil getInstance() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if (httpUtilInstance == null) {
synchronized (HttpUtil.class) {
if (httpUtilInstance == null) {
httpUtilInstance = new HttpUtil();
}
}
}
return httpUtilInstance;
}
/**
* 获取client客户端
*
* @return
*/
public CloseableHttpClient getClient() {
return HttpClients.custom()
.setConnectionManager(connPoolMng)
.setDefaultRequestConfig(requestConfig)
.build();
}
/**
* post请求
*
* @param postUrl
* @param jsonStr
* @return String
* @throws Exception
*/
public String postRequest(String postUrl, String jsonStr) throws Exception {
String result;
HttpPost post = new HttpPost(postUrl);
StringEntity reqEntity = new StringEntity(jsonStr, ContentType.APPLICATION_JSON);
post.setEntity(reqEntity);
result = getClient().execute(post, response -> {
StaticLog.debug("[安全接入平台]--请求地址--{}--响应状态--{}", postUrl, response.getCode());
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
});
return result;
}
}
package cn.anicert.secure.access.utils;
import cn.anicert.module.sign.bc.ICryptoService;
import cn.anicert.module.sign.bc.exception.BadKeyException;
import cn.anicert.module.sign.bc.exception.BadSignatureException;
import cn.anicert.module.sign.bc.mix.MixCryptoFactory;
import cn.anicert.module.sign.bc.std.Asymmetric;
import cn.anicert.module.sign.bc.std.Symmetric;
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.StandardCharsets;
public class SignUtil {
private final static ICryptoService cryptoService = MixCryptoFactory.getInstance(Asymmetric.SM2, Symmetric.SM4);
public static String sign(String signBody) throws BadSignatureException, BadKeyException {
return sign(signBody, ConfigUtil.getConfig().getSignPrivateKey());
}
public static String sign(String signBody, String signPriKey) throws BadSignatureException, BadKeyException {
return Base64.encodeBase64String(cryptoService.sign(signPriKey, signBody.getBytes(StandardCharsets.UTF_8)));
}
public static boolean verifySign(String body, String sign) throws BadSignatureException, BadKeyException {
return verifySign(body, sign, ConfigUtil.getConfig().getSignVerifyPublicKey());
}
public static boolean verifySign(String body, String sign, String key) throws BadSignatureException, BadKeyException {
byte[] byteSign = Base64.decodeBase64(sign);
byte[] byteBody = body.getBytes(StandardCharsets.UTF_8);
return cryptoService.verifySign(key, byteSign, byteBody);
}
}
......@@ -17,5 +17,6 @@
<module>liquidnet-common-third-antchain</module>
<module>liquidnet-common-third-xuper</module>
<module>liquidnet-common-third-sqb</module>
<module>liquidnet-common-third-secure-access</module>
</modules>
</project>
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