liding
2025-05-09 cb635802bd0187fd2874c8ad3d6664d4c7aa8555
1.销售台账合同号生成 2.关联产品新增删除 3.采购台账
已修改9个文件
已添加7个文件
508 ■■■■■ 文件已修改
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/MyBaseMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 157 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -17,6 +17,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -113,9 +114,40 @@
    }
    @Override
    public List customerList(Customer customer) {
    public List<Map<String, Object>> customerList(Customer customer) {
        LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(Customer::getId, Customer::getCustomerName);
        return customerMapper.selectMaps(queryWrapper);
        queryWrapper.select(Customer::getId, Customer::getCustomerName,Customer::getTaxpayerIdentificationNumber);
        // èŽ·å–åŽŸå§‹æŸ¥è¯¢ç»“æžœ
        List<Map<String, Object>> result = customerMapper.selectMaps(queryWrapper);
        // å°†ä¸‹åˆ’线命名转换为驼峰命名
        return result.stream().map(map -> map.entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> underlineToCamel(entry.getKey()),
                        Map.Entry::getValue))
        ).collect(Collectors.toList());
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
    private String underlineToCamel(String param) {
        if (param == null || "".equals(param.trim())) {
            return "";
        }
        int len = param.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = param.charAt(i);
            if (c == '_') {
                if (++i < len) {
                    sb.append(Character.toUpperCase(param.charAt(i)));
                }
            } else {
                sb.append(Character.toLowerCase(c));
            }
        }
        return sb.toString();
    }
}
src/main/java/com/ruoyi/common/config/MyBaseMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.common.config;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
public interface MyBaseMapper<T> extends BaseMapper<T> {
    /**
     * æ‰¹é‡æ’入(仅插入非空字段)
     * @param list å®žä½“列表
     * @return æ’入成功的记录数
     */
    int insertBatchSomeColumn(List<T> list);
    /**
     * æ‰¹é‡æ›´æ–°ï¼ˆä»…更新非空字段)
     */
    int updateBatchSomeColumn(List<T> list);
}
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package com.ruoyi.purchase.controller;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.page.TableDataInfo;
import java.util.List;
/**
 * é‡‡è´­å°è´¦Controller
 *
 * @author ruoyi
 * @date 2025-05-09
 */
@RestController
@RequestMapping("/system/ledger")
@AllArgsConstructor
public class PurchaseLedgerController extends BaseController {
    private IPurchaseLedgerService purchaseLedgerService;
    /**
     * æŸ¥è¯¢é‡‡è´­å°è´¦åˆ—表
     */
    @GetMapping("/list")
    public TableDataInfo list(PurchaseLedger purchaseLedger) {
        startPage();
        List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger);
        return getDataTable(list);
    }
    /**
     * å¯¼å‡ºé‡‡è´­å°è´¦åˆ—表
     */
    @Log(title = "采购台账", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, PurchaseLedger purchaseLedger) {
        List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger);
        ExcelUtil<PurchaseLedger> util = new ExcelUtil<PurchaseLedger>(PurchaseLedger.class);
        util.exportExcel(response, list, "【请填写功能名称】数据");
    }
    /**
     * æ–°å¢žä¿®æ”¹é‡‡è´­å°è´¦
     */
    @Log(title = "采购台账", businessType = BusinessType.INSERT)
    @PostMapping ("/addOrEditPurchase")
    public AjaxResult addOrEditPurchase(@RequestBody PurchaseLedger purchaseLedger) {
        return toAjax(purchaseLedgerService.addOrEditPurchase(purchaseLedger));
    }
    /**
     * åˆ é™¤é‡‡è´­å°è´¦
     */
    @Log(title = "采购台账", businessType = BusinessType.DELETE)
    @DeleteMapping("/delPurchase")
    public AjaxResult remove(@RequestBody Long[] ids) {
        return toAjax(purchaseLedgerService.deletePurchaseLedgerByIds(ids));
    }
}
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.purchase.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
/**
 * é‡‡è´­å°è´¦Mapper接口
 *
 * @author ruoyi
 * @date 2025-05-09
 */
