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<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
|
public void deleteBarcode(Long id) {
|
// 校验存在
|
validateBarcodeExists(id);
|
// 删除
|
barcodeMapper.deleteById(id);
|
}
|
|
@Override
|
public void deleteBarcodeByBizTypeAndBizIds(Integer bizType, Collection<Long> 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<MesWmBarcodeDO> 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);
|
}
|
|
}
|