From b4eee35c1fcd2b15a054274c1e4defef952e1fc9 Mon Sep 17 00:00:00 2001 From: dev <182542500@qq.com> Date: Thu, 17 Apr 2025 16:56:45 +0800 Subject: [PATCH] =?UTF-8?q?4.12=E6=96=B0=E9=9C=80=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BizInquiryRequestController.java | 8 + .../controller/BizShipmentPlanController.java | 10 + .../asinkj/amz/domain/BizInquiryRequest.java | 5 + .../asinkj/amz/domain/BizLogisticsQuote.java | 6 + .../amz/domain/vo/BizInquiryRequestVo.java | 5 +- .../amz/domain/vo/BizLogisticsQuoteVo.java | 5 + .../amz/domain/vo/InquiryRequestItemVo.java | 58 +++++ .../service/IBizInquiryRequestService.java | 4 + .../amz/service/IBizShipmentPlanService.java | 4 + .../impl/BizInquiryRequestServiceImpl.java | 230 ++++++++++++++++-- .../impl/BizLogisticsOrderServiceImpl.java | 20 +- .../impl/BizShipmentPlanServiceImpl.java | 25 +- .../redis/annotation/DistributedLock.java | 31 +++ .../redis/aspect/DistributedLockAspect.java | 100 ++++++++ .../asinkj/common/redis/utils/RedisUtils.java | 20 ++ 15 files changed, 507 insertions(+), 24 deletions(-) create mode 100644 asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/InquiryRequestItemVo.java create mode 100644 asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/annotation/DistributedLock.java create mode 100644 asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/aspect/DistributedLockAspect.java diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizInquiryRequestController.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizInquiryRequestController.java index cdd26c9..5676de6 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizInquiryRequestController.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizInquiryRequestController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import org.asinkj.amz.domain.vo.InquiryRequestItemVo; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.asinkj.common.idempotent.annotation.RepeatSubmit; @@ -72,6 +73,13 @@ public class BizInquiryRequestController extends BaseController { return bizInquiryRequestService.createWithDesAndChannel(destination, channelId,date); } + @SaCheckPermission("amz:shipmentPlan:edit") + @PostMapping("/create/batch") + @RepeatSubmit + public R batchCreateWithItems(@RequestBody List items) { + return bizInquiryRequestService.batchCreateWithItems(items); + } + /** * 导出物流询价列表 */ diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizShipmentPlanController.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizShipmentPlanController.java index 9afd6ea..1e23736 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizShipmentPlanController.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/controller/BizShipmentPlanController.java @@ -89,6 +89,16 @@ public class BizShipmentPlanController extends BaseController { return toAjax(bizShipmentPlanService.insertByBo(bo)); } + @SaCheckPermission("amz:shipmentPlan:add") + @Log(title = "货件计划", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/update/list/{status}") + public R updateList( @RequestBody List list, @PathVariable String status) { + + bizShipmentPlanService.updateList(list, status); + return R.ok(); + } + /** * 修改货件计划 */ diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizInquiryRequest.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizInquiryRequest.java index b02bbe1..bbfa1b4 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizInquiryRequest.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizInquiryRequest.java @@ -1,5 +1,6 @@ package org.asinkj.amz.domain; +import org.asinkj.asinking.entity.FbaShipmentApiResponse; import org.asinkj.common.tenant.core.TenantEntity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; @@ -7,6 +8,7 @@ import lombok.EqualsAndHashCode; import java.time.LocalDateTime; import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import org.springframework.format.annotation.DateTimeFormat; @@ -104,4 +106,7 @@ public class BizInquiryRequest extends TenantEntity { private Date quoteDate; + private FbaShipmentApiResponse.Address shipToAddress; + + } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizLogisticsQuote.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizLogisticsQuote.java index ec9b079..9e76238 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizLogisticsQuote.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/BizLogisticsQuote.java @@ -110,4 +110,10 @@ public class BizLogisticsQuote extends TenantEntity { private Long inquiryId; + /** + * 后端物流类型 + */ + private String backLogisticsType; + + } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizInquiryRequestVo.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizInquiryRequestVo.java index 22ddf1b..f95469e 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizInquiryRequestVo.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizInquiryRequestVo.java @@ -2,10 +2,12 @@ package org.asinkj.amz.domain.vo; import java.time.LocalDateTime; import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import org.asinkj.amz.domain.BizInquiryRequest; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; +import org.asinkj.asinking.entity.FbaShipmentApiResponse; import org.asinkj.common.excel.annotation.ExcelDictFormat; import org.asinkj.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; @@ -16,7 +18,6 @@ import java.io.Serializable; import java.util.Date; - /** * 物流询价视图对象 biz_inquiry_request * @@ -120,5 +121,7 @@ public class BizInquiryRequestVo implements Serializable { */ private Date quoteDate; + private FbaShipmentApiResponse.Address shipToAddress; + } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizLogisticsQuoteVo.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizLogisticsQuoteVo.java index b510d33..6056465 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizLogisticsQuoteVo.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/BizLogisticsQuoteVo.java @@ -129,4 +129,9 @@ public class BizLogisticsQuoteVo implements Serializable { + + private String backLogisticsType; + + + } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/InquiryRequestItemVo.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/InquiryRequestItemVo.java new file mode 100644 index 0000000..831fcd6 --- /dev/null +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/domain/vo/InquiryRequestItemVo.java @@ -0,0 +1,58 @@ +package org.asinkj.amz.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.asinkj.amz.domain.BizInquiryRequest; +import org.asinkj.asinking.entity.FbaShipmentApiResponse; +import org.asinkj.common.excel.annotation.ExcelDictFormat; +import org.asinkj.common.excel.convert.ExcelDictConvert; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Date; + + +/** + * 物流询价视图对象 biz_inquiry_request + * + * @author shuo hu + * @date 2025-03-21 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = BizInquiryRequest.class) +public class InquiryRequestItemVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String shipmentId; + /** + * 目的地(存储四级行政编码,如CN310115) + */ + @ExcelProperty(value = "目的地", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "存=储四级行政编码,如CN310115") + private String destination; + + + /** + * 渠道ID(system=dict_code,custom=自定义渠道ID) + */ + @ExcelProperty(value = "渠道ID") + private Long channelId; + + /** + * 报价目标日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date quoteDate; + + + private FbaShipmentApiResponse.Address shipToAddress; + + +} diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizInquiryRequestService.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizInquiryRequestService.java index 1115e9a..4801d6d 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizInquiryRequestService.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizInquiryRequestService.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import org.asinkj.amz.domain.BizInquiryRequest; import org.asinkj.amz.domain.vo.BizInquiryRequestVo; import org.asinkj.amz.domain.bo.BizInquiryRequestBo; +import org.asinkj.amz.domain.vo.InquiryRequestItemVo; import org.asinkj.common.core.domain.R; import org.asinkj.common.mybatis.core.page.TableDataInfo; import org.asinkj.common.mybatis.core.page.PageQuery; @@ -71,5 +72,8 @@ public interface IBizInquiryRequestService { R createWithDesAndChannel(@NotNull(message = "目的地不能为空") String destination, @NotNull(message = "渠道不能为空") String channelId, @NotNull(message = "日期不能为空") String date); + + R batchCreateWithItems(List items); + TableDataInfo queryWithDesAndChannel(@NotNull(message = "目的地不能为空") String destination, @NotNull(message = "渠道不能为空") String channelId, @NotNull(message = "日期不能为空") String date); } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizShipmentPlanService.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizShipmentPlanService.java index 8107869..bd482bf 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizShipmentPlanService.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/IBizShipmentPlanService.java @@ -77,4 +77,8 @@ public interface IBizShipmentPlanService { void takeTodayAmzPlanData(); TableDataInfo queryPageListOrder(BizShipmentPlanBo bo, PageQuery pageQuery); + + void updateStatusbyFbaShipmentIds(List fbaShipmentids); + + void updateList(Collection list, String status); } diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizInquiryRequestServiceImpl.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizInquiryRequestServiceImpl.java index c47c578..cfc1ce6 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizInquiryRequestServiceImpl.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizInquiryRequestServiceImpl.java @@ -1,13 +1,16 @@ package org.asinkj.amz.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.NumberUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.extern.slf4j.Slf4j; -import org.asinkj.amz.domain.BizLogisticsQuote; import org.asinkj.amz.domain.vo.BizLogisticsChannelVo; +import org.asinkj.amz.domain.vo.InquiryRequestItemVo; import org.asinkj.amz.mapper.BizLogisticsQuoteMapper; import org.asinkj.amz.service.IBizLogisticsChannelService; +import org.asinkj.amz.service.IBizShipmentPlanService; import org.asinkj.common.core.domain.R; import org.asinkj.common.core.utils.MapstructUtils; import org.asinkj.common.core.utils.StringUtils; @@ -17,10 +20,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; +import org.asinkj.common.redis.annotation.DistributedLock; +import org.asinkj.common.redis.utils.RedisUtils; import org.asinkj.common.satoken.utils.LoginHelper; import org.asinkj.resource.api.RemoteMessageService; import org.asinkj.utils.SerialNoGenerator; -import org.springframework.beans.factory.annotation.Autowired; +import org.redisson.api.RLock; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.asinkj.amz.domain.bo.BizInquiryRequestBo; import org.asinkj.amz.domain.vo.BizInquiryRequestVo; @@ -29,12 +35,12 @@ import org.asinkj.amz.mapper.BizInquiryRequestMapper; import org.asinkj.amz.service.IBizInquiryRequestService; import javax.annotation.Resource; -import java.text.SimpleDateFormat; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * 物流询价Service业务层处理 @@ -64,6 +70,10 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { @Resource private RemoteMessageService remoteMessageService; + @Resource + private IBizShipmentPlanService bizShipmentPlanService; + + /** * 查询物流询价 * @@ -71,7 +81,7 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { * @return 物流询价 */ @Override - public BizInquiryRequestVo queryById(Long id){ + public BizInquiryRequestVo queryById(Long id) { return baseMapper.selectVoById(id); } @@ -151,7 +161,7 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { /** * 保存前的数据校验 */ - private void validEntityBeforeSave(BizInquiryRequest entity){ + private void validEntityBeforeSave(BizInquiryRequest entity) { //TODO 做一些数据校验,如唯一约束 } @@ -164,16 +174,14 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ + if (isValid) { //TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteByIds(ids) > 0; } @Override - public R createWithDesAndChannel(String destination, String channelId,String date) { - - + public R createWithDesAndChannel(String destination, String channelId, String date) { BizInquiryRequest bizInquiryRequest = new BizInquiryRequest(); @@ -192,13 +200,13 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { //查询出渠道信息 BizLogisticsChannelVo bizLogisticsChannelVo = bizLogisticsChannelService.queryById(bizInquiryRequest.getChannelId()); - if (bizLogisticsChannelVo == null){ + if (bizLogisticsChannelVo == null) { log.info("渠道信息为空"); } - if ("air".equals(bizLogisticsChannelVo.getShippingMethod())){ + if ("air".equals(bizLogisticsChannelVo.getShippingMethod())) { bizInquiryRequest.setEffectiveStartTime(new Date()); bizInquiryRequest.setEffectiveEndTime(new Date()); - }else if("sea".equals(bizLogisticsChannelVo.getShippingMethod())){ + } else if ("sea".equals(bizLogisticsChannelVo.getShippingMethod())) { //获取本周的周日上个周六的日期 LocalDate sunday = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)); LocalDate saturday = sunday.plusDays(6); @@ -213,18 +221,212 @@ public class BizInquiryRequestServiceImpl implements IBizInquiryRequestService { bizInquiryRequest.setTransportChannel(bizLogisticsChannelVo.getShippingMethod()); //获取当天上午11点的时间 ? bizInquiryRequest.setDeadline(LocalDate.now() - .atTime(11, 0)); + .atTime(11, 0)); bizInquiryRequest.setRequesterId(LoginHelper.getUserId()); DateTime dateTime = DateUtil.parseDate(date); bizInquiryRequest.setQuoteDate(dateTime); billingRequestMapper.insert(bizInquiryRequest); - remoteMessageService.publishMessage(LoginHelper.getUserId(), "您好,您的询价单创建成功。具体询价单号为:"+bizInquiryRequest.getInquiryNo()); + remoteMessageService.publishMessage(LoginHelper.getUserId(), "您好,您的询价单创建成功。具体询价单号为:" + bizInquiryRequest.getInquiryNo()); return R.ok(bizInquiryRequest.getInquiryNo()); } + +// @Override +// public R batchCreateWithItems(List items) { +// +// if (CollectionUtil.isEmpty(items)) { +// return R.fail("询价单不能为空"); +// } +// +// List requests = fiterItemsList(items); +// if (CollectionUtil.isEmpty(requests)) { +// return R.warn("已经有相同的询价了"); +// } +// List bizInquiryRequests = new ArrayList<>(); +// for (InquiryRequestItemVo request : requests) { +// +// +// BizInquiryRequest bizInquiryRequest = new BizInquiryRequest(); +// bizInquiryRequest.setDestination(request.getDestination()); +// bizInquiryRequest.setChannelId(request.getChannelId()); +// +// bizInquiryRequest.setRequesterName(LoginHelper.getLoginUser().getNickname()); +// if (bizInquiryRequest.getChannelId() == null) { +// return R.fail("渠道不能为空"); +// } +// if (bizInquiryRequest.getDestination() == null) { +// return R.fail("目的地不能为空"); +// } +// //询价单号(规则:INQ+年月+6位序列) +// bizInquiryRequest.setInquiryNo(SerialNoGenerator.generateInquiryNo()); +// //查询出渠道信息 +// +// BizLogisticsChannelVo bizLogisticsChannelVo = bizLogisticsChannelService.queryById(bizInquiryRequest.getChannelId()); +// if (bizLogisticsChannelVo == null) { +// log.info("渠道信息为空"); +// } +// if ("air".equals(bizLogisticsChannelVo.getShippingMethod())) { +// bizInquiryRequest.setEffectiveStartTime(new Date()); +// bizInquiryRequest.setEffectiveEndTime(new Date()); +// } else if ("sea".equals(bizLogisticsChannelVo.getShippingMethod())) { +// //获取本周的周日上个周六的日期 +// LocalDate sunday = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)); +// +// LocalDate saturday = sunday.plusDays(6); +// +// bizInquiryRequest.setEffectiveStartTime(DateUtil.date(sunday)); +// +// bizInquiryRequest.setEffectiveEndTime(DateUtil.date(saturday)); +// } +// +// bizInquiryRequest.setChannelName(bizLogisticsChannelVo.getChannelName()); +// bizInquiryRequest.setTransportChannel(bizLogisticsChannelVo.getShippingMethod()); +// //获取当天上午11点的时间 ? +// bizInquiryRequest.setDeadline(LocalDate.now() +// .atTime(11, 0)); +// +// bizInquiryRequest.setRequesterId(LoginHelper.getUserId()); +// bizInquiryRequest.setQuoteDate(request.getQuoteDate()); +// bizInquiryRequest.setShipToAddress(request.getShipToAddress()); +// bizInquiryRequests.add(bizInquiryRequest); +// } +// billingRequestMapper.insertBatch(bizInquiryRequests); +// +//// remoteMessageService.publishMessage(LoginHelper.getUserId(), "您好,您的询价单创建成功。具体询价单号为:" + bizInquiryRequest.getInquiryNo()); +// +// return R.ok(); +// } + + + /** + * 创建单个询价单(带分布式锁) + */ + @DistributedLock( + prefix = "inquiry:create", + params = {"request.channelId", "request.destination"}, // 从request对象提取字段 + waitTime = 0, + leaseTime = 30 + ) + private BizInquiryRequest createSingleInquiry(InquiryRequestItemVo request) { + // 参数校验 + if (request.getChannelId() == null) { + throw new RuntimeException("渠道不能为空"); + } + if (request.getDestination() == null) { + throw new RuntimeException("目的地不能为空"); + } + + // 构建询价单对象 + BizInquiryRequest bizInquiryRequest = new BizInquiryRequest(); + bizInquiryRequest.setDestination(request.getDestination()); + bizInquiryRequest.setChannelId(request.getChannelId()); + bizInquiryRequest.setRequesterName(LoginHelper.getLoginUser().getNickname()); + bizInquiryRequest.setInquiryNo(SerialNoGenerator.generateInquiryNo()); + + // 查询渠道信息 + BizLogisticsChannelVo channelVo = bizLogisticsChannelService.queryById(request.getChannelId()); + if (channelVo == null) { + throw new RuntimeException("渠道不存在"); + } + + // 设置运输时效 + if ("air".equals(channelVo.getShippingMethod())) { + bizInquiryRequest.setEffectiveStartTime(new Date()); + bizInquiryRequest.setEffectiveEndTime(new Date()); + } else if ("sea".equals(channelVo.getShippingMethod())) { + LocalDate sunday = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)); + LocalDate saturday = sunday.plusDays(6); + bizInquiryRequest.setEffectiveStartTime(DateUtil.date(sunday)); + bizInquiryRequest.setEffectiveEndTime(DateUtil.date(saturday)); + } + + // 设置其他字段 + bizInquiryRequest.setChannelName(channelVo.getChannelName()); + bizInquiryRequest.setTransportChannel(channelVo.getShippingMethod()); + bizInquiryRequest.setDeadline(LocalDate.now().atTime(11, 0)); + bizInquiryRequest.setRequesterId(LoginHelper.getUserId()); + bizInquiryRequest.setQuoteDate(request.getQuoteDate()); + bizInquiryRequest.setShipToAddress(request.getShipToAddress()); + + return bizInquiryRequest; + } + + + @Override + public R batchCreateWithItems(List items) { + // 1. 参数校验 + if (CollectionUtil.isEmpty(items)) { + return R.fail("询价单不能为空"); + } + Set shipmentIds = items.stream().map(InquiryRequestItemVo::getShipmentId).collect(Collectors.toSet()); + bizShipmentPlanService.updateList(shipmentIds, "send"); + + // 2. 过滤重复请求(前置校验) + List requests = fiterItemsList(items); + if (CollectionUtil.isEmpty(requests)) { + return R.warn("已有相同询价单"); + } + + // 3. 批量创建(每个询价单独立加锁) + List bizInquiryRequests = new ArrayList<>(); + for (InquiryRequestItemVo request : requests) { + try { + BizInquiryRequest item = createSingleInquiry(request); + bizInquiryRequests.add(item); + } catch (RuntimeException e) { + // 捕获锁冲突或业务校验异常 + return R.fail(e.getMessage()); + } + } + + // 4. 批量插入(兜底:数据库唯一索引) + try { + billingRequestMapper.insertBatch(bizInquiryRequests); + } catch (DuplicateKeyException e) { + log.error("唯一索引拦截重复数据: {}", e.getMessage()); + return R.fail("存在重复的渠道-目的地组合"); + } + + // 5. 发送通知(可选) + // String inquiryNo = bizInquiryRequests.get(0).getInquiryNo(); + // remoteMessageService.publishMessage(LoginHelper.getUserId(), "询价单创建成功,单号:" + inquiryNo); + + return R.ok(); + } + + + + private List fiterItemsList(List items) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + for (InquiryRequestItemVo item : items) { + wrapper.or(w -> w + .eq(BizInquiryRequest::getDestination, item.getDestination()) + .eq(BizInquiryRequest::getChannelId, item.getChannelId()) + .eq(BizInquiryRequest::getQuoteDate, item.getQuoteDate()) + ); + } + +// 查询已存在的记录 + List existingList = baseMapper.selectList(wrapper); + + + // 将已存在的记录转为 Set 方便快速比对(假设三字段组合唯一) + Set existingKeys = existingList.stream() + .map(e -> e.getDestination() + "_" + e.getChannelId() + "_" + e.getQuoteDate()) + .collect(Collectors.toSet()); + +// 过滤掉重复的待插入数据 + List filteredItems = items.stream() + .filter(item -> !existingKeys.contains( + item.getDestination() + "_" + item.getChannelId() + "_" + item.getQuoteDate() + )) + .collect(Collectors.toList()); + return filteredItems; + } + @Override public TableDataInfo queryWithDesAndChannel(String destination, String channelId, String date) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizLogisticsOrderServiceImpl.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizLogisticsOrderServiceImpl.java index 1808111..66c667d 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizLogisticsOrderServiceImpl.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizLogisticsOrderServiceImpl.java @@ -75,7 +75,7 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { * @return 物流订单 */ @Override - public BizLogisticsOrderVo queryById(Long id){ + public BizLogisticsOrderVo queryById(Long id) { return baseMapper.selectVoById(id); } @@ -118,7 +118,9 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { lqw.eq(bo.getShipmentQuantity() != null, BizLogisticsOrder::getShipmentQuantity, bo.getShipmentQuantity()); lqw.eq(bo.getAmazonShelfDate() != null, BizLogisticsOrder::getAmazonShelfDate, bo.getAmazonShelfDate()); lqw.eq(bo.getShelfTimeliness() != null, BizLogisticsOrder::getShelfTimeliness, bo.getShelfTimeliness()); - lqw.and(wrapper -> wrapper.eq(BizLogisticsOrder::getCreateBy, LoginHelper.getUserId()).or().eq(BizLogisticsOrder::getLogisticsProviderId, LoginHelper.getUserId())); + if (!LoginHelper.isSuperAdmin() && !LoginHelper.isManagerAdmin()) { + lqw.and(wrapper -> wrapper.eq(BizLogisticsOrder::getCreateBy, LoginHelper.getUserId()).or().eq(BizLogisticsOrder::getLogisticsProviderId, LoginHelper.getUserId())); + } // Set roleIdSet = LoginHelper.getLoginUser().getRoles().stream().map(RoleDTO::getRoleId).collect(Collectors.toSet()); // log.info("roleIdSet:{}", roleIdSet); // if (roleIdSet.contains(3L)){ @@ -165,7 +167,7 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { /** * 保存前的数据校验 */ - private void validEntityBeforeSave(BizLogisticsOrder entity){ + private void validEntityBeforeSave(BizLogisticsOrder entity) { //TODO 做一些数据校验,如唯一约束 } @@ -178,10 +180,14 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ + if (isValid) { //TODO 做一些业务上的校验,判断是否需要校验 } - return baseMapper.deleteByIds(ids) > 0; + boolean b = baseMapper.deleteByIds(ids) > 0; + List bizLogisticsOrders = baseMapper.selectByIds(ids); + List fbaShipmentids = bizLogisticsOrders.stream().map(BizLogisticsOrder::getFbaShipmentId).collect(Collectors.toList()); + bizShipmentPlanService.updateStatusbyFbaShipmentIds(fbaShipmentids); + return b; } @Override @@ -225,7 +231,7 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { bizLogisticsOrder.setOrderId(SerialNoGenerator.generateOrderNo()); bizLogisticsOrder.setLogisticsProviderId(quoteVo.getUserId()); - String nickName = remoteUserService.selectNicknameById(quoteVo.getUserId()); + String nickName = remoteUserService.selectNicknameById(quoteVo.getUserId()); bizLogisticsOrder.setLogisticsProviderName(nickName); bizLogisticsOrder.setQuoteOrderId(Long.valueOf(bo.getLogicQuoteId())); @@ -240,7 +246,7 @@ public class BizLogisticsOrderServiceImpl implements IBizLogisticsOrderService { bizLogisticsOrder.setShipmentQuantity(sum); - ArrayList orderDetailList = new ArrayList<>(); + ArrayList orderDetailList = new ArrayList<>(); for (BizShipmentTracking bizShipmentTracking : trackingList) { BizLogisticsOrderDetail detail = new BizLogisticsOrderDetail(); diff --git a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizShipmentPlanServiceImpl.java b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizShipmentPlanServiceImpl.java index e84f232..70f88ba 100644 --- a/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizShipmentPlanServiceImpl.java +++ b/asinkj-biz/asinkj-amz/src/main/java/org/asinkj/amz/service/impl/BizShipmentPlanServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.asinkj.amz.domain.*; import org.asinkj.amz.domain.bo.SysAmazonStoreBo; @@ -361,8 +362,11 @@ public class BizShipmentPlanServiceImpl implements IBizShipmentPlanService { } bizShipmentPlanVo.setBoxQuantity(count); bizShipmentPlanVo.setOrder(bizLogisticsOrder); - BizLogisticsQuote bizLogisticsQuote = quoteMap.get(bizLogisticsOrder.getQuoteOrderId()); - bizShipmentPlanVo.setQuote(bizLogisticsQuote); + if (bizLogisticsOrder != null) { + BizLogisticsQuote bizLogisticsQuote = quoteMap.get(bizLogisticsOrder.getQuoteOrderId()); + bizShipmentPlanVo.setQuote(bizLogisticsQuote); + } + if (CollectionUtil.isNotEmpty(sids)) { String storeName = storeNameMap.get(bizShipmentPlanVo.getSid()); bizShipmentPlanVo.setSellerName(storeName); @@ -371,4 +375,21 @@ public class BizShipmentPlanServiceImpl implements IBizShipmentPlanService { } return TableDataInfo.build(bizShipmentPlanOrderVoPage); } + + @Override + public void updateStatusbyFbaShipmentIds(List fbaShipmentids) { + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + lambdaUpdateWrapper.in(BizShipmentPlan::getShipmentId, fbaShipmentids) + .set(BizShipmentPlan::getFbaStatus, "ask"); + baseMapper.update(null,lambdaUpdateWrapper); + + } + + @Override + public void updateList(Collection list, String status) { + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + lambdaUpdateWrapper.in(BizShipmentPlan::getShipmentId, list) + .set(BizShipmentPlan::getFbaStatus, "send"); + baseMapper.update(null,lambdaUpdateWrapper); + } } diff --git a/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/annotation/DistributedLock.java b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/annotation/DistributedLock.java new file mode 100644 index 0000000..9e58c2a --- /dev/null +++ b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/annotation/DistributedLock.java @@ -0,0 +1,31 @@ +package org.asinkj.common.redis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) // 标注在方法上 +@Retention(RetentionPolicy.RUNTIME) // 运行时生效 +public @interface DistributedLock { + /** + * 锁的Key前缀(默认使用方法名) + */ + String prefix() default ""; + + /** + * 等待锁时间(秒),0表示不等待 + */ + long waitTime() default 0; + + /** + * 锁持有时间(秒) + */ + long leaseTime() default 30; + + /** + * 业务唯一标识参数名(从方法参数中提取) + * 例如:params = {"channelId", "destination"} + */ + String[] params() default {}; +} diff --git a/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/aspect/DistributedLockAspect.java b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/aspect/DistributedLockAspect.java new file mode 100644 index 0000000..743f5d5 --- /dev/null +++ b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/aspect/DistributedLockAspect.java @@ -0,0 +1,100 @@ +package org.asinkj.common.redis.aspect; + +import org.asinkj.common.redis.annotation.DistributedLock; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.asinkj.common.redis.utils.RedisUtils; +import org.redisson.api.RLock; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Aspect +@Component +public class DistributedLockAspect { + + private final ExpressionParser parser = new SpelExpressionParser(); + + /** + * 环绕通知,拦截被 @DistributedLock 注解的方法 + */ + @Around("@annotation(distributedLock)") + public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { + // 1. 生成分布式锁Key + String lockKey = generateLockKey(joinPoint, distributedLock); + + // 2. 获取 Redisson 锁对象 + RLock lock = RedisUtils.getClient().getLock(lockKey); + + try { + // 3. 尝试加锁 + boolean isLocked = lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), TimeUnit.SECONDS); + if (!isLocked) { + throw new RuntimeException("系统繁忙,请稍后重试"); + } + + // 4. 执行目标方法(业务逻辑) + return joinPoint.proceed(); + + } finally { + // 5. 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + /** + * 生成锁的Key(格式:prefix:param1_value:param2_value) + */ + private String generateLockKey(ProceedingJoinPoint joinPoint, DistributedLock lock) { + // 获取方法参数名和值 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String[] paramNames = signature.getParameterNames(); + Object[] args = joinPoint.getArgs(); + + // 解析业务唯一标识参数值 + StringBuilder keyBuilder = new StringBuilder(); + if (lock.prefix().isEmpty()) { + keyBuilder.append(joinPoint.getSignature().getName()); // 默认用方法名 + } else { + keyBuilder.append(lock.prefix()); + } + + for (String param : lock.params()) { + Object value = getParamValue(param, paramNames, args); + keyBuilder.append(":").append(value); + } + + return keyBuilder.toString(); + } + + /** + * 根据参数名获取参数值(支持SpEL表达式) + */ + private Object getParamValue(String param, String[] paramNames, Object[] args) { + // 检查是否是SpEL表达式(例如 #request.channelId) + if (param.startsWith("#")) { + StandardEvaluationContext context = new StandardEvaluationContext(); + for (int i = 0; i < paramNames.length; i++) { + context.setVariable(paramNames[i], args[i]); + } + Expression exp = parser.parseExpression(param); + return exp.getValue(context); + } + + // 普通参数名匹配 + for (int i = 0; i < paramNames.length; i++) { + if (param.equals(paramNames[i])) { + return args[i]; + } + } + throw new IllegalArgumentException("未找到参数: " + param); + } +} diff --git a/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/utils/RedisUtils.java b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/utils/RedisUtils.java index a9d45eb..532f2d1 100644 --- a/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/utils/RedisUtils.java +++ b/asinkj-common/asinkj-common-redis/src/main/java/org/asinkj/common/redis/utils/RedisUtils.java @@ -10,7 +10,9 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -545,4 +547,22 @@ public class RedisUtils { RKeys rKeys = CLIENT.getKeys(); return rKeys.countExists(key) > 0; } + + public static T executeWithLock(String lockKey, Supplier supplier, + long waitTime, long leaseTime) { + RLock lock = RedisUtils.getClient().getLock(lockKey); + try { + if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) { + return supplier.get(); // 执行核心业务逻辑 + } + throw new RuntimeException("获取锁失败"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("锁等待被中断"); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } }