3 天以前 b852254d90e7d1955db31db154193ec8ee84d8b3
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.wm.barcode;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
@@ -11,11 +12,26 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeConfigDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.barcode.MesWmBarcodeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.BarcodeFormatEnum;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -27,7 +43,13 @@
 */
@Service
@Validated
@Slf4j
public class MesWmBarcodeServiceImpl implements MesWmBarcodeService {
    /**
     * 内容格式模板中的占位符识别正则
     */
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{[A-Za-z0-9_]+\\}");
    @Resource
    private MesWmBarcodeMapper barcodeMapper;
@@ -43,7 +65,8 @@
        // 2. 生成条码内容,并校验唯一性
        String content = generateAndValidateContent(createReqVO.getId(),
                createReqVO.getContent(), config.getContentFormat(), createReqVO.getBizCode());
                createReqVO.getContent(), config.getContentFormat(), createReqVO.getBizCode(),
                createReqVO.getBizName());
        // 3. 保存条码记录
        MesWmBarcodeDO barcode = BeanUtils.toBean(createReqVO, MesWmBarcodeDO.class).setContent(content)
@@ -53,7 +76,7 @@
    }
    @Override
    public String generateBarcodeContent(Integer bizType, String bizCode) {
    public String generateBarcodeContent(Integer bizType, String bizCode, String bizName) {
        // 1.1 校验参数
        if (bizType == null) {
            throw exception(BARCODE_BIZ_TYPE_NOT_EXISTS);
@@ -65,7 +88,7 @@
        MesWmBarcodeConfigDO config = barcodeConfigService.validateBarcodeConfigByBizType(bizType);
        // 2. 生成条码内容
        return generateBarcodeContent(config.getContentFormat(), bizCode);
        return generateBarcodeContent(config.getContentFormat(), bizCode, bizName);
    }
    @Override
@@ -78,7 +101,8 @@
        // 2. 生成条码内容,并校验唯一性
        String content = generateAndValidateContent(updateReqVO.getId(),
                updateReqVO.getContent(), config.getContentFormat(), updateReqVO.getBizCode());
                updateReqVO.getContent(), config.getContentFormat(), updateReqVO.getBizCode(),
                updateReqVO.getBizName());
        // 3. 更新(刷新 configId 和 format,确保与最新配置一致)
        MesWmBarcodeDO updateObj = BeanUtils.toBean(updateReqVO, MesWmBarcodeDO.class)
@@ -131,10 +155,10 @@
     * @param bizCode 业务编码
     * @return 最终条码内容
     */
    private String generateAndValidateContent(Long id, String content, String contentFormat, String bizCode) {
    private String generateAndValidateContent(Long id, String content, String contentFormat, String bizCode, String bizName) {
        // 1. 如果前端未传递内容,则自动生成
        if (StrUtil.isBlank(content)) {
            content = generateBarcodeContent(contentFormat, bizCode);
            content = generateBarcodeContent(contentFormat, bizCode, bizName);
        }
        // 2. 校验条码内容唯一性(排除自身)
        if (StrUtil.isNotBlank(content)) {
@@ -151,13 +175,144 @@
     *
     * @param contentFormat 内容格式模板
     * @param bizCode 业务编码
     * @param bizName 业务名称
     * @return 条码内容
     */
    private String generateBarcodeContent(String contentFormat, String bizCode) {
    private String generateBarcodeContent(String contentFormat, String bizCode, String bizName) {
        if (StrUtil.isBlank(contentFormat)) {
            return bizCode;
        }
        return contentFormat.replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE, bizCode);
        // 1. 替换已支持的占位符
        String content = contentFormat
                .replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE, StrUtil.nullToEmpty(bizCode))
                .replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_NAME, StrUtil.nullToEmpty(bizName));
        // 2. 清空模板中其它未填充的未知占位符,避免 {XXX} 残留影响扫码
        return PLACEHOLDER_PATTERN.matcher(content).replaceAll("");
    }
    @Override
    public String renderBarcodeImage(String content, Integer format, Integer width, Integer height) {
        if (StrUtil.isBlank(content)) {
            throw exception(BARCODE_CONTENT_EMPTY);
        }
        int w = width != null && width > 0 ? width : 200;
        int h = height != null && height > 0 ? height : 200;
        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, toZxingFormat(format), w, h);
            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ImageIO.write(image, "png", out);
            return "data:image/png;base64," + Base64.encode(out.toByteArray());
        } catch (Exception e) {
            log.error("[renderBarcodeImage] 条码渲染失败,content={}, format={}", content, format, e);
            throw new RuntimeException("条码渲染失败");
        }
    }
    /**
     * 将项目条码格式枚举值映射为 zxing 条码格式
     *
     * @param format 项目条码格式 {@link BarcodeFormatEnum}
     * @return zxing 条码格式
     */
    private BarcodeFormat toZxingFormat(Integer format) {
        if (format == null) {
            return BarcodeFormat.CODE_128;
        }
        switch (format) {
            case 1: // BarcodeFormatEnum.QR_CODE
                return BarcodeFormat.QR_CODE;
            case 2: // BarcodeFormatEnum.EAN13
                return BarcodeFormat.EAN_13;
            case 3: // BarcodeFormatEnum.CODE39
                return BarcodeFormat.CODE_39;
            case 4: // BarcodeFormatEnum.UPC_A
                return BarcodeFormat.UPC_A;
            default:
                throw exception(BARCODE_FORMAT_INVALID);
        }
    }
    @Override
    public Map<String, Object> parseBarcodeContent(String content) {
        if (StrUtil.isBlank(content)) {
            throw exception(BARCODE_CONTENT_EMPTY);
        }
        // 1. 优先按条码内容精确命中已有条码记录
        MesWmBarcodeDO barcode = barcodeMapper.selectByContent(content);
        if (barcode != null) {
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("matched", true);
            result.put("barcodeId", barcode.getId());
            result.put("configId", barcode.getConfigId());
            result.put("format", barcode.getFormat());
            result.put("bizType", barcode.getBizType());
            result.put("bizId", barcode.getBizId());
            result.put("bizCode", barcode.getBizCode());
            result.put("bizName", barcode.getBizName());
            result.put("content", barcode.getContent());
            return result;
        }
        // 2. 未命中时,遍历启用状态的配置按内容格式模板反解
        List<MesWmBarcodeConfigDO> configs = barcodeConfigService.getEnableBarcodeConfigList();
        for (MesWmBarcodeConfigDO config : configs) {
            Map<String, Object> fields = matchByTemplate(config.getContentFormat(), content);
            if (fields != null) {
                Map<String, Object> result = new LinkedHashMap<>();
                result.put("matched", false);
                result.put("configId", config.getId());
                result.put("format", config.getFormat());
                result.put("bizType", config.getBizType());
                result.put("bizCode", fields.get(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE));
                result.put("bizName", fields.get(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_NAME));
                result.put("fields", fields);
                return result;
            }
        }
        // 3. 均未命中
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("matched", false);
        result.put("content", content);
        return result;
    }
    /**
     * 按内容格式模板反解条码内容
     *
     * @param contentFormat 内容格式模板
     * @param content 待解析的条码内容
     * @return 占位符 -> 实际值的映射;不匹配返回 null
     */
    private Map<String, Object> matchByTemplate(String contentFormat, String content) {
        if (StrUtil.isBlank(contentFormat) || !contentFormat.contains("{")) {
            return null;
        }
        // 1. 将模板转为正则:字面量转义,占位符转为捕获组
        List<String> placeholders = new ArrayList<>();
        StringBuilder regexSb = new StringBuilder();
        Matcher matcher = PLACEHOLDER_PATTERN.matcher(contentFormat);
        int lastEnd = 0;
        while (matcher.find()) {
            regexSb.append(Pattern.quote(contentFormat.substring(lastEnd, matcher.start())));
            placeholders.add(matcher.group());
            regexSb.append("(.+?)");
            lastEnd = matcher.end();
        }
        if (placeholders.isEmpty()) {
            return null;
        }
        regexSb.append(Pattern.quote(contentFormat.substring(lastEnd)));
        // 2. 全量匹配并提取各占位符值
        Matcher contentMatcher = Pattern.compile(regexSb.toString()).matcher(content);
        if (!contentMatcher.matches()) {
            return null;
        }
        Map<String, Object> fields = new LinkedHashMap<>();
        for (int i = 0; i < placeholders.size(); i++) {
            fields.put(placeholders.get(i), contentMatcher.group(i + 1));
        }
        return fields;
    }
    @Override