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; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo.MesWmBarcodePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.barcode.vo.MesWmBarcodeSaveReqVO; 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.*; /** * MES 条码清单 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class MesWmBarcodeServiceImpl implements MesWmBarcodeService { /** * 内容格式模板中的占位符识别正则 */ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{[A-Za-z0-9_]+\\}"); @Resource private MesWmBarcodeMapper barcodeMapper; @Resource private MesWmBarcodeConfigService barcodeConfigService; @Override public Long createBarcode(MesWmBarcodeSaveReqVO createReqVO) { // 1. 校验数据 MesWmBarcodeConfigDO config = validateBarcodeSaveData(createReqVO.getId(), createReqVO.getBizType(), createReqVO.getBizId()); // 2. 生成条码内容,并校验唯一性 String content = generateAndValidateContent(createReqVO.getId(), createReqVO.getContent(), config.getContentFormat(), createReqVO.getBizCode(), createReqVO.getBizName()); // 3. 保存条码记录 MesWmBarcodeDO barcode = BeanUtils.toBean(createReqVO, MesWmBarcodeDO.class).setContent(content) .setConfigId(config.getId()).setFormat(config.getFormat()); barcodeMapper.insert(barcode); return barcode.getId(); } @Override public String generateBarcodeContent(Integer bizType, String bizCode, String bizName) { // 1.1 校验参数 if (bizType == null) { throw exception(BARCODE_BIZ_TYPE_NOT_EXISTS); } if (StrUtil.isBlank(bizCode)) { throw exception(BARCODE_BIZ_CODE_NOT_EXISTS); } // 1.2 查询条码配置 MesWmBarcodeConfigDO config = barcodeConfigService.validateBarcodeConfigByBizType(bizType); // 2. 生成条码内容 return generateBarcodeContent(config.getContentFormat(), bizCode, bizName); } @Override public void updateBarcode(MesWmBarcodeSaveReqVO updateReqVO) { // 1.1 校验存在 validateBarcodeExists(updateReqVO.getId()); // 1.2 校验数据 MesWmBarcodeConfigDO config = validateBarcodeSaveData(updateReqVO.getId(), updateReqVO.getBizType(), updateReqVO.getBizId()); // 2. 生成条码内容,并校验唯一性 String content = generateAndValidateContent(updateReqVO.getId(), updateReqVO.getContent(), config.getContentFormat(), updateReqVO.getBizCode(), updateReqVO.getBizName()); // 3. 更新(刷新 configId 和 format,确保与最新配置一致) MesWmBarcodeDO updateObj = BeanUtils.toBean(updateReqVO, MesWmBarcodeDO.class) .setConfigId(config.getId()).setFormat(config.getFormat()); if (StrUtil.isNotBlank(content)) { updateObj.setContent(content); } barcodeMapper.updateById(updateObj); } /** * 校验条码保存数据:条码配置 + 业务对象唯一性 * * @param id 条码编号(新增时为 null) * @param bizType 业务类型 * @param bizId 业务编号 * @return 条码配置 */ private MesWmBarcodeConfigDO validateBarcodeSaveData(Long id, Integer bizType, Long bizId) { // 1. 校验条码配置 MesWmBarcodeConfigDO config = barcodeConfigService.validateBarcodeConfigByBizType(bizType); // 2. 校验业务对象唯一性 validateBarcodeUnique(id, bizType, bizId); return config; } /** * 校验业务对象(bizType + bizId)唯一性 * * @param id 条码编号(新增时为 null,更新时排除自身) * @param bizType 业务类型 * @param bizId 业务编号 */ private void validateBarcodeUnique(Long id, Integer bizType, Long bizId) { MesWmBarcodeDO barcode = barcodeMapper.selectByBizTypeAndBizId(bizType, bizId); if (barcode == null) { return; } if (ObjUtil.notEqual(barcode, id)) { throw exception(WM_BARCODE_ALREADY_EXISTS); } } /** * 生成条码内容(如果前端未传递则自动生成),并校验内容唯一性 * * @param id 条码编号(新增时为 null,更新时排除自身) * @param content 前端传递的条码内容(可为空) * @param contentFormat 内容格式模板 * @param bizCode 业务编码 * @return 最终条码内容 */ private String generateAndValidateContent(Long id, String content, String contentFormat, String bizCode, String bizName) { // 1. 如果前端未传递内容,则自动生成 if (StrUtil.isBlank(content)) { content = generateBarcodeContent(contentFormat, bizCode, bizName); } // 2. 校验条码内容唯一性(排除自身) if (StrUtil.isNotBlank(content)) { MesWmBarcodeDO contentBarcode = barcodeMapper.selectByContent(content); if (contentBarcode != null && ObjUtil.notEqual(contentBarcode.getId(), id)) { throw exception(WM_BARCODE_CONTENT_DUPLICATE); } } return content; } /** * 生成条码内容 * * @param contentFormat 内容格式模板 * @param bizCode 业务编码 * @param bizName 业务名称 * @return 条码内容 */ private String generateBarcodeContent(String contentFormat, String bizCode, String bizName) { if (StrUtil.isBlank(contentFormat)) { return 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 parseBarcodeContent(String content) { if (StrUtil.isBlank(content)) { throw exception(BARCODE_CONTENT_EMPTY); } // 1. 优先按条码内容精确命中已有条码记录 MesWmBarcodeDO barcode = barcodeMapper.selectByContent(content); if (barcode != null) { Map 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 configs = barcodeConfigService.getEnableBarcodeConfigList(); for (MesWmBarcodeConfigDO config : configs) { Map fields = matchByTemplate(config.getContentFormat(), content); if (fields != null) { Map 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 result = new LinkedHashMap<>(); result.put("matched", false); result.put("content", content); return result; } /** * 按内容格式模板反解条码内容 * * @param contentFormat 内容格式模板 * @param content 待解析的条码内容 * @return 占位符 -> 实际值的映射;不匹配返回 null */ private Map matchByTemplate(String contentFormat, String content) { if (StrUtil.isBlank(contentFormat) || !contentFormat.contains("{")) { return null; } // 1. 将模板转为正则:字面量转义,占位符转为捕获组 List 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 fields = new LinkedHashMap<>(); for (int i = 0; i < placeholders.size(); i++) { fields.put(placeholders.get(i), contentMatcher.group(i + 1)); } return fields; } @Override public void deleteBarcode(Long id) { // 校验存在 validateBarcodeExists(id); // 删除 barcodeMapper.deleteById(id); } @Override public void deleteBarcodeByBizTypeAndBizIds(Integer bizType, Collection bizIds) { if (CollUtil.isEmpty(bizIds)) { return; } barcodeMapper.deleteByBizTypeAndBizIds(bizType, bizIds); } private MesWmBarcodeDO validateBarcodeExists(Long id) { MesWmBarcodeDO barcode = barcodeMapper.selectById(id); if (barcode == null) { throw exception(WM_BARCODE_NOT_EXISTS); } return barcode; } @Override public MesWmBarcodeDO getBarcode(Long id) { return barcodeMapper.selectById(id); } @Override public PageResult getBarcodePage(MesWmBarcodePageReqVO pageReqVO) { return barcodeMapper.selectPage(pageReqVO); } @Override public MesWmBarcodeDO getBarcodeByBizTypeAndBizId(Integer bizType, Long bizId) { return barcodeMapper.selectByBizTypeAndBizId(bizType, bizId); } @Override public void autoGenerateBarcode(Integer bizType, Long bizId, String bizCode, String bizName) { // 1.1 检查是否配置自动生成 MesWmBarcodeConfigDO config = barcodeConfigService.getBarcodeConfigByBizType(bizType); if (config == null || Boolean.FALSE.equals(config.getAutoGenerateFlag()) || CommonStatusEnum.isDisable(config.getStatus())) { return; } // 1.2 检查是否已存在条码 MesWmBarcodeDO existBarcode = barcodeMapper.selectByBizTypeAndBizId(bizType, bizId); if (existBarcode != null) { return; } // 2. 创建条码记录 MesWmBarcodeSaveReqVO createReqVO = new MesWmBarcodeSaveReqVO() .setBizType(bizType).setBizId(bizId).setBizCode(bizCode).setBizName(bizName) .setStatus(CommonStatusEnum.ENABLE.getStatus()); createBarcode(createReqVO); } @Override public long getBarcodeCountByConfigId(Long configId) { return barcodeMapper.selectCountByConfigId(configId); } }