public interface PurchaseLedgerMapper extends BaseMapper<PurchaseLedger> {
}
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package com.ruoyi.purchase.pojo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
/**
 * é‡‡è´­å°è´¦å¯¹è±¡ purchase_ledger
 *
 * @author ruoyi
 * @date 2025-05-09
 */
@TableName("purchase_ledger")
@Data
public class PurchaseLedger {
    private static final long serialVersionUID = 1L;
    /**
     * è‡ªå¢žä¸»é”®ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * é‡‡è´­åˆåŒå·
     */
    @Excel(name = "采购合同号")
    private String purchaseContractNumber;
    /**
     * ä¾›åº”商名称
     */
    @Excel(name = "供应商名称")
    private String supplierName;
    /**
     * å½•入人姓名
     */
    @Excel(name = "录入人姓名")
    private String recorderName;
    /**
     * é”€å”®åˆåŒå·
     */
    @Excel(name = "销售合同号")
    private String salesContractNo;
    /**
     * é¡¹ç›®åç§°
     */
    @Excel(name = "项目名称")
    private String projectName;
    /**
     * å½•入日期
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "录入日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date entryDate;
    /**
     * å¤‡æ³¨
     */
    @Excel(name = "备注")
    private String remarks;
    /**
     * é™„件材料路径或名称
     */
    @Excel(name = "附件材料路径或名称")
    private String attachmentMaterials;
    /**
     * è®°å½•创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "记录创建时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date createdAt;
    /**
     * è®°å½•最后更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "记录最后更新时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date updatedAt;
    /**
     * å…³è”销售台账主表主键
     */
    private Long salesLedgerId;
}
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.purchase.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import java.util.List;
/**
 * é‡‡è´­å°è´¦Service接口
 *
 * @author ruoyi
 * @date 2025-05-09
 */
public interface IPurchaseLedgerService extends IService<PurchaseLedger> {
    List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger);
    int addOrEditPurchase(PurchaseLedger purchaseLedger);
    int deletePurchaseLedgerByIds(Long[] ids);
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package com.ruoyi.purchase.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
 * é‡‡è´­å°è´¦Service业务层处理
 *
 * @author ruoyi
 * @date 2025-05-09
 */
@Service
@AllArgsConstructor
public class PurchaseLedgerServiceImpl extends ServiceImpl<PurchaseLedgerMapper, PurchaseLedger> implements IPurchaseLedgerService {
    private PurchaseLedgerMapper purchaseLedgerMapper;
    @Override
    public List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger) {
        return purchaseLedgerMapper.selectList(new LambdaQueryWrapper<>());
    }
    @Override
    public int addOrEditPurchase(PurchaseLedger purchaseLedger) {
        if (purchaseLedger.getId() == null) {
            return purchaseLedgerMapper.insert(purchaseLedger);
        } else {
            return purchaseLedgerMapper.updateById(purchaseLedger);
        }
    }
    @Override
    public int deletePurchaseLedgerByIds(Long[] ids) {
        return purchaseLedgerMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -82,9 +82,9 @@
     */
    @Log(title = "销售台账", businessType = BusinessType.INSERT)
    @PostMapping ("/addOrUpdateSalesLedger")
    public AjaxResult add(@RequestBody SalesLedger salesLedger)
    public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto)
    {
        return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedger));
        return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedgerDto));
    }
    /**
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -22,5 +22,5 @@
    private String remarks;
    private String attachmentMaterials;
    private Boolean hasChildren = false;
    private List<SalesLedgerProduct> children;
    private List<SalesLedgerProduct> productData;
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -3,6 +3,7 @@
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import org.apache.ibatis.annotations.Param;
/**
@@ -12,4 +13,10 @@
 * @date 2025-05-08
 */
public interface SalesLedgerMapper extends BaseMapper<SalesLedger> {
    /**
     * æŸ¥è¯¢æŒ‡å®šæ—¥æœŸçš„æ‰€æœ‰åˆåŒåºåˆ—号
     * @param datePart æ—¥æœŸéƒ¨åˆ†ï¼ˆæ ¼å¼ï¼šyyyyMMdd)
     * @return åºåˆ—号列表
     */
    List<Integer> selectSequencesByDate(@Param("datePart") String datePart);
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -1,6 +1,6 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.config.MyBaseMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
/**
@@ -9,5 +9,5 @@
 * @author ruoyi
 * @date 2025-05-08
 */
public interface SalesLedgerProductMapper extends BaseMapper<SalesLedgerProduct> {
public interface SalesLedgerProductMapper extends MyBaseMapper<SalesLedgerProduct> {
}
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -1,5 +1,6 @@
package com.ruoyi.sales.pojo;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.*;
@@ -81,11 +82,16 @@
    /**
     * é™„件材料,存储文件名等相关信息
     */
    @Excel(name = "附件材料,存储文件名等相关信息")
    private String attachmentMaterials;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * åˆåŒé‡‘额(产品含税总价)
     */
    @Excel(name = "税率")
    private BigDecimal contractAmount;
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -20,7 +20,7 @@
    int deleteSalesLedgerByIds(Long[] ids);
    int addOrUpdateSalesLedger(SalesLedger salesLedger);
    int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto);
    List<SalesLedgerDto> getSalesLedgerWithProducts();
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.pojo.Customer;
@@ -12,12 +13,16 @@
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerService;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -35,6 +40,12 @@
    private CustomerMapper customerMapper;
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private static final String LOCK_PREFIX = "contract_no_lock:";
    private static final long LOCK_WAIT_TIMEOUT = 10; // é”ç­‰å¾…超时时间(秒)
    private static final long LOCK_EXPIRE_TIME = 30;  // é”è‡ªåŠ¨è¿‡æœŸæ—¶é—´ï¼ˆç§’ï¼‰
    private final RedisTemplate<String, String> redisTemplate;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedger salesLedger) {
@@ -55,7 +66,7 @@
            List<SalesLedgerProduct> ledgerProducts = productMap.getOrDefault(ledger.getId(), Collections.emptyList());
            if (!ledgerProducts.isEmpty()) {
                dto.setHasChildren(true);
                dto.setChildren(ledgerProducts);
                dto.setProductData(ledgerProducts);
            }
            return dto;
        }).collect(Collectors.toList());
@@ -67,27 +78,139 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteSalesLedgerByIds(Long[] ids) {
        return salesLedgerMapper.deleteBatchIds(Arrays.asList(ids));
        List<Long> idList = Arrays.stream(ids)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(idList)) {
            return 0;
        }
        // 1. å…ˆåˆ é™¤å­è¡¨æ•°æ®
        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList);
        salesLedgerProductMapper.delete(productWrapper);
        // 2. å†åˆ é™¤ä¸»è¡¨æ•°æ®
        return salesLedgerMapper.deleteBatchIds(idList);
    }
    public int addOrUpdateSalesLedger(SalesLedger salesLedger) {
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Customer::getId, salesLedger.getCustomerId());
        Customer customer = customerMapper.selectOne(queryWrapper);
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
        Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
        if (customer == null) {
            throw new BaseException("未查询到对应的 Customer ä¿¡æ¯");
            throw new BaseException("客户不存在");
        }
        // 2. DTO转Entity
        SalesLedger salesLedger = convertToEntity(salesLedgerDto);
        salesLedger.setCustomerName(customer.getCustomerName());
        salesLedger.setTenantId(customer.getTenantId());
        return saveOrUpdates(salesLedger);
        // 3. æ–°å¢žæˆ–更新主表
        if (salesLedger.getId() == null) {
            // ç”ŸæˆåˆåŒç¼–号
            String contractNo = generateSalesContractNo();
            salesLedger.setSalesContractNo(contractNo);
            salesLedgerMapper.insert(salesLedger);
        } else {
            salesLedgerMapper.updateById(salesLedger);
    }
    private int saveOrUpdates(SalesLedger salesLedger) {
        if (salesLedger.getId() == null) {
            return salesLedgerMapper.insert(salesLedger);
        } else {
            return salesLedgerMapper.updateById(salesLedger);
        // 4. å¤„理子表数据
        if (salesLedgerDto.getProductData() != null && !salesLedgerDto.getProductData().isEmpty()) {
            handleSalesLedgerProducts(salesLedger.getId(), salesLedgerDto.getProductData());
        }
        return 1; // æ“ä½œæˆåŠŸè¿”å›ž1
    }
    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) {
        // æŒ‰ID分组,区分新增和更新的记录
        Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
                .peek(p -> p.setSalesLedgerId(salesLedgerId))
                .collect(Collectors.partitioningBy(p -> p.getId() != null));
        List<SalesLedgerProduct> updateList = partitionedProducts.get(true);
        List<SalesLedgerProduct> insertList = partitionedProducts.get(false);
        // æ‰§è¡Œæ›´æ–°æ“ä½œ
        if (!updateList.isEmpty()) {
            salesLedgerProductMapper.updateBatchSomeColumn(updateList);
        }
        // æ‰§è¡Œæ’入操作
        if (!insertList.isEmpty()) {
            salesLedgerProductMapper.insertBatchSomeColumn(insertList);
        }
    }
    private SalesLedger convertToEntity(SalesLedgerDto dto) {
        SalesLedger entity = new SalesLedger();
        BeanUtils.copyProperties(dto, entity);
        return entity;
    }
    @Transactional(readOnly = true)
    public String generateSalesContractNo() {
        LocalDate currentDate = LocalDate.now();
        String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
        String lockKey = LOCK_PREFIX + datePart;
        String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // å”¯ä¸€æ ‡è¯†é”æŒæœ‰è€…
        try {
            // 1. å°è¯•获取分布式锁(循环直到超时)
            long startWaitTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startWaitTime < LOCK_WAIT_TIMEOUT * 1000) {
                // SET key value NX PX 30000:仅当锁不存在时获取,设置30秒过期
                Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
                if (Boolean.TRUE.equals(locked)) {
                    break; // æˆåŠŸèŽ·å–é”
                }
                // çŸ­æš‚休眠避免忙等待
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("获取锁时被中断", e);
                }
            }
            if (!redisTemplate.hasKey(lockKey)) {
                throw new RuntimeException("获取合同编号生成锁失败:超时");
            }
            // 2. æŸ¥è¯¢å½“天已存在的序列号(与原逻辑一致)
            List<Integer> existingSequences = salesLedgerMapper.selectSequencesByDate(datePart);
            int nextSequence = findFirstMissingSequence(existingSequences);
            return datePart + String.format("%02d", nextSequence);
        } finally {
            // 3. é‡Šæ”¾é”ï¼ˆä½¿ç”¨Lua脚本保证原子性,避免误删其他线程的锁)
            String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
            redisTemplate.execute(
                    new DefaultRedisScript<>(luaScript, Long.class),
                    Collections.singletonList(lockKey),
                    lockValue // åªæœ‰æŒæœ‰ç›¸åŒå€¼çš„线程才能删除锁
            );
        }
    }
    private int findFirstMissingSequence(List<Integer> sequences) {
        if (sequences.isEmpty()) {
            return 1;
        }
        // æŽ’序后查找第一个缺失的正整数(与原逻辑一致)
        sequences.sort(Integer::compareTo);
        int next = 1;
        for (int seq : sequences) {
            if (seq == next) {
                next++;
            } else if (seq > next) {
                break;
            }
        }
        return next;
    }
}
src/main/resources/application.yml
@@ -100,7 +100,7 @@
# MyBatis Plus配置
mybatis-plus:
  # æœç´¢æŒ‡å®šåŒ…别名   æ ¹æ®è‡ªå·±çš„项目来
  typeAliasesPackage: com.ruoyi.basic.**.pojo
  typeAliasesPackage: com.ruoyi.**.pojo
  # é…ç½®mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # åŠ è½½å…¨å±€çš„é…ç½®æ–‡ä»¶
src/main/resources/mapper/sales/SalesLedgerMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sales.mapper.SalesLedgerMapper">
    <select id="selectSequencesByDate" resultType="java.lang.Integer">
        SELECT CAST(SUBSTR(sales_contract_no, 9, 2) AS SIGNED)
        FROM sales_ledger
        WHERE SUBSTR(sales_contract_no, 1, 8) = #{datePart}
    </select>
</mapper>