3 天以前 b852254d90e7d1955db31db154193ec8ee84d8b3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
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);
    }
 
}