From 5632c515271943200f4b189204947632bf77ccc9 Mon Sep 17 00:00:00 2001 From: DevDengChao <2325690622@qq.com> Date: Wed, 15 May 2024 13:44:18 +0800 Subject: [PATCH 01/23] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20DeliveryExpressDO=20=E6=97=B6=E6=8F=90=E7=A4=BA=20i?= =?UTF-8?q?d=20=E8=BF=87=E5=A4=A7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trade/dal/dataobject/delivery/DeliveryExpressDO.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java index 265066d83..c6b7fbf33 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.delivery; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -20,7 +21,7 @@ public class DeliveryExpressDO extends BaseDO { /** * 编号,自增 */ - @TableId + @TableId(type = IdType.AUTO) private Long id; /** From c68f32cefeecd3afef706f9db677c38ba7c704d6 Mon Sep 17 00:00:00 2001 From: DevDengChao <2325690622@qq.com> Date: Wed, 15 May 2024 13:44:39 +0800 Subject: [PATCH 02/23] =?UTF-8?q?chore:=20=E5=A4=87=E6=B3=A8=E5=9B=A2?= =?UTF-8?q?=E9=95=BF=E7=BF=BB=E8=AF=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/trade/dal/dataobject/order/TradeOrderDO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index b127004aa..ba7e689f8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -322,7 +322,7 @@ public class TradeOrderDO extends BaseDO { * * 关联 CombinationRecordDO 的 headId 字段 */ - private Long combinationHeadId; + private Long combinationHeadId; // FIXME 2024/5/15: "团长" 应该译作 leader / starter 而不是 head /** * 拼团记录编号 * From 9c111acfbe8a1e81542e345d2bef59327bc9cc1c Mon Sep 17 00:00:00 2001 From: DevDengChao <2325690622@qq.com> Date: Wed, 15 May 2024 13:44:57 +0800 Subject: [PATCH 03/23] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20TradeOrderUpd?= =?UTF-8?q?ateServiceTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/TradeOrderUpdateServiceTest.java | 50 +++- .../src/test/resources/sql/create_tables.sql | 275 ++++++++++-------- 2 files changed, 203 insertions(+), 122 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index 3f05f5559..2f78dbe08 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java @@ -1,33 +1,49 @@ package cn.iocoder.yudao.module.trade.service.order; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; +import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; +import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; +import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; +import cn.iocoder.yudao.module.trade.service.cart.CartServiceImpl; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressServiceImpl; +import cn.iocoder.yudao.module.trade.service.message.TradeMessageServiceImpl; +import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler; +import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl; +import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; +import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; import java.time.Duration; +import java.util.UUID; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -37,8 +53,10 @@ import static org.mockito.Mockito.when; * @author LeeYan9 * @since 2022-09-07 */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 -@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class}) +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class, + DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class +}) public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @Resource @@ -55,7 +73,9 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { private ProductSpuApi productSpuApi; @MockBean private ProductSkuApi productSkuApi; -// @MockBean + @MockBean + private ProductCommentApi productCommentApi; + // @MockBean // private PriceApi priceApi; @MockBean private PayOrderApi payOrderApi; @@ -66,11 +86,22 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @MockBean private TradeOrderProperties tradeOrderProperties; + @MockBean + private TradeNoRedisDAO tradeNoRedisDAO; + @MockBean + private TradeOrderHandler tradeOrderHandler; + @MockBean + private TradePriceCalculator tradePriceCalculator; + @MockBean + private NotifyMessageSendApi notifyMessageSendApi; + @Autowired + private DeliveryExpressService deliveryExpressService; @BeforeEach public void setUp() { when(tradeOrderProperties.getAppId()).thenReturn(888L); when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1)); + when(tradeNoRedisDAO.generate(anyString())).thenReturn(UUID.randomUUID().toString()); } // @Test @@ -259,11 +290,18 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> { o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()); o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null); + o.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); + o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()); }); tradeOrderMapper.insert(order); + + DeliveryExpressCreateReqVO expressCreateReqVO = new DeliveryExpressCreateReqVO(); + expressCreateReqVO.setCode("code").setName("Name").setLogo("logo").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); + Long deliveryExpressId = deliveryExpressService.createDeliveryExpress(expressCreateReqVO); // 准备参数 TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L) - .setLogisticsId(10L).setLogisticsNo("100"); + .setLogisticsId(deliveryExpressId).setLogisticsNo("100"); + // mock 方法(支付单) // 调用 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql index d263fdfb9..f619c01de 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql @@ -1,128 +1,155 @@ -CREATE TABLE IF NOT EXISTS "trade_order" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "no" varchar NOT NULL, - "type" int NOT NULL, - "terminal" int NOT NULL, - "user_id" bigint NOT NULL, - "user_ip" varchar NOT NULL, - "user_remark" varchar, - "status" int NOT NULL, - "product_count" int NOT NULL, - "cancel_type" int, - "remark" varchar, - "pay_status" bit NOT NULL, - "pay_time" datetime, - "finish_time" datetime, - "cancel_time" datetime, - "original_price" int NOT NULL, - "order_price" int NOT NULL, - "discount_price" int NOT NULL, - "delivery_price" int NOT NULL, - "adjust_price" int NOT NULL, - "pay_price" int NOT NULL, - "pay_order_id" bigint, - "pay_channel_code" varchar, - "delivery_template_id" bigint, - "logistics_id" bigint, - "logistics_no" varchar, - "delivery_time" datetime, - "receive_time" datetime, - "receiver_name" varchar NOT NULL, - "receiver_mobile" varchar NOT NULL, - "receiver_area_id" int NOT NULL, - "receiver_post_code" int, - "receiver_detail_address" varchar NOT NULL, - "after_sale_status" int NOT NULL, - "refund_price" int NOT NULL, - "coupon_id" bigint NOT NULL, - "coupon_price" int NOT NULL, - "point_price" int NOT NULL, - "creator" varchar DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") +CREATE TABLE IF NOT EXISTS "trade_order" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar NOT NULL, + "type" int NOT NULL, + "terminal" int NOT NULL, + "user_id" bigint NOT NULL, + "user_ip" varchar NOT NULL, + "user_remark" varchar, + "status" int NOT NULL, + "product_count" int NOT NULL, + "cancel_type" int, + "remark" varchar, + "comment_status" boolean, + "brokerage_user_id" bigint, + "pay_status" bit NOT NULL, + "pay_time" datetime, + "finish_time" datetime, + "cancel_time" datetime, + "total_price" int NULL, + "order_price" int NULL, + "discount_price" int NOT NULL, + "delivery_price" int NOT NULL, + "adjust_price" int NOT NULL, + "pay_price" int NOT NULL, + "delivery_type" int NOT NULL, + "pay_order_id" bigint, + "pay_channel_code" varchar, + "delivery_template_id" bigint, + "logistics_id" bigint, + "logistics_no" varchar, + "delivery_time" datetime, + "receive_time" datetime, + "receiver_name" varchar NOT NULL, + "receiver_mobile" varchar NOT NULL, + "receiver_area_id" int NOT NULL, + "receiver_post_code" int, + "receiver_detail_address" varchar NOT NULL, + "pick_up_store_id" long NULL, + "pick_up_verify_code" varchar NULL, + "refund_status" int NULL, + "refund_price" int NULL, + "after_sale_status" int NULL, + "coupon_id" bigint NOT NULL, + "coupon_price" int NOT NULL, + "use_point" int NULL, + "point_price" int NOT NULL, + "give_point" int NULL, + "refund_point" int NULL, + "vip_price" int NULL, + "seckill_activity_id" long NULL, + "bargain_activity_id" long NULL, + "bargain_record_id" long NULL, + "combination_activity_id" long NULL, + "combination_head_id" long NULL, + "combination_record_id" long NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") ) COMMENT '交易订单表'; -CREATE TABLE IF NOT EXISTS "trade_order_item" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "user_id" bigint NOT NULL, - "order_id" bigint NOT NULL, - "spu_id" bigint NOT NULL, - "spu_name" varchar NOT NULL, - "sku_id" bigint NOT NULL, - "properties" varchar, - "pic_url" varchar, - "count" int NOT NULL, - "original_price" int NOT NULL, - "original_unit_price" int NOT NULL, - "discount_price" int NOT NULL, - "pay_price" int NOT NULL, - "order_part_price" int NOT NULL, - "order_divide_price" int NOT NULL, - "after_sale_status" int NOT NULL, - "creator" varchar DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") +CREATE TABLE IF NOT EXISTS "trade_order_item" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "order_id" bigint NOT NULL, + "cart_id" int NULL, + "spu_id" bigint NOT NULL, + "spu_name" varchar NOT NULL, + "sku_id" bigint NOT NULL, + "properties" varchar, + "pic_url" varchar, + "count" int NOT NULL, + "comment_status" boolean NULL, + "price" int NOT NULL, + "discount_price" int NOT NULL, + "delivery_price" int NULL, + "adjust_price" int NULL, + "pay_price" int NOT NULL, + "coupon_price" int NULL, + "point_price" int NULL, + "use_point" int NULL, + "give_point" int NULL, + "vip_price" int NULL, + "after_sale_id" long NULL, + "after_sale_status" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") ) COMMENT '交易订单明细表'; -CREATE TABLE IF NOT EXISTS "trade_after_sale" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "no" varchar NOT NULL, - "status" int NOT NULL, - "type" int NOT NULL, - "way" int NOT NULL, - "user_id" bigint NOT NULL, - "apply_reason" varchar NOT NULL, +CREATE TABLE IF NOT EXISTS "trade_after_sale" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar NOT NULL, + "status" int NOT NULL, + "type" int NOT NULL, + "way" int NOT NULL, + "user_id" bigint NOT NULL, + "apply_reason" varchar NOT NULL, "apply_description" varchar, "apply_pic_urls" varchar, - "order_id" bigint NOT NULL, - "order_no" varchar NOT NULL, - "order_item_id" bigint NOT NULL, - "spu_id" bigint NOT NULL, - "spu_name" varchar NOT NULL, - "sku_id" bigint NOT NULL, - "properties" varchar, - "pic_url" varchar, - "count" int NOT NULL, - "audit_time" varchar, - "audit_user_id" bigint, - "audit_reason" varchar, - "refund_price" int NOT NULL, - "pay_refund_id" bigint, - "refund_time" varchar, - "logistics_id" bigint, - "logistics_no" varchar, - "delivery_time" varchar, - "receive_time" varchar, + "order_id" bigint NOT NULL, + "order_no" varchar NOT NULL, + "order_item_id" bigint NOT NULL, + "spu_id" bigint NOT NULL, + "spu_name" varchar NOT NULL, + "sku_id" bigint NOT NULL, + "properties" varchar, + "pic_url" varchar, + "count" int NOT NULL, + "audit_time" varchar, + "audit_user_id" bigint, + "audit_reason" varchar, + "refund_price" int NOT NULL, + "pay_refund_id" bigint, + "refund_time" varchar, + "logistics_id" bigint, + "logistics_no" varchar, + "delivery_time" varchar, + "receive_time" varchar, "receive_reason" varchar, - "creator" varchar DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") ) COMMENT '交易售后表'; -CREATE TABLE IF NOT EXISTS "trade_after_sale_log" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "user_id" bigint NOT NULL, - "user_type" int NOT NULL, - "after_sale_id" bigint NOT NULL, - "order_id" bigint NOT NULL, - "order_item_id" bigint NOT NULL, +CREATE TABLE IF NOT EXISTS "trade_after_sale_log" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "user_type" int NOT NULL, + "after_sale_id" bigint NOT NULL, + "order_id" bigint NOT NULL, + "order_item_id" bigint NOT NULL, "before_status" int, - "after_status" int NOT NULL, - "content" varchar NOT NULL, - "creator" varchar DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, + "after_status" int NOT NULL, + "content" varchar NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") ) COMMENT '交易售后日志'; @@ -161,7 +188,7 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_record" "updater" varchar DEFAULT '', "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint not null default '0', + "tenant_id" bigint not null default '0', PRIMARY KEY ("id") ) COMMENT '佣金记录'; CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw" @@ -186,6 +213,22 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw" "updater" varchar DEFAULT '', "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint not null default '0', + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '佣金提现'; + +CREATE TABLE IF NOT EXISTS "trade_delivery_express" +( + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar NULL, + "name" varchar, + "logo" varchar NULL, + "sort" int NOT NULL, + "status" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") ) COMMENT '佣金提现'; \ No newline at end of file From 8e5584821e722498b21523a38efe22eef57ae638 Mon Sep 17 00:00:00 2001 From: zhougang <921366807@qq.com> Date: Sun, 19 May 2024 15:34:45 +0800 Subject: [PATCH 04/23] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=20pr?= =?UTF-8?q?otection=20=E6=A8=A1=E5=9D=97=E6=96=B0=E5=A2=9E=20signature=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20API=20=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 9 +- .../YudaoSignatureAutoConfiguration.java | 27 +++ .../signature/core/annotation/Signature.java | 57 ++++++ .../signature/core/aop/SignatureAspect.java | 170 ++++++++++++++++++ .../core/redis/SignatureRedisDAO.java | 56 ++++++ .../signature/core/SignatureTest.java | 136 ++++++++++++++ .../core/filter/CacheRequestBodyWrapper.java | 4 + 7 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java diff --git a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml index bbb5b12eb..025bb7591 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml @@ -12,7 +12,7 @@ jar ${project.artifactId} - 服务保证,提供分布式锁、幂等、限流、熔断等等功能 + 服务保证,提供分布式锁、幂等、限流、熔断、API签名等等功能 https://github.com/YunaiV/ruoyi-vue-pro @@ -35,6 +35,13 @@ lock4j-redisson-spring-boot-starter true + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java new file mode 100644 index 000000000..5b4b8e43e --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.framework.signature.config; + +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; +import cn.iocoder.yudao.framework.signature.core.aop.SignatureAspect; +import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author Zhougang + */ +@AutoConfiguration(after = YudaoRedisAutoConfiguration.class) +public class YudaoSignatureAutoConfiguration { + + @Bean + public SignatureAspect signatureAspect(SignatureRedisDAO signatureRedisDAO) { + return new SignatureAspect(signatureRedisDAO); + } + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public SignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) { + return new SignatureRedisDAO(stringRedisTemplate); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java new file mode 100644 index 000000000..1b7e12786 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.framework.signature.core.annotation; + +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; + +import java.lang.annotation.*; + + +/** + * 签名注解 + * + * @author Zhougang + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Signature { + + /** + * 同一个请求多长时间内有效 默认 10分钟 + */ + long expireTime() default 600000L; + + // ========================== 签名参数 ========================== + + /** + * 提示信息,签名失败的提示 + * + * @see GlobalErrorCodeConstants#BAD_REQUEST + */ + String message() default "签名不正确"; // 为空时,使用 BAD_REQUEST 错误提示 + + /** + * 签名字段:appId 应用ID + */ + String appId() default "appId"; + + /** + * 签名字段:timestamp 时间戳 + */ + String timestamp() default "timestamp"; + + /** + * 签名字段:nonce 随机数,10 位以上 + */ + String nonce() default "nonce"; + + /** + * sign 客户端签名 + */ + String sign() default "sign"; + + /** + * url 客户端不需要传递,但是可以用来加签(如: /{id} 带有动态参数的 url ,如果没有动态参数可设置为 false 不进行加签) + */ + boolean urlEnable() default true; +} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java new file mode 100644 index 000000000..dc1510465 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java @@ -0,0 +1,170 @@ +package cn.iocoder.yudao.framework.signature.core.aop; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SignUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.signature.core.annotation.Signature; +import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; +import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.util.Assert; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; + +/** + * 拦截声明了 {@link Signature} 注解的方法,实现签名 + * + * @author Zhougang + */ +@Aspect +@Slf4j +@AllArgsConstructor +public class SignatureAspect { + + private final SignatureRedisDAO signatureRedisDAO; + + @Before("@annotation(signature)") + public void beforePointCut(JoinPoint joinPoint, Signature signature) { + if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { + log.info("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), + joinPoint.getArgs()); + String message = StrUtil.blankToDefault(signature.message(), + GlobalErrorCodeConstants.BAD_REQUEST.getMsg()); + throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), message); + } + } + + private boolean verifySignature(Signature signature, HttpServletRequest request) { + if (!verifyHeaders(signature, request)) { + return false; + } + // 校验 appId 是否能获取到对应的 appSecret + String appId = request.getHeader(signature.appId()); + String appSecret = signatureRedisDAO.getAppSecret(appId); + Assert.notNull(appSecret, "找不到对应的 appSecret"); + // 请求头 + SortedMap headersMap = getRequestHeaders(signature, request); + // 如:/user/{id} url 带有动态参数的情况 + String urlParams = signature.urlEnable() ? request.getServletPath() : ""; + // 请求参数 + String requestParams = getRequestParams(request); + // 请求体 + String requestBody = getRequestBody(request); + // 生成服务端签名 + String serverSignature = SignUtil.signParamsSha256(headersMap, + urlParams + requestParams + requestBody + appSecret); + // 客户端签名 + String clientSignature = request.getHeader(signature.sign()); + if (!StrUtil.equals(clientSignature, serverSignature)) { + return false; + } + String nonce = headersMap.get(signature.nonce()); + // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) + signatureRedisDAO.setNonce(nonce, signature.expireTime(), TimeUnit.MILLISECONDS); + return true; + } + + /** + * 校验请求头加签参数 + * 1.appId 是否为空 + * 2.timestamp 是否为空,请求是否已经超时,默认 10 分钟 + * 3.nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 + * 4.sign 是否为空 + * + * @param signature signature + * @param request request + */ + private boolean verifyHeaders(Signature signature, HttpServletRequest request) { + String appId = request.getHeader(signature.appId()); + if (StrUtil.isBlank(appId)) { + return false; + } + String timestamp = request.getHeader(signature.timestamp()); + if (StrUtil.isBlank(timestamp)) { + return false; + } + String nonce = request.getHeader(signature.nonce()); + if (StrUtil.isBlank(nonce) || nonce.length() < 10) { + return false; + } + String sign = request.getHeader(signature.sign()); + if (StrUtil.isBlank(sign)) { + return false; + } + // 其他合法性校验 + long expireTime = signature.expireTime(); + long requestTimestamp = Long.parseLong(timestamp); + // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) + long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); + if (timestampDisparity > expireTime) { + return false; + } + String cacheNonce = signatureRedisDAO.getNonce(nonce); + return StrUtil.isBlank(cacheNonce); + } + + /** + * 获取请求头加签参数 + * + * @param request request + * @return signature params + */ + private SortedMap getRequestHeaders(Signature signature, HttpServletRequest request) { + SortedMap sortedMap = new TreeMap<>(); + sortedMap.put(signature.appId(), request.getHeader(signature.appId())); + sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); + sortedMap.put(signature.nonce(), request.getHeader(signature.nonce())); + return sortedMap; + } + + /** + * 获取 URL 参数 + * + * @param request request + * @return queryParams + */ + private String getRequestParams(HttpServletRequest request) { + if (CollUtil.isEmpty(request.getParameterMap())) { + return ""; + } + Map requestParams = request.getParameterMap(); + // 获取 URL 请求参数 + SortedMap sortParamsMap = new TreeMap<>(); + for (Map.Entry entry : requestParams.entrySet()) { + sortParamsMap.put(entry.getKey(), entry.getValue()[0]); + } + // 按 key 排序 + StringBuilder queryString = new StringBuilder(); + for (Map.Entry entry : sortParamsMap.entrySet()) { + queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); + } + return queryString.substring(1); + } + + /** + * 获取请求体参数 + * + * @param request request + * @return body + */ + private String getRequestBody(HttpServletRequest request) { + CacheRequestBodyWrapper requestWrapper = new CacheRequestBodyWrapper(request); + // 获取 body + return new String(requestWrapper.getBody(), StandardCharsets.UTF_8); + } + +} + diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java new file mode 100644 index 000000000..d00fe7f8d --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.framework.signature.core.redis; + +import lombok.AllArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * API 签名 Redis DAO + * + * @author Zhougang + */ +@AllArgsConstructor +public class SignatureRedisDAO { + + private final StringRedisTemplate stringRedisTemplate; + + /** + * 验签随机数 + *

+ * KEY 格式:signature_nonce:%s // 参数为 随机数 + * VALUE 格式:String + * 过期时间:不固定 + */ + private static final String SIGNATURE_NONCE = "signature_nonce:%s"; + + /** + * 签名密钥 + *

+ * KEY 格式:signature_appid:%s // 参数为 appid + * VALUE 格式:String + * 过期时间:预加载到 redis 永不过期 + */ + private static final String SIGNATURE_APPID = "signature_appid:%s"; + + public String getAppSecret(String appId) { + return stringRedisTemplate.opsForValue().get(formatAppIdKey(appId)); + } + + public String getNonce(String nonce) { + return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce)); + } + + public void setNonce(String nonce, long time, TimeUnit timeUnit) { + // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) + stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time * 2, timeUnit); + } + + private static String formatAppIdKey(String key) { + return String.format(SIGNATURE_APPID, key); + } + + private static String formatNonceKey(String key) { + return String.format(SIGNATURE_NONCE, key); + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java new file mode 100644 index 000000000..a253cd51a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java @@ -0,0 +1,136 @@ +package cn.iocoder.yudao.framework.signature.core; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * {@link SignatureTest} 的单元测试 + */ +public class SignatureTest { + + @Test + public void testSignatureGet() { + String appId = "xxxxxx"; + Snowflake snowflake = new Snowflake(); + + // 验签请求头前端需传入字段 + SortedMap headersMap = new TreeMap<>(); + headersMap.put("appId", appId); + headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); + headersMap.put("nonce", String.valueOf(snowflake.nextId())); + + // 客户端加签内容 + StringBuilder clientSignatureContent = new StringBuilder(); + // 请求头 + for (Map.Entry entry : headersMap.entrySet()) { + clientSignatureContent.append(entry.getKey()).append(entry.getValue()); + } + // 请求 url + clientSignatureContent.append("/admin-api/infra/demo01-contact/get"); + // 请求参数 + SortedMap paramsMap = new TreeMap<>(); + paramsMap.put("id", "100"); + paramsMap.put("name", "张三"); + StringBuilder queryString = new StringBuilder(); + for (Map.Entry entry : paramsMap.entrySet()) { + queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); + } + clientSignatureContent.append(queryString.substring(1)); + // 密钥 + clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); + System.out.println("加签内容:" + clientSignatureContent); + // 加签 + headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); + headersMap.put("Authorization", "Bearer xxx"); + + HttpRequest get = HttpUtil.createGet("http://localhost:48080/admin-api/infra/demo01-contact/get?id=100&name=张三"); + get.addHeaders(headersMap); + System.out.println("执行结果==" + get.execute()); + } + + @Test + public void testSignaturePost() { + String appId = "xxxxxx"; + Snowflake snowflake = new Snowflake(); + + // 验签请求头前端需传入字段 + SortedMap headersMap = new TreeMap<>(); + headersMap.put("appId", appId); + headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); + headersMap.put("nonce", String.valueOf(snowflake.nextId())); + + // 客户端加签内容 + StringBuilder clientSignatureContent = new StringBuilder(); + // 请求头 + for (Map.Entry entry : headersMap.entrySet()) { + clientSignatureContent.append(entry.getKey()).append(entry.getValue()); + } + // 请求 url + clientSignatureContent.append("/admin-api/infra/demo01-contact/create"); + // 请求体 + String body = "{\n" + + " \"password\": \"1\",\n" + + " \"date\": \"2024-04-24 16:28:00\",\n" + + " \"user\": {\n" + + " \"area\": \"浦东新区\",\n" + + " \"1\": \"xx\",\n" + + " \"2\": \"xx\",\n" + + " \"province\": \"上海市\",\n" + + " \"data\": {\n" + + " \"99\": \"xx\",\n" + + " \"1\": \"xx\",\n" + + " \"100\": \"xx\",\n" + + " \"2\": \"xx\",\n" + + " \"3\": \"xx\",\n" + + " \"array\": [\n" + + " {\n" + + " \"3\": \"aa\",\n" + + " \"4\": \"aa\",\n" + + " \"2\": \"aa\",\n" + + " \"1\": \"aa\"\n" + + " },\n" + + " {\n" + + " \"99\": \"aa\",\n" + + " \"100\": \"aa\",\n" + + " \"88\": \"aa\",\n" + + " \"120\": \"aa\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"sex\": \"男\",\n" + + " \"name\": \"张三\",\n" + + " \"array\": [\n" + + " \"1\",\n" + + " \"3\",\n" + + " \"5\",\n" + + " \"2\"\n" + + " ]\n" + + " },\n" + + " \"username\": \"xiaoming\"\n" + + "}"; + clientSignatureContent.append(body); + + // 密钥 + clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); + System.out.println("加签内容:" + clientSignatureContent); + // 加签 + headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); + headersMap.put("Authorization", "Bearer xxx"); + + HttpRequest post = HttpUtil.createPost("http://localhost:48080/admin-api/infra/demo01-contact/create"); + post.addHeaders(headersMap); + post.body(body); + try (HttpResponse execute = post.execute()) { + System.out.println("执行结果==" + execute); + } + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java index 8e80fa591..e181edeb4 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java @@ -23,6 +23,10 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper { */ private final byte[] body; + public byte[] getBody() { + return body; + } + public CacheRequestBodyWrapper(HttpServletRequest request) { super(request); body = ServletUtils.getBodyBytes(request); From 5bc01901948de01ef94d6186389a566aa6b44858 Mon Sep 17 00:00:00 2001 From: zhougang <921366807@qq.com> Date: Sun, 19 May 2024 15:39:22 +0800 Subject: [PATCH 05/23] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91=20?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-framework/yudao-spring-boot-starter-protection/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml index 025bb7591..46326c63a 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml @@ -12,7 +12,7 @@ jar ${project.artifactId} - 服务保证,提供分布式锁、幂等、限流、熔断、API签名等等功能 + 服务保证,提供分布式锁、幂等、限流、熔断、API 签名等等功能 https://github.com/YunaiV/ruoyi-vue-pro From 9cc6528eba87cfe7539da4a61a69eb3fc31ef10c Mon Sep 17 00:00:00 2001 From: orchidblessing <63270044+orchidblessing@users.noreply.github.com> Date: Sun, 19 May 2024 18:05:38 +0800 Subject: [PATCH 06/23] =?UTF-8?q?fix=EF=BC=9A=E4=B8=8A=E4=BC=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=B5=8B=E8=AF=95=E6=97=B6pg=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B=E4=B8=8D=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 错误详情:Caused by: org.postgresql.util.PSQLException: ERROR: column "id" is of type bigint but expression is of type character varying --- .../yudao/module/infra/dal/dataobject/file/FileContentDO.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java index eda8a7264..80e18fc56 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java @@ -28,8 +28,8 @@ public class FileContentDO extends BaseDO { /** * 编号,数据库自增 */ - @TableId(type = IdType.INPUT) - private String id; + @TableId + private Long id; /** * 配置编号 * From 5f278ac23b392d6ecd9452cc50fdfc9e77af97e8 Mon Sep 17 00:00:00 2001 From: zhougang Date: Tue, 28 May 2024 15:09:01 +0800 Subject: [PATCH 07/23] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Signature.java => ApiSignature.java} | 16 ++++--- .../signature/core/aop/SignatureAspect.java | 43 ++++++------------- .../core/redis/SignatureRedisDAO.java | 3 +- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- .../core/filter/CacheRequestBodyWrapper.java | 4 -- 5 files changed, 26 insertions(+), 43 deletions(-) rename yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/{Signature.java => ApiSignature.java} (75%) diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java similarity index 75% rename from yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java rename to yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java index 1b7e12786..e338ae709 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/Signature.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.signature.core.annotation; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; /** @@ -14,12 +15,17 @@ import java.lang.annotation.*; @Documented @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -public @interface Signature { +public @interface ApiSignature { /** - * 同一个请求多长时间内有效 默认 10分钟 + * 同一个请求多长时间内有效 默认 60 秒 */ - long expireTime() default 600000L; + int timeout() default 60; + + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; // ========================== 签名参数 ========================== @@ -50,8 +56,4 @@ public @interface Signature { */ String sign() default "sign"; - /** - * url 客户端不需要传递,但是可以用来加签(如: /{id} 带有动态参数的 url ,如果没有动态参数可设置为 false 不进行加签) - */ - boolean urlEnable() default true; } diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java index dc1510465..a001419f8 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java @@ -1,12 +1,13 @@ package cn.iocoder.yudao.framework.signature.core.aop; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SignUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.framework.signature.core.annotation.Signature; +import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper; import jakarta.servlet.http.HttpServletRequest; @@ -15,7 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; -import org.springframework.util.Assert; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -25,7 +25,7 @@ import java.util.TreeMap; import java.util.concurrent.TimeUnit; /** - * 拦截声明了 {@link Signature} 注解的方法,实现签名 + * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 * * @author Zhougang */ @@ -37,9 +37,9 @@ public class SignatureAspect { private final SignatureRedisDAO signatureRedisDAO; @Before("@annotation(signature)") - public void beforePointCut(JoinPoint joinPoint, Signature signature) { + public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { - log.info("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), + log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), joinPoint.getArgs()); String message = StrUtil.blankToDefault(signature.message(), GlobalErrorCodeConstants.BAD_REQUEST.getMsg()); @@ -47,25 +47,22 @@ public class SignatureAspect { } } - private boolean verifySignature(Signature signature, HttpServletRequest request) { + private boolean verifySignature(ApiSignature signature, HttpServletRequest request) { if (!verifyHeaders(signature, request)) { return false; } // 校验 appId 是否能获取到对应的 appSecret String appId = request.getHeader(signature.appId()); String appSecret = signatureRedisDAO.getAppSecret(appId); - Assert.notNull(appSecret, "找不到对应的 appSecret"); + Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); // 请求头 SortedMap headersMap = getRequestHeaders(signature, request); - // 如:/user/{id} url 带有动态参数的情况 - String urlParams = signature.urlEnable() ? request.getServletPath() : ""; // 请求参数 String requestParams = getRequestParams(request); // 请求体 - String requestBody = getRequestBody(request); + String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : ""; // 生成服务端签名 - String serverSignature = SignUtil.signParamsSha256(headersMap, - urlParams + requestParams + requestBody + appSecret); + String serverSignature = SignUtil.signParamsSha256(headersMap, requestParams + requestBody + appSecret); // 客户端签名 String clientSignature = request.getHeader(signature.sign()); if (!StrUtil.equals(clientSignature, serverSignature)) { @@ -73,7 +70,7 @@ public class SignatureAspect { } String nonce = headersMap.get(signature.nonce()); // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) - signatureRedisDAO.setNonce(nonce, signature.expireTime(), TimeUnit.MILLISECONDS); + signatureRedisDAO.setNonce(nonce, signature.timeout() * 2L, signature.timeUnit()); return true; } @@ -87,7 +84,7 @@ public class SignatureAspect { * @param signature signature * @param request request */ - private boolean verifyHeaders(Signature signature, HttpServletRequest request) { + private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { String appId = request.getHeader(signature.appId()); if (StrUtil.isBlank(appId)) { return false; @@ -97,7 +94,7 @@ public class SignatureAspect { return false; } String nonce = request.getHeader(signature.nonce()); - if (StrUtil.isBlank(nonce) || nonce.length() < 10) { + if (StrUtil.isBlank(nonce) || StrUtil.length(nonce) < 10) { return false; } String sign = request.getHeader(signature.sign()); @@ -105,7 +102,7 @@ public class SignatureAspect { return false; } // 其他合法性校验 - long expireTime = signature.expireTime(); + long expireTime = signature.timeUnit().toMillis(signature.timeout()); long requestTimestamp = Long.parseLong(timestamp); // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); @@ -122,7 +119,7 @@ public class SignatureAspect { * @param request request * @return signature params */ - private SortedMap getRequestHeaders(Signature signature, HttpServletRequest request) { + private SortedMap getRequestHeaders(ApiSignature signature, HttpServletRequest request) { SortedMap sortedMap = new TreeMap<>(); sortedMap.put(signature.appId(), request.getHeader(signature.appId())); sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); @@ -154,17 +151,5 @@ public class SignatureAspect { return queryString.substring(1); } - /** - * 获取请求体参数 - * - * @param request request - * @return body - */ - private String getRequestBody(HttpServletRequest request) { - CacheRequestBodyWrapper requestWrapper = new CacheRequestBodyWrapper(request); - // 获取 body - return new String(requestWrapper.getBody(), StandardCharsets.UTF_8); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java index d00fe7f8d..326e238ee 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java @@ -42,8 +42,7 @@ public class SignatureRedisDAO { } public void setNonce(String nonce, long time, TimeUnit timeUnit) { - // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) - stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time * 2, timeUnit); + stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time, timeUnit); } private static String formatAppIdKey(String key) { diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d7cd3a883..6cab74e75 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration -cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration \ No newline at end of file +cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration +cn.iocoder.yudao.framework.signature.config.YudaoSignatureAutoConfiguration \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java index e181edeb4..8e80fa591 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java @@ -23,10 +23,6 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper { */ private final byte[] body; - public byte[] getBody() { - return body; - } - public CacheRequestBodyWrapper(HttpServletRequest request) { super(request); body = ServletUtils.getBodyBytes(request); From e0a6e3988bf177fdb6b900eb95152e0600402844 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 4 Jun 2024 19:10:48 +0800 Subject: [PATCH 08/23] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91framework=EF=BC=9A=E4=BC=98=E5=8C=96=20HTTP?= =?UTF-8?q?=20=E8=AF=B7=E6=B1=82=E7=AD=BE=E5=90=8D=E7=9A=84=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=201=E3=80=81=E5=8D=95=E6=B5=8B=E4=BB=8E=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95=EF=BC=8C=E6=94=B9=E6=88=90=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=202=E3=80=81SignatureAspect=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E6=98=93=E8=AF=BB=E6=80=A7=203=E3=80=81sign=20=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E8=B0=83=E6=95=B4=EF=BC=8C=E4=BD=BF=E7=94=A8=20querys?= =?UTF-8?q?tring=20+=20body=20+=20header=20+=20appsecret=20=E6=9B=B4?= =?UTF-8?q?=E5=AE=B9=E6=98=93=E7=90=86=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 4 +- .../YudaoApiSignatureAutoConfiguration.java | 28 +++ .../YudaoSignatureAutoConfiguration.java | 27 --- .../core/annotation/ApiSignature.java | 2 +- .../core/aop/ApiSignatureAspect.java | 169 ++++++++++++++++++ .../signature/core/aop/SignatureAspect.java | 155 ---------------- ...edisDAO.java => ApiSignatureRedisDAO.java} | 36 ++-- .../framework/signature/package-info.java | 6 + ...ot.autoconfigure.AutoConfiguration.imports | 2 +- .../signature/core/ApiSignatureTest.java | 75 ++++++++ .../signature/core/SignatureTest.java | 136 -------------- 11 files changed, 301 insertions(+), 339 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoApiSignatureAutoConfiguration.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java rename yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/{SignatureRedisDAO.java => ApiSignatureRedisDAO.java} (59%) create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java create mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java diff --git a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml index 46326c63a..7e7279eb8 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml @@ -38,8 +38,8 @@ - org.springframework.boot - spring-boot-starter-test + cn.iocoder.boot + yudao-spring-boot-starter-test test diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoApiSignatureAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoApiSignatureAutoConfiguration.java new file mode 100644 index 000000000..7c6842408 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoApiSignatureAutoConfiguration.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.framework.signature.config; + +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; +import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect; +import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * HTTP API 签名的自动配置类 + * + * @author Zhougang + */ +@AutoConfiguration(after = YudaoRedisAutoConfiguration.class) +public class YudaoApiSignatureAutoConfiguration { + + @Bean + public ApiSignatureAspect signatureAspect(ApiSignatureRedisDAO signatureRedisDAO) { + return new ApiSignatureAspect(signatureRedisDAO); + } + + @Bean + public ApiSignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) { + return new ApiSignatureRedisDAO(stringRedisTemplate); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java deleted file mode 100644 index 5b4b8e43e..000000000 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/config/YudaoSignatureAutoConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.framework.signature.config; - -import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; -import cn.iocoder.yudao.framework.signature.core.aop.SignatureAspect; -import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.core.StringRedisTemplate; - -/** - * @author Zhougang - */ -@AutoConfiguration(after = YudaoRedisAutoConfiguration.class) -public class YudaoSignatureAutoConfiguration { - - @Bean - public SignatureAspect signatureAspect(SignatureRedisDAO signatureRedisDAO) { - return new SignatureAspect(signatureRedisDAO); - } - - @Bean - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - public SignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) { - return new SignatureRedisDAO(stringRedisTemplate); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java index e338ae709..281bcec97 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/annotation/ApiSignature.java @@ -7,7 +7,7 @@ import java.util.concurrent.TimeUnit; /** - * 签名注解 + * HTTP API 签名注解 * * @author Zhougang */ diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java new file mode 100644 index 000000000..3259dac11 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java @@ -0,0 +1,169 @@ +package cn.iocoder.yudao.framework.signature.core.aop; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; +import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; + +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; + +/** + * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 + * + * @author Zhougang + */ +@Aspect +@Slf4j +@AllArgsConstructor +public class ApiSignatureAspect { + + private final ApiSignatureRedisDAO signatureRedisDAO; + + @Before("@annotation(signature)") + public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { + // 1. 验证通过,直接结束 + if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { + return; + } + + // 2. 验证不通过,抛出异常 + log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), + joinPoint.getArgs()); + throw new ServiceException(BAD_REQUEST.getCode(), + StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg())); + } + + public boolean verifySignature(ApiSignature signature, HttpServletRequest request) { + // 1.1 校验 Header + if (!verifyHeaders(signature, request)) { + return false; + } + // 1.2 校验 appId 是否能获取到对应的 appSecret + String appId = request.getHeader(signature.appId()); + String appSecret = signatureRedisDAO.getAppSecret(appId); + Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); + + // 2. 校验签名【重要!】 + String clientSignature = request.getHeader(signature.sign()); // 客户端签名 + String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串 + String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名 + if (ObjUtil.notEqual(clientSignature, serverSignature)) { + return false; + } + + // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) + String nonce = request.getHeader(signature.nonce()); + signatureRedisDAO.setNonce(nonce, signature.timeout() * 2, signature.timeUnit()); + return true; + } + + /** + * 校验请求头加签参数 + * + * 1. appId 是否为空 + * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟 + * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 + * 4. sign 是否为空 + * + * @param signature signature + * @param request request + * @return 是否校验 Header 通过 + */ + private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { + // 1. 非空校验 + String appId = request.getHeader(signature.appId()); + if (StrUtil.isBlank(appId)) { + return false; + } + String timestamp = request.getHeader(signature.timestamp()); + if (StrUtil.isBlank(timestamp)) { + return false; + } + String nonce = request.getHeader(signature.nonce()); + if (StrUtil.length(nonce) < 10) { + return false; + } + String sign = request.getHeader(signature.sign()); + if (StrUtil.isBlank(sign)) { + return false; + } + + // 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) + long expireTime = signature.timeUnit().toMillis(signature.timeout()); + long requestTimestamp = Long.parseLong(timestamp); + long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); + if (timestampDisparity > expireTime) { + return false; + } + + // 3. 检查 nonce 是否存在,有且仅能使用一次 + return signatureRedisDAO.getNonce(nonce) == null; + } + + /** + * 构建签名字符串 + * + * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥 + * + * @param signature signature + * @param request request + * @param appSecret appSecret + * @return 签名字符串 + */ + private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) { + SortedMap parameterMap = getRequestParameterMap(request); // 请求头 + SortedMap headerMap = getRequestHeaderMap(signature, request); // 请求参数 + String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体 + return MapUtil.join(parameterMap, "&", "=") + + requestBody + + MapUtil.join(headerMap, "&", "=") + + appSecret; + } + + /** + * 获取请求头加签参数 Map + * + * @param request 请求 + * @param signature 签名注解 + * @return signature params + */ + private static SortedMap getRequestHeaderMap(ApiSignature signature, HttpServletRequest request) { + SortedMap sortedMap = new TreeMap<>(); + sortedMap.put(signature.appId(), request.getHeader(signature.appId())); + sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); + sortedMap.put(signature.nonce(), request.getHeader(signature.nonce())); + return sortedMap; + } + + /** + * 获取请求参数 Map + * + * @param request 请求 + * @return queryParams + */ + private static SortedMap getRequestParameterMap(HttpServletRequest request) { + SortedMap sortedMap = new TreeMap<>(); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + sortedMap.put(entry.getKey(), entry.getValue()[0]); + } + return sortedMap; + } + +} + diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java deleted file mode 100644 index a001419f8..000000000 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/SignatureAspect.java +++ /dev/null @@ -1,155 +0,0 @@ -package cn.iocoder.yudao.framework.signature.core.aop; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SignUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; -import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; -import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper; -import jakarta.servlet.http.HttpServletRequest; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; - -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Objects; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.TimeUnit; - -/** - * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 - * - * @author Zhougang - */ -@Aspect -@Slf4j -@AllArgsConstructor -public class SignatureAspect { - - private final SignatureRedisDAO signatureRedisDAO; - - @Before("@annotation(signature)") - public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { - if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { - log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), - joinPoint.getArgs()); - String message = StrUtil.blankToDefault(signature.message(), - GlobalErrorCodeConstants.BAD_REQUEST.getMsg()); - throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), message); - } - } - - private boolean verifySignature(ApiSignature signature, HttpServletRequest request) { - if (!verifyHeaders(signature, request)) { - return false; - } - // 校验 appId 是否能获取到对应的 appSecret - String appId = request.getHeader(signature.appId()); - String appSecret = signatureRedisDAO.getAppSecret(appId); - Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); - // 请求头 - SortedMap headersMap = getRequestHeaders(signature, request); - // 请求参数 - String requestParams = getRequestParams(request); - // 请求体 - String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : ""; - // 生成服务端签名 - String serverSignature = SignUtil.signParamsSha256(headersMap, requestParams + requestBody + appSecret); - // 客户端签名 - String clientSignature = request.getHeader(signature.sign()); - if (!StrUtil.equals(clientSignature, serverSignature)) { - return false; - } - String nonce = headersMap.get(signature.nonce()); - // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) - signatureRedisDAO.setNonce(nonce, signature.timeout() * 2L, signature.timeUnit()); - return true; - } - - /** - * 校验请求头加签参数 - * 1.appId 是否为空 - * 2.timestamp 是否为空,请求是否已经超时,默认 10 分钟 - * 3.nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 - * 4.sign 是否为空 - * - * @param signature signature - * @param request request - */ - private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { - String appId = request.getHeader(signature.appId()); - if (StrUtil.isBlank(appId)) { - return false; - } - String timestamp = request.getHeader(signature.timestamp()); - if (StrUtil.isBlank(timestamp)) { - return false; - } - String nonce = request.getHeader(signature.nonce()); - if (StrUtil.isBlank(nonce) || StrUtil.length(nonce) < 10) { - return false; - } - String sign = request.getHeader(signature.sign()); - if (StrUtil.isBlank(sign)) { - return false; - } - // 其他合法性校验 - long expireTime = signature.timeUnit().toMillis(signature.timeout()); - long requestTimestamp = Long.parseLong(timestamp); - // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) - long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); - if (timestampDisparity > expireTime) { - return false; - } - String cacheNonce = signatureRedisDAO.getNonce(nonce); - return StrUtil.isBlank(cacheNonce); - } - - /** - * 获取请求头加签参数 - * - * @param request request - * @return signature params - */ - private SortedMap getRequestHeaders(ApiSignature signature, HttpServletRequest request) { - SortedMap sortedMap = new TreeMap<>(); - sortedMap.put(signature.appId(), request.getHeader(signature.appId())); - sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); - sortedMap.put(signature.nonce(), request.getHeader(signature.nonce())); - return sortedMap; - } - - /** - * 获取 URL 参数 - * - * @param request request - * @return queryParams - */ - private String getRequestParams(HttpServletRequest request) { - if (CollUtil.isEmpty(request.getParameterMap())) { - return ""; - } - Map requestParams = request.getParameterMap(); - // 获取 URL 请求参数 - SortedMap sortParamsMap = new TreeMap<>(); - for (Map.Entry entry : requestParams.entrySet()) { - sortParamsMap.put(entry.getKey(), entry.getValue()[0]); - } - // 按 key 排序 - StringBuilder queryString = new StringBuilder(); - for (Map.Entry entry : sortParamsMap.entrySet()) { - queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); - } - return queryString.substring(1); - } - -} - diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java similarity index 59% rename from yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java rename to yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java index 326e238ee..f4aa84910 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/SignatureRedisDAO.java +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java @@ -6,50 +6,52 @@ import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.TimeUnit; /** - * API 签名 Redis DAO + * HTTP API 签名 Redis DAO * * @author Zhougang */ @AllArgsConstructor -public class SignatureRedisDAO { +public class ApiSignatureRedisDAO { private final StringRedisTemplate stringRedisTemplate; /** * 验签随机数 - *

+ * * KEY 格式:signature_nonce:%s // 参数为 随机数 * VALUE 格式:String * 过期时间:不固定 */ - private static final String SIGNATURE_NONCE = "signature_nonce:%s"; + private static final String SIGNATURE_NONCE = "api_signature_nonce:%s"; /** * 签名密钥 - *

- * KEY 格式:signature_appid:%s // 参数为 appid + * + * HASH 结构 + * KEY 格式:%s // 参数为 appid * VALUE 格式:String - * 过期时间:预加载到 redis 永不过期 + * 过期时间:永不过期(预加载到 Redis) */ - private static final String SIGNATURE_APPID = "signature_appid:%s"; + private static final String SIGNATURE_APPID = "api_signature_app"; - public String getAppSecret(String appId) { - return stringRedisTemplate.opsForValue().get(formatAppIdKey(appId)); - } + // ========== 验签随机数 ========== public String getNonce(String nonce) { return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce)); } - public void setNonce(String nonce, long time, TimeUnit timeUnit) { - stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time, timeUnit); - } - - private static String formatAppIdKey(String key) { - return String.format(SIGNATURE_APPID, key); + public void setNonce(String nonce, int time, TimeUnit timeUnit) { + stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit); } private static String formatNonceKey(String key) { return String.format(SIGNATURE_NONCE, key); } + + // ========== 签名密钥 ========== + + public String getAppSecret(String appId) { + return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java new file mode 100644 index 000000000..4ebd87afb --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/package-info.java @@ -0,0 +1,6 @@ +/** + * HTTP API 签名,校验安全性 + * + * @see builder() + .put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build()); + when(request.getContentType()).thenReturn("application/json"); + when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test"))); + // mock 方法 + when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret); + + // 调用 + boolean result = apiSignatureAspect.verifySignature(apiSignature, request); + // 断言结果 + assertTrue(result); + // 断言调用 + verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS)); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java deleted file mode 100644 index a253cd51a..000000000 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/SignatureTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package cn.iocoder.yudao.framework.signature.core; - -import cn.hutool.core.lang.Snowflake; -import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import cn.hutool.http.HttpUtil; -import org.junit.jupiter.api.Test; - -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * {@link SignatureTest} 的单元测试 - */ -public class SignatureTest { - - @Test - public void testSignatureGet() { - String appId = "xxxxxx"; - Snowflake snowflake = new Snowflake(); - - // 验签请求头前端需传入字段 - SortedMap headersMap = new TreeMap<>(); - headersMap.put("appId", appId); - headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); - headersMap.put("nonce", String.valueOf(snowflake.nextId())); - - // 客户端加签内容 - StringBuilder clientSignatureContent = new StringBuilder(); - // 请求头 - for (Map.Entry entry : headersMap.entrySet()) { - clientSignatureContent.append(entry.getKey()).append(entry.getValue()); - } - // 请求 url - clientSignatureContent.append("/admin-api/infra/demo01-contact/get"); - // 请求参数 - SortedMap paramsMap = new TreeMap<>(); - paramsMap.put("id", "100"); - paramsMap.put("name", "张三"); - StringBuilder queryString = new StringBuilder(); - for (Map.Entry entry : paramsMap.entrySet()) { - queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); - } - clientSignatureContent.append(queryString.substring(1)); - // 密钥 - clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); - System.out.println("加签内容:" + clientSignatureContent); - // 加签 - headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); - headersMap.put("Authorization", "Bearer xxx"); - - HttpRequest get = HttpUtil.createGet("http://localhost:48080/admin-api/infra/demo01-contact/get?id=100&name=张三"); - get.addHeaders(headersMap); - System.out.println("执行结果==" + get.execute()); - } - - @Test - public void testSignaturePost() { - String appId = "xxxxxx"; - Snowflake snowflake = new Snowflake(); - - // 验签请求头前端需传入字段 - SortedMap headersMap = new TreeMap<>(); - headersMap.put("appId", appId); - headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); - headersMap.put("nonce", String.valueOf(snowflake.nextId())); - - // 客户端加签内容 - StringBuilder clientSignatureContent = new StringBuilder(); - // 请求头 - for (Map.Entry entry : headersMap.entrySet()) { - clientSignatureContent.append(entry.getKey()).append(entry.getValue()); - } - // 请求 url - clientSignatureContent.append("/admin-api/infra/demo01-contact/create"); - // 请求体 - String body = "{\n" + - " \"password\": \"1\",\n" + - " \"date\": \"2024-04-24 16:28:00\",\n" + - " \"user\": {\n" + - " \"area\": \"浦东新区\",\n" + - " \"1\": \"xx\",\n" + - " \"2\": \"xx\",\n" + - " \"province\": \"上海市\",\n" + - " \"data\": {\n" + - " \"99\": \"xx\",\n" + - " \"1\": \"xx\",\n" + - " \"100\": \"xx\",\n" + - " \"2\": \"xx\",\n" + - " \"3\": \"xx\",\n" + - " \"array\": [\n" + - " {\n" + - " \"3\": \"aa\",\n" + - " \"4\": \"aa\",\n" + - " \"2\": \"aa\",\n" + - " \"1\": \"aa\"\n" + - " },\n" + - " {\n" + - " \"99\": \"aa\",\n" + - " \"100\": \"aa\",\n" + - " \"88\": \"aa\",\n" + - " \"120\": \"aa\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"sex\": \"男\",\n" + - " \"name\": \"张三\",\n" + - " \"array\": [\n" + - " \"1\",\n" + - " \"3\",\n" + - " \"5\",\n" + - " \"2\"\n" + - " ]\n" + - " },\n" + - " \"username\": \"xiaoming\"\n" + - "}"; - clientSignatureContent.append(body); - - // 密钥 - clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); - System.out.println("加签内容:" + clientSignatureContent); - // 加签 - headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); - headersMap.put("Authorization", "Bearer xxx"); - - HttpRequest post = HttpUtil.createPost("http://localhost:48080/admin-api/infra/demo01-contact/create"); - post.addHeaders(headersMap); - post.body(body); - try (HttpResponse execute = post.execute()) { - System.out.println("执行结果==" + execute); - } - } - -} From f091727ad3678473e56ef9a53e94bd690ba3bde2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 4 Jun 2024 19:32:06 +0800 Subject: [PATCH 09/23] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91frame?= =?UTF-8?q?work=EF=BC=9A=E9=92=88=E5=AF=B9=E5=A4=A7=E9=87=91=E7=9A=84?= =?UTF-8?q?=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/tools/README.md | 8 ++++---- .../module/infra/dal/dataobject/logger/ApiErrorLogDO.java | 1 - .../iocoder/yudao/module/system/enums/common/SexEnum.java | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/tools/README.md b/sql/tools/README.md index 06eed8bc4..e3779c18b 100644 --- a/sql/tools/README.md +++ b/sql/tools/README.md @@ -67,8 +67,8 @@ exit ① 下载人大金仓 Docker 镜像: -> x86_64版本: https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar -> aarch64版本:https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar +> x86_64 版本: https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar +> aarch64 版本:https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar ② 加载镜像文件,在镜像 tar 文件所在目录运行: @@ -80,11 +80,11 @@ docker load -i x86_64/kdb_x86_64_V009R001C001B0025.tar ```Bash docker compose up -d kingbase -# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本 +# 注意:启动完 kingbase 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本 docker compose exec kingbase bash -c "exec ksql -Uroot -d test -f /tmp/schema.sql" ``` -**注意**: MyBatis, MyBatis Plus 目前不兼容人大金仓,推荐直接使用PostgreSQL JDBC 驱动,已经 url 配置方式连接数据库。 +**注意**: MyBatis、MyBatis Plus 目前不兼容人大金仓,推荐直接使用 PostgreSQL JDBC 驱动,已经 url 配置方式连接数据库。 ## 1.X 容器的销毁重建 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java index af48f073f..87d6974a9 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java @@ -25,7 +25,6 @@ import java.time.LocalDateTime; @KeySequence(value = "infra_api_error_log_seq") public class ApiErrorLogDO extends BaseDO { - /** * {@link #requestParams} 的最大长度 */ diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java index 40ac3cab1..c1e222bed 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java @@ -11,6 +11,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum SexEnum { + /** 男 */ MALE(1), /** 女 */ From 97e8c35f70afed96a380531bdea815ba83683ef6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 4 Jun 2024 19:39:11 +0800 Subject: [PATCH 10/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91CRM?= =?UTF-8?q?=EF=BC=9ACrmBusinessStatusDO=20=E7=BC=BA=E5=B0=91=20BaseDO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crm/dal/dataobject/business/CrmBusinessStatusDO.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java index 4ec8bbe4a..5d7b8bd13 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -19,7 +20,7 @@ import lombok.*; @Builder @NoArgsConstructor @AllArgsConstructor -public class CrmBusinessStatusDO { +public class CrmBusinessStatusDO extends BaseDO { /** * 主键 From 5e0a8b44c42fc47a843e054b0486e1ed0b34f27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E9=AB=98?= <171376172@qq.com> Date: Thu, 6 Jun 2024 10:01:47 +0000 Subject: [PATCH 11/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=8C=87=E5=AE=9A=E6=9F=90?= =?UTF-8?q?=E4=B8=80=E5=B9=B4=E6=89=A7=E8=A1=8C=E6=97=B6=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E4=BC=9A=E6=89=A7=E8=A1=8C=E4=B8=80=E6=AC=A1=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E6=9F=A5=E7=9C=8B=E4=BB=BB=E5=8A=A1=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E4=BC=9A=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 高高 <171376172@qq.com> --- .../iocoder/yudao/framework/quartz/core/util/CronUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java index 9b5a2bff7..a97a8e1eb 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java @@ -46,6 +46,10 @@ public class CronUtils { List nextTimes = new ArrayList<>(n); for (int i = 0; i < n; i++) { Date nextTime = cron.getNextValidTimeAfter(now); + if (nextTime == null) { + // 如果 nextTime 为 null,说明没有更多的有效时间,退出循环 + break; + } nextTimes.add(LocalDateTimeUtil.of(nextTime)); // 切换现在,为下一个触发时间; now = nextTime; From d1c6d741c3f771e1b1198ea8e731c40882f9f2cc Mon Sep 17 00:00:00 2001 From: DevDengChao <2325690622@qq.com> Date: Fri, 7 Jun 2024 10:47:28 +0800 Subject: [PATCH 12/23] =?UTF-8?q?Revert=20"fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=20DeliveryExpressDO=20=E6=97=B6=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=20id=20=E8=BF=87=E5=A4=A7=E7=9A=84=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5632c515271943200f4b189204947632bf77ccc9. --- .../trade/dal/dataobject/delivery/DeliveryExpressDO.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java index c6b7fbf33..265066d83 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressDO.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.delivery; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -21,7 +20,7 @@ public class DeliveryExpressDO extends BaseDO { /** * 编号,自增 */ - @TableId(type = IdType.AUTO) + @TableId private Long id; /** From 919066dbf0c8df937a7062c11e20c1655a1af221 Mon Sep 17 00:00:00 2001 From: DevDengChao <2325690622@qq.com> Date: Fri, 7 Jun 2024 10:49:11 +0800 Subject: [PATCH 13/23] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8=20IdUtil.r?= =?UTF-8?q?andomUUID=20=E4=BB=A3=E6=9B=BF=20UUID.randomUUID()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trade/service/order/TradeOrderUpdateServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index 2f78dbe08..108c68a2c 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.trade.service.order; +import cn.hutool.core.util.IdUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; @@ -38,7 +39,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import java.time.Duration; -import java.util.UUID; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -101,7 +101,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { public void setUp() { when(tradeOrderProperties.getAppId()).thenReturn(888L); when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1)); - when(tradeNoRedisDAO.generate(anyString())).thenReturn(UUID.randomUUID().toString()); + when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID()); } // @Test From f2b7ad5092b07db95e4b7271c0d1cf0593e06b1b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Jun 2024 17:33:54 +0800 Subject: [PATCH 14/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91MALL?= =?UTF-8?q?=EF=BC=9ATradeOrderUpdateService=20=E7=9A=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/trade/dal/dataobject/order/TradeOrderDO.java | 2 +- .../trade/service/order/TradeOrderUpdateServiceTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index ba7e689f8..b127004aa 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -322,7 +322,7 @@ public class TradeOrderDO extends BaseDO { * * 关联 CombinationRecordDO 的 headId 字段 */ - private Long combinationHeadId; // FIXME 2024/5/15: "团长" 应该译作 leader / starter 而不是 head + private Long combinationHeadId; /** * 拼团记录编号 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index 108c68a2c..e9677e665 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java @@ -33,8 +33,8 @@ import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; @@ -53,7 +53,7 @@ import static org.mockito.Mockito.when; * @author LeeYan9 * @since 2022-09-07 */ -@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +@Disabled // TODO 芋艿:后续 fix 补充的单测 @Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class, DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class }) @@ -94,7 +94,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { private TradePriceCalculator tradePriceCalculator; @MockBean private NotifyMessageSendApi notifyMessageSendApi; - @Autowired + @MockBean private DeliveryExpressService deliveryExpressService; @BeforeEach From 7eaddd16f9cec7a4eab5bf036fb56e9efd2b20f0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Jun 2024 17:57:13 +0800 Subject: [PATCH 15/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=8C=87=E5=AE=9A=E6=9F=90?= =?UTF-8?q?=E4=B8=80=E5=B9=B4=E6=89=A7=E8=A1=8C=E6=97=B6=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E4=BC=9A=E6=89=A7=E8=A1=8C=E4=B8=80=E6=AC=A1=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E6=9F=A5=E7=9C=8B=E4=BB=BB=E5=8A=A1=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E4=BC=9A=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/framework/quartz/core/util/CronUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java index a97a8e1eb..5658fa302 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java @@ -34,24 +34,24 @@ public class CronUtils { * @return 满足条件的执行时间 */ public static List getNextTimes(String cronExpression, int n) { - // 获得 CronExpression 对象 + // 1. 获得 CronExpression 对象 CronExpression cron; try { cron = new CronExpression(cronExpression); } catch (ParseException e) { throw new IllegalArgumentException(e.getMessage()); } - // 从当前开始计算,n 个满足条件的 + // 2. 从当前开始计算,n 个满足条件的 Date now = new Date(); List nextTimes = new ArrayList<>(n); for (int i = 0; i < n; i++) { Date nextTime = cron.getNextValidTimeAfter(now); + // 2.1 如果 nextTime 为 null,说明没有更多的有效时间,退出循环 if (nextTime == null) { - // 如果 nextTime 为 null,说明没有更多的有效时间,退出循环 break; } nextTimes.add(LocalDateTimeUtil.of(nextTime)); - // 切换现在,为下一个触发时间; + // 2.2 切换现在,为下一个触发时间; now = nextTime; } return nextTimes; From 088871d083cd03f25682e02cc2aedebefa0998d1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Jun 2024 18:00:46 +0800 Subject: [PATCH 16/23] =?UTF-8?q?bugfix:=20=E5=AF=BC=E5=85=A5=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=9C=B0=E5=9D=80=E7=9A=84=E6=97=B6=E5=80=99=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E8=8E=B7=E5=8F=96areaId=EF=BC=88=E5=8F=AA=E9=92=88?= =?UTF-8?q?=E5=AF=B9=E5=9B=9B=E4=B8=AA=E7=9B=B4=E8=BE=96=E5=B8=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/area.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/area.csv b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/area.csv index 06954ba6c..0dd830e22 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/area.csv +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/area.csv @@ -247,15 +247,15 @@ id,name,type,parentId 246,英属印度洋领地,1,0 247,东萨摩亚,1,0 248,诺福克岛,1,0 -110000,北京,2,1 -120000,天津,2,1 +110000,北京市,2,1 +120000,天津市,2,1 130000,河北省,2,1 140000,山西省,2,1 150000,内蒙古自治区,2,1 210000,辽宁省,2,1 220000,吉林省,2,1 230000,黑龙江省,2,1 -310000,上海,2,1 +310000,上海市,2,1 320000,江苏省,2,1 330000,浙江省,2,1 340000,安徽省,2,1 @@ -268,7 +268,7 @@ id,name,type,parentId 440000,广东省,2,1 450000,广西壮族自治区,2,1 460000,海南省,2,1 -500000,重庆,2,1 +500000,重庆市,2,1 510000,四川省,2,1 520000,贵州省,2,1 530000,云南省,2,1 From 9a9d95f04527bbd51b294bf641dcb4ce6e3bfd06 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Jun 2024 18:04:02 +0800 Subject: [PATCH 17/23] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E5=92=8C=E8=A1=A8=E5=90=8D=E7=A7=B0=E4=B8=80=E6=A0=B7=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=A6=82=E2=80=9Cshop=E2=80=9D?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=9A=84=E2=80=9Cshop=E2=80=9D=E8=A1=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90=E4=BB=A3=E7=A0=81=E6=97=B6shop=E9=83=BD?= =?UTF-8?q?=E8=A2=AB=E7=A7=BB=E9=99=A4=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/infra/service/codegen/inner/CodegenEngine.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index e4b7e18c8..4e742539d 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -342,7 +342,8 @@ public class CodegenEngine { // className 相关 // 去掉指定前缀,将 TestDictType 转换成 DictType. 因为在 create 等方法后,不需要带上 Test 前缀 - String simpleClassName = removePrefix(table.getClassName(), upperFirst(table.getModuleName())); + String simpleClassName = equalsAnyIgnoreCase(table.getClassName(), table.getModuleName()) ? table.getClassName() + : removePrefix(table.getClassName(), upperFirst(table.getModuleName())); bindingMap.put("simpleClassName", simpleClassName); bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType,用于变量 From 312f7e4890a1d3b5102e54b199234c621f8e6887 Mon Sep 17 00:00:00 2001 From: "dongdong.xiang" Date: Sat, 15 Jun 2024 22:19:07 +0800 Subject: [PATCH 18/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=8F=9C=E5=8D=95=E7=B2=BE=E7=AE=80=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=8E=92=E9=99=A4=E7=88=B6=20ID=20=E9=9D=9E=200=20=E7=9A=84?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/auth/AuthController.java | 1 - .../admin/permission/MenuController.java | 2 +- .../service/permission/MenuServiceImpl.java | 58 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index cb002d3a2..8e2871e48 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -109,7 +109,6 @@ public class AuthController { // 1.3 获得菜单列表 Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); List menuList = menuService.getMenuList(menuIds); - menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单 // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java index 104141751..1804096a1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java @@ -71,7 +71,7 @@ public class MenuController { "在多租户的场景下,会只返回租户所在套餐有的菜单") public CommonResult> getSimpleMenuList() { List list = menuService.getMenuListByTenant( - new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + new MenuListReqVO()); list.sort(Comparator.comparing(MenuDO::getSort)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index beee296d1..88aed2a92 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuSaveVO; @@ -13,14 +14,15 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collection; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -106,12 +108,60 @@ public class MenuServiceImpl implements MenuService { @Override public List getMenuListByTenant(MenuListReqVO reqVO) { - List menus = getMenuList(reqVO); + // 查询所有菜单,并过滤掉关闭的节点 + List menus = filterClosedNodes(getMenuList(reqVO)); // 开启多租户的情况下,需要过滤掉未开通的菜单 tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); return menus; } + /** + * 过滤关闭的菜单节点及其子节点 + * + * @param menuList 所有菜单列表 + * @return 过滤后的菜单列表 + */ + public List filterClosedNodes(List menuList) { + // 根据parentId快速查找子节点 + Map> childrenMap = menuList.stream() + .collect(Collectors.groupingBy(MenuDO::getParentId)); + + // 所有关闭的节点ID + Set closedNodeIds = new HashSet<>(); + + // 标记所有关闭的节点 + for (MenuDO menu : menuList) { + if (Objects.equals(menu.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { + markClosedNodes(menu.getId(), childrenMap, closedNodeIds); + } + } + + // 过滤掉关闭的节点及其子节点 + return menuList.stream() + .filter(menu -> !closedNodeIds.contains(menu.getId())) + .collect(Collectors.toList()); + } + + /** + * 递归标记关闭的节点及其子节点 + * + * @param nodeId 节点ID + * @param childrenMap 子节点Map + * @param closedNodeIds 关闭节点ID集合 + */ + private void markClosedNodes(Long nodeId, Map> childrenMap, + Set closedNodeIds) { + closedNodeIds.add(nodeId); + List children = childrenMap.get(nodeId); + if (CollectionUtils.isNotEmpty(children)) { + for (MenuDO child : children) { + markClosedNodes(child.getId(), childrenMap, closedNodeIds); + } + } + } + + @Override public List getMenuList(MenuListReqVO reqVO) { return menuMapper.selectList(reqVO); @@ -135,7 +185,7 @@ public class MenuServiceImpl implements MenuService { if (CollUtil.isEmpty(ids)) { return Lists.newArrayList(); } - return menuMapper.selectBatchIds(ids); + return filterClosedNodes(menuMapper.selectBatchIds(ids)); } /** From 6419aef36c7a8496407e538bfbfae102c8e49c16 Mon Sep 17 00:00:00 2001 From: "dongdong.xiang" Date: Sun, 16 Jun 2024 21:21:55 +0800 Subject: [PATCH 19/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=8F=9C=E5=8D=95=E7=B2=BE=E7=AE=80=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=8E=92=E9=99=A4=E7=88=B6=20ID=20=E9=9D=9E=200=20=E7=9A=84?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/auth/AuthController.java | 2 ++ .../admin/permission/MenuController.java | 4 ++- .../service/permission/MenuService.java | 8 ++++++ .../service/permission/MenuServiceImpl.java | 28 +++++++++++-------- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 8e2871e48..20e9e9d6b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -109,6 +109,8 @@ public class AuthController { // 1.3 获得菜单列表 Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); List menuList = menuService.getMenuList(menuIds); + // 过滤掉关闭的菜单及其子菜单 + menuList = menuService.filterClosedMenus(menuList); // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java index 1804096a1..0ec9ac804 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java @@ -71,7 +71,9 @@ public class MenuController { "在多租户的场景下,会只返回租户所在套餐有的菜单") public CommonResult> getSimpleMenuList() { List list = menuService.getMenuListByTenant( - new MenuListReqVO()); + new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + // 过滤掉关闭的菜单及其子菜单 + list = menuService.filterClosedMenus(list); list.sort(Comparator.comparing(MenuDO::getSort)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java index adc2b3151..ed21ce378 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java @@ -52,6 +52,14 @@ public interface MenuService { */ List getMenuListByTenant(MenuListReqVO reqVO); + /** + * 过滤掉关闭的菜单及其子菜单 + * + * @param menuList + * @return + */ + List filterClosedMenus(List menuList); + /** * 筛选菜单列表 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index 88aed2a92..bba798f84 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -109,7 +109,7 @@ public class MenuServiceImpl implements MenuService { @Override public List getMenuListByTenant(MenuListReqVO reqVO) { // 查询所有菜单,并过滤掉关闭的节点 - List menus = filterClosedNodes(getMenuList(reqVO)); + List menus = getMenuList(reqVO); // 开启多租户的情况下,需要过滤掉未开通的菜单 tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); return menus; @@ -119,9 +119,13 @@ public class MenuServiceImpl implements MenuService { * 过滤关闭的菜单节点及其子节点 * * @param menuList 所有菜单列表 - * @return 过滤后的菜单列表 + * @return */ - public List filterClosedNodes(List menuList) { + @Override + public List filterClosedMenus(List menuList) { + if(CollectionUtils.isEmpty(menuList)){ + return Collections.emptyList(); + } // 根据parentId快速查找子节点 Map> childrenMap = menuList.stream() .collect(Collectors.groupingBy(MenuDO::getParentId)); @@ -135,8 +139,7 @@ public class MenuServiceImpl implements MenuService { markClosedNodes(menu.getId(), childrenMap, closedNodeIds); } } - - // 过滤掉关闭的节点及其子节点 + // 移除掉关闭的节点及其子节点 return menuList.stream() .filter(menu -> !closedNodeIds.contains(menu.getId())) .collect(Collectors.toList()); @@ -152,12 +155,13 @@ public class MenuServiceImpl implements MenuService { private void markClosedNodes(Long nodeId, Map> childrenMap, Set closedNodeIds) { - closedNodeIds.add(nodeId); - List children = childrenMap.get(nodeId); - if (CollectionUtils.isNotEmpty(children)) { - for (MenuDO child : children) { - markClosedNodes(child.getId(), childrenMap, closedNodeIds); - } + // 如果已经标记过,则直接返回 + if (!closedNodeIds.add(nodeId)) { + return; + } + List children = childrenMap.getOrDefault(nodeId,Collections.emptyList()); + for (MenuDO child : children) { + markClosedNodes(child.getId(), childrenMap, closedNodeIds); } } @@ -185,7 +189,7 @@ public class MenuServiceImpl implements MenuService { if (CollUtil.isEmpty(ids)) { return Lists.newArrayList(); } - return filterClosedNodes(menuMapper.selectBatchIds(ids)); + return menuMapper.selectBatchIds(ids); } /** From 1c7ba5c0d6f2dbb95e916aa45eec32a94103a96f Mon Sep 17 00:00:00 2001 From: "dongdong.xiang" Date: Sun, 16 Jun 2024 22:41:32 +0800 Subject: [PATCH 20/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=8F=9C=E5=8D=95=E7=B2=BE=E7=AE=80=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=8E=92=E9=99=A4=E7=88=B6=20ID=20=E9=9D=9E=200=20=E7=9A=84?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/admin/auth/AuthController.java | 3 ++- .../module/system/service/permission/MenuServiceImpl.java | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 20e9e9d6b..6ebaba212 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -109,9 +109,10 @@ public class AuthController { // 1.3 获得菜单列表 Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); List menuList = menuService.getMenuList(menuIds); - // 过滤掉关闭的菜单及其子菜单 + // 过滤掉关闭的菜单 menuList = menuService.filterClosedMenus(menuList); + menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单 // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index bba798f84..104da073d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -126,16 +126,18 @@ public class MenuServiceImpl implements MenuService { if(CollectionUtils.isEmpty(menuList)){ return Collections.emptyList(); } + List allMenuList = getMenuList(); + // 根据parentId快速查找子节点 - Map> childrenMap = menuList.stream() + Map> childrenMap = allMenuList.stream() .collect(Collectors.groupingBy(MenuDO::getParentId)); // 所有关闭的节点ID Set closedNodeIds = new HashSet<>(); // 标记所有关闭的节点 - for (MenuDO menu : menuList) { - if (Objects.equals(menu.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { + for (MenuDO menu : allMenuList) { + if (!Objects.equals(menu.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { markClosedNodes(menu.getId(), childrenMap, closedNodeIds); } } From 7deaf1cdbf3f59151c2a466a5d227ab627a959a4 Mon Sep 17 00:00:00 2001 From: "dongdong.xiang" Date: Sun, 16 Jun 2024 22:45:02 +0800 Subject: [PATCH 21/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=8F=9C=E5=8D=95=E7=B2=BE=E7=AE=80=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=8E=92=E9=99=A4=E7=88=B6=20ID=20=E9=9D=9E=200=20=E7=9A=84?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/system/controller/admin/auth/AuthController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 6ebaba212..1192d15b0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -112,7 +112,6 @@ public class AuthController { // 过滤掉关闭的菜单 menuList = menuService.filterClosedMenus(menuList); - menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单 // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); } From fbfa1050d2d587784ae33cf229bf9fbc79c39bf5 Mon Sep 17 00:00:00 2001 From: "dongdong.xiang" Date: Mon, 17 Jun 2024 22:19:47 +0800 Subject: [PATCH 22/23] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/auth/AuthController.java | 3 +- .../admin/permission/MenuController.java | 3 +- .../service/permission/MenuService.java | 6 +- .../service/permission/MenuServiceImpl.java | 69 ++++++++++--------- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 1192d15b0..afdf82481 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -109,8 +109,7 @@ public class AuthController { // 1.3 获得菜单列表 Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); List menuList = menuService.getMenuList(menuIds); - // 过滤掉关闭的菜单 - menuList = menuService.filterClosedMenus(menuList); + menuList = menuService.filterDisableMenus(menuList); // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java index 0ec9ac804..b6d067f1f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java @@ -72,8 +72,7 @@ public class MenuController { public CommonResult> getSimpleMenuList() { List list = menuService.getMenuListByTenant( new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); - // 过滤掉关闭的菜单及其子菜单 - list = menuService.filterClosedMenus(list); + list = menuService.filterDisableMenus(list); list.sort(Comparator.comparing(MenuDO::getSort)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java index ed21ce378..5c3700db9 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java @@ -55,10 +55,10 @@ public interface MenuService { /** * 过滤掉关闭的菜单及其子菜单 * - * @param menuList - * @return + * @param list 菜单列表 + * @return List 过滤后的菜单列表 */ - List filterClosedMenus(List menuList); + List filterDisableMenus(List list); /** * 筛选菜单列表 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index 104da073d..7cc293258 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -119,54 +120,54 @@ public class MenuServiceImpl implements MenuService { * 过滤关闭的菜单节点及其子节点 * * @param menuList 所有菜单列表 - * @return + * @return List 过滤后的菜单列表 */ @Override - public List filterClosedMenus(List menuList) { + public List filterDisableMenus(List menuList) { if(CollectionUtils.isEmpty(menuList)){ return Collections.emptyList(); } - List allMenuList = getMenuList(); - // 根据parentId快速查找子节点 - Map> childrenMap = allMenuList.stream() - .collect(Collectors.groupingBy(MenuDO::getParentId)); + Map menuMap = new HashMap<>(); - // 所有关闭的节点ID - Set closedNodeIds = new HashSet<>(); + for (MenuDO menuDO : menuList) { + menuMap.put(menuDO.getId(),menuDO); + } - // 标记所有关闭的节点 - for (MenuDO menu : allMenuList) { - if (!Objects.equals(menu.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { - markClosedNodes(menu.getId(), childrenMap, closedNodeIds); + // 存下递归搜索过被禁用的菜单,防止重复的搜索 + Set disabledMenuIds = new HashSet<>(); + + List enabledMenus = new ArrayList<>(); + for (MenuDO menu : menuList) { + if (!isMenuDisabled(menu, menuMap, disabledMenuIds)) { + enabledMenus.add(menu); } } - // 移除掉关闭的节点及其子节点 - return menuList.stream() - .filter(menu -> !closedNodeIds.contains(menu.getId())) - .collect(Collectors.toList()); + return enabledMenus; } - /** - * 递归标记关闭的节点及其子节点 - * - * @param nodeId 节点ID - * @param childrenMap 子节点Map - * @param closedNodeIds 关闭节点ID集合 - */ - private void markClosedNodes(Long nodeId, Map> childrenMap, - Set closedNodeIds) { - // 如果已经标记过,则直接返回 - if (!closedNodeIds.add(nodeId)) { - return; + private boolean isMenuDisabled(MenuDO node, Map menuMap, Set disabledMenuIds) { + if (disabledMenuIds.contains(node.getId())) { + return true; } - List children = childrenMap.getOrDefault(nodeId,Collections.emptyList()); - for (MenuDO child : children) { - markClosedNodes(child.getId(), childrenMap, closedNodeIds); - } - } + Long parentId = node.getParentId(); + if (parentId == 0) { + if (!node.getStatus().equals(CommonStatusEnum.ENABLE.getStatus())) { + disabledMenuIds.add(node.getId()); + return true; + } + return false; + } + + MenuDO parent = menuMap.get(parentId); + if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuIds)) { + disabledMenuIds.add(node.getId()); + return true; + } + + return false; + } @Override public List getMenuList(MenuListReqVO reqVO) { From 3da2449a151143333985f739545b762459e5aafe Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 18 Jun 2024 09:20:49 +0800 Subject: [PATCH 23/23] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91SYSTE?= =?UTF-8?q?M=EF=BC=9A=E8=8E=B7=E5=8F=96=E8=8F=9C=E5=8D=95=E7=B2=BE?= =?UTF-8?q?=E7=AE=80=E4=BF=A1=E6=81=AF=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E6=8E=92=E9=99=A4=E7=88=B6=20ID=20=E9=9D=9E?= =?UTF-8?q?=200=20=E7=9A=84=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/permission/MenuService.java | 2 +- .../service/permission/MenuServiceImpl.java | 48 ++++++++----------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java index 5c3700db9..d74dc6134 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java @@ -56,7 +56,7 @@ public interface MenuService { * 过滤掉关闭的菜单及其子菜单 * * @param list 菜单列表 - * @return List 过滤后的菜单列表 + * @return 过滤后的菜单列表 */ List filterDisableMenus(List list); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index 7cc293258..730958f82 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO; @@ -14,7 +15,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Lazy; @@ -22,11 +22,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -116,56 +115,47 @@ public class MenuServiceImpl implements MenuService { return menus; } - /** - * 过滤关闭的菜单节点及其子节点 - * - * @param menuList 所有菜单列表 - * @return List 过滤后的菜单列表 - */ @Override public List filterDisableMenus(List menuList) { - if(CollectionUtils.isEmpty(menuList)){ + if (CollUtil.isEmpty(menuList)){ return Collections.emptyList(); } + Map menuMap = convertMap(menuList, MenuDO::getId); - Map menuMap = new HashMap<>(); - - for (MenuDO menuDO : menuList) { - menuMap.put(menuDO.getId(),menuDO); - } - - // 存下递归搜索过被禁用的菜单,防止重复的搜索 - Set disabledMenuIds = new HashSet<>(); - + // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果 List enabledMenus = new ArrayList<>(); + Set disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单,防止重复的搜索 for (MenuDO menu : menuList) { - if (!isMenuDisabled(menu, menuMap, disabledMenuIds)) { - enabledMenus.add(menu); + if (isMenuDisabled(menu, menuMap, disabledMenuCache)) { + continue; } + enabledMenus.add(menu); } return enabledMenus; } - private boolean isMenuDisabled(MenuDO node, Map menuMap, Set disabledMenuIds) { - if (disabledMenuIds.contains(node.getId())) { + private boolean isMenuDisabled(MenuDO node, Map menuMap, Set disabledMenuCache) { + // 如果已经判定是禁用的节点,直接结束 + if (disabledMenuCache.contains(node.getId())) { return true; } + // 1. 遍历到 parentId 为根节点,则无需判断 Long parentId = node.getParentId(); - if (parentId == 0) { - if (!node.getStatus().equals(CommonStatusEnum.ENABLE.getStatus())) { - disabledMenuIds.add(node.getId()); + if (ObjUtil.equal(parentId, ID_ROOT)) { + if (CommonStatusEnum.isDisable(node.getStatus())) { + disabledMenuCache.add(node.getId()); return true; } return false; } + // 2. 继续遍历 parent 节点 MenuDO parent = menuMap.get(parentId); - if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuIds)) { - disabledMenuIds.add(node.getId()); + if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) { + disabledMenuCache.add(node.getId()); return true; } - return false; }