feat(mes): 实现袋码大批量生成异步任务功能
- 新增袋码批量生成配置属性,支持同步/异步分界线设置
- 实现批量取码Redis脚本优化,减少网络往返次数
- 添加袋码生成任务数据对象和状态管理机制
- 实现分段落库和后台任务执行器,支持进度跟踪和取消
- 扩展袋码生成接口,支持同步/异步双模式自动切换
- 添加任务查询和取消API,支持前端轮询进度显示
- 实现生成失败自动清理机制,避免残留半成品数据
- 新增幂等键机制防止网络重试导致重复生成
- 添加相关错误码定义和文档说明
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import java.nio.file.*; |
| | | import java.sql.*; |
| | | import java.util.*; |
| | | |
| | | public class TempDbAdmin { |
| | | public static void main(String[] args) throws Exception { |
| | | String yaml = Files.readString(Path.of("yudao-server/src/main/resources/application-thzy.yaml")); |
| | | String block = yaml.substring(yaml.indexOf("master:")); |
| | | String[] lines = block.split("\\R"); |
| | | String user = null, pass = null; |
| | | boolean master = false; |
| | | for (String line : lines) { |
| | | if (line.trim().equals("master:")) { master = true; continue; } |
| | | if (!master) continue; |
| | | if (line.trim().startsWith("username:")) user = line.trim().substring(9).trim().split("\\s+#", 2)[0]; |
| | | if (line.trim().startsWith("password:")) { pass = line.trim().substring(9).trim().split("\\s+#", 2)[0]; break; } |
| | | } |
| | | if (user == null || pass == null) throw new IllegalStateException("Missing database credentials"); |
| | | String url = "jdbc:mysql://1.95.78.124:9000/mom-thzynew?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"; |
| | | try (Connection conn = DriverManager.getConnection(url, user, pass)) { |
| | | System.out.println("DATABASE=" + conn.getCatalog()); |
| | | if (args.length == 0) return; |
| | | try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(args[0])) { |
| | | ResultSetMetaData md = rs.getMetaData(); |
| | | for (int i = 1; i <= md.getColumnCount(); i++) System.out.print((i > 1 ? "\t" : "") + md.getColumnLabel(i)); |
| | | System.out.println(); |
| | | while (rs.next()) { |
| | | for (int i = 1; i <= md.getColumnCount(); i++) System.out.print((i > 1 ? "\t" : "") + Objects.toString(rs.getObject(i), "NULL")); |
| | | System.out.println(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import java.io.*; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.*; |
| | | import java.security.MessageDigest; |
| | | import java.sql.*; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.zip.GZIPOutputStream; |
| | | |
| | | /** Temporary, scoped administrator for the requested mom-thzynew data cleanup. */ |
| | | public class TempDbCleanup { |
| | | private static final String DB = "mom-thzynew"; |
| | | private static final Set<String> PRO_TABLES = Set.of( |
| | | "mes_pro_bag_code", "mes_pro_bag_code_replace", "mes_pro_batch", |
| | | "mes_pro_batch_worker", "mes_pro_pallet", "mes_pro_qr_export_task", |
| | | "mes_pro_scan_event"); |
| | | private static final Set<String> KEEP = Set.of("mes_wm_warehouse", "mes_wm_warehouse_area", |
| | | "mes_wm_warehouse_location", "mes_pro_line"); |
| | | |
| | | public static void main(String[] args) throws Exception { |
| | | if (args.length < 1 || !Set.of("inventory", "backup", "clean").contains(args[0]) |
| | | || (args[0].equals("clean") ? args.length != 2 : args.length != 1)) { |
| | | throw new IllegalArgumentException("Usage: TempDbCleanup inventory|backup|clean <backup.sql.gz>"); |
| | | } |
| | | String yaml = Files.readString(Path.of("yudao-server/src/main/resources/application-thzy.yaml")); |
| | | String block = yaml.substring(yaml.indexOf("master:")); |
| | | String user = null, pass = null; |
| | | for (String line : block.split("\\R")) { |
| | | if (line.trim().startsWith("username:")) user = value(line); |
| | | if (line.trim().startsWith("password:")) { pass = value(line); break; } |
| | | } |
| | | if (user == null || pass == null) throw new IllegalStateException("Missing database credentials"); |
| | | String url = "jdbc:mysql://1.95.78.124:9000/" + DB |
| | | + "?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useCursorFetch=true"; |
| | | try (Connection conn = DriverManager.getConnection(url, user, pass)) { |
| | | if (!DB.equals(conn.getCatalog())) throw new IllegalStateException("Wrong database: " + conn.getCatalog()); |
| | | List<String> tables = targetTables(conn); |
| | | System.out.println("DATABASE=" + DB + " TARGET_TABLES=" + tables.size()); |
| | | if (args[0].equals("inventory")) inventory(conn, tables); |
| | | if (args[0].equals("backup")) backup(conn, tables); |
| | | if (args[0].equals("clean")) clean(conn, tables, Path.of(args[1])); |
| | | } |
| | | } |
| | | |
| | | private static String value(String line) { |
| | | return line.trim().split(":", 2)[1].trim().split("\\s+#", 2)[0]; |
| | | } |
| | | |
| | | private static List<String> targetTables(Connection conn) throws SQLException { |
| | | List<String> names = new ArrayList<>(); |
| | | try (Statement s = conn.createStatement(); ResultSet r = s.executeQuery( |
| | | "SELECT table_name FROM information_schema.tables WHERE table_schema='" + DB + "' " |
| | | + "AND table_type='BASE TABLE' ORDER BY table_name")) { |
| | | while (r.next()) { |
| | | String t = r.getString(1); |
| | | if (KEEP.contains(t)) continue; |
| | | if (PRO_TABLES.contains(t) || t.startsWith("mes_qc_") || t.startsWith("mes_wm_") |
| | | || t.startsWith("mes_trace_") || t.startsWith("crm_")) names.add(t); |
| | | } |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | private static long count(Connection conn, String table) throws SQLException { |
| | | try (Statement s = conn.createStatement(); ResultSet r = s.executeQuery("SELECT COUNT(*) FROM `" + table + "`")) { |
| | | r.next(); return r.getLong(1); |
| | | } |
| | | } |
| | | |
| | | private static void inventory(Connection conn, List<String> tables) throws SQLException { |
| | | for (String t : tables) { |
| | | long n = count(conn, t); |
| | | if (n > 0) System.out.println(t + "=" + n); |
| | | } |
| | | for (String t : KEEP) System.out.println("KEEP " + t + "=" + count(conn, t)); |
| | | try (Statement s = conn.createStatement(); ResultSet r = s.executeQuery( |
| | | "SELECT status, COUNT(*), SUM(total_count-finished_count) FROM mes_pro_qr_export_task " |
| | | + "WHERE status IN (10,20) GROUP BY status")) { |
| | | while (r.next()) System.out.println("ACTIVE_EXPORT status=" + r.getInt(1) |
| | | + " tasks=" + r.getLong(2) + " remaining=" + r.getLong(3)); |
| | | } |
| | | } |
| | | |
| | | private static void requireIdle(Connection conn) throws SQLException { |
| | | try (Statement s = conn.createStatement(); ResultSet r = s.executeQuery( |
| | | "SELECT COUNT(*) FROM mes_pro_qr_export_task WHERE status IN (10,20)")) { |
| | | r.next(); |
| | | if (r.getLong(1) != 0) throw new IllegalStateException("QR export task still active; aborting"); |
| | | } |
| | | } |
| | | |
| | | private static void backup(Connection conn, List<String> tables) throws Exception { |
| | | requireIdle(conn); |
| | | Path dir = Path.of("db-backups"); |
| | | Files.createDirectories(dir); |
| | | String stamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); |
| | | Path dest = dir.resolve(DB + "-business-cleanup-" + stamp + ".sql.gz"); |
| | | Path temp = dir.resolve(dest.getFileName() + ".part"); |
| | | long total = 0; |
| | | Properties manifest = new Properties(); |
| | | manifest.setProperty("database", DB); |
| | | manifest.setProperty("table.count", Integer.toString(tables.size())); |
| | | conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); |
| | | conn.setAutoCommit(false); |
| | | try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter( |
| | | new GZIPOutputStream(Files.newOutputStream(temp), 1024 * 1024), StandardCharsets.UTF_8), 1024 * 1024)) { |
| | | out.write("-- mom-thzynew requested business-data backup; restore only to the intended database\n"); |
| | | out.write("SET NAMES utf8mb4;\nSET sql_mode='';\n"); |
| | | for (String table : tables) { |
| | | long rows = 0; |
| | | try (Statement s = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { |
| | | s.setFetchSize(1000); |
| | | try (ResultSet r = s.executeQuery("SELECT * FROM `" + table + "`")) { |
| | | ResultSetMetaData md = r.getMetaData(); |
| | | int cols = md.getColumnCount(); |
| | | StringJoiner columns = new StringJoiner(","); |
| | | for (int i = 1; i <= cols; i++) columns.add("`" + md.getColumnName(i) + "`"); |
| | | String prefix = "INSERT INTO `" + table + "` (" + columns + ") VALUES ("; |
| | | while (r.next()) { |
| | | out.write(prefix); |
| | | for (int i = 1; i <= cols; i++) { |
| | | if (i > 1) out.write(','); |
| | | writeSqlValue(out, r, md, i); |
| | | } |
| | | out.write(");\n"); |
| | | rows++; |
| | | } |
| | | } |
| | | } |
| | | total += rows; |
| | | manifest.setProperty("row." + table, Long.toString(rows)); |
| | | if (rows > 0) System.out.println("BACKED_UP " + table + "=" + rows); |
| | | } |
| | | out.write("-- backup complete: " + total + " rows\n"); |
| | | conn.rollback(); |
| | | } catch (Exception e) { |
| | | conn.rollback(); |
| | | Files.deleteIfExists(temp); |
| | | throw e; |
| | | } finally { |
| | | conn.setAutoCommit(true); |
| | | } |
| | | Files.move(temp, dest, StandardCopyOption.ATOMIC_MOVE); |
| | | manifest.setProperty("sha256", sha256(dest)); |
| | | manifest.setProperty("rows.total", Long.toString(total)); |
| | | try (OutputStream m = Files.newOutputStream(Path.of(dest + ".manifest"))) { |
| | | manifest.store(m, "mom-thzynew business data backup manifest"); |
| | | } |
| | | System.out.println("BACKUP_FILE=" + dest.toAbsolutePath() + " ROWS=" + total |
| | | + " BYTES=" + Files.size(dest)); |
| | | } |
| | | |
| | | private static void writeSqlValue(Writer out, ResultSet r, ResultSetMetaData md, int i) throws Exception { |
| | | int type = md.getColumnType(i); |
| | | if (type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY |
| | | || type == Types.BLOB || type == Types.BIT) { |
| | | byte[] bytes = r.getBytes(i); |
| | | if (bytes == null) { out.write("NULL"); return; } |
| | | out.write("X'"); |
| | | for (byte b : bytes) out.write(String.format("%02X", b & 0xff)); |
| | | out.write("'"); |
| | | return; |
| | | } |
| | | String value = r.getString(i); |
| | | if (value == null) { out.write("NULL"); return; } |
| | | if (type == Types.INTEGER || type == Types.BIGINT || type == Types.SMALLINT |
| | | || type == Types.TINYINT || type == Types.DECIMAL || type == Types.NUMERIC |
| | | || type == Types.DOUBLE || type == Types.FLOAT || type == Types.REAL) { |
| | | out.write(value); return; |
| | | } |
| | | out.write('\''); |
| | | for (int p = 0; p < value.length(); p++) { |
| | | char c = value.charAt(p); |
| | | switch (c) { |
| | | case '\\' -> out.write("\\\\"); |
| | | case '\'' -> out.write("\\'"); |
| | | case '\n' -> out.write("\\n"); |
| | | case '\r' -> out.write("\\r"); |
| | | case '\0' -> out.write("\\0"); |
| | | case 26 -> out.write("\\Z"); |
| | | default -> out.write(c); |
| | | } |
| | | } |
| | | out.write('\''); |
| | | } |
| | | |
| | | private static String sha256(Path path) throws Exception { |
| | | MessageDigest digest = MessageDigest.getInstance("SHA-256"); |
| | | try (InputStream in = Files.newInputStream(path)) { |
| | | byte[] buf = new byte[1024 * 1024]; |
| | | int n; |
| | | while ((n = in.read(buf)) != -1) digest.update(buf, 0, n); |
| | | } |
| | | return HexFormat.of().formatHex(digest.digest()); |
| | | } |
| | | |
| | | private static void clean(Connection conn, List<String> tables, Path backup) throws Exception { |
| | | requireIdle(conn); |
| | | Properties manifest = new Properties(); |
| | | try (InputStream in = Files.newInputStream(Path.of(backup + ".manifest"))) { manifest.load(in); } |
| | | if (!DB.equals(manifest.getProperty("database")) |
| | | || tables.size() != Integer.parseInt(manifest.getProperty("table.count")) |
| | | || !sha256(backup).equals(manifest.getProperty("sha256"))) { |
| | | throw new IllegalStateException("Backup integrity or target mismatch"); |
| | | } |
| | | // Verify every table before any destructive statement. New writes cause a hard abort. |
| | | Map<String, Long> expected = new LinkedHashMap<>(); |
| | | for (String table : tables) { |
| | | String raw = manifest.getProperty("row." + table); |
| | | if (raw == null) throw new IllegalStateException("Missing backup count: " + table); |
| | | long n = Long.parseLong(raw); |
| | | long now = count(conn, table); |
| | | if (n != now) throw new IllegalStateException("Table changed since backup: " + table |
| | | + " backup=" + n + " current=" + now); |
| | | expected.put(table, n); |
| | | } |
| | | for (String table : List.of("mes_pro_line", "mes_wm_warehouse", "mes_wm_warehouse_area", |
| | | "mes_wm_warehouse_location")) { |
| | | System.out.println("PRESERVE " + table + "=" + count(conn, table)); |
| | | } |
| | | for (Map.Entry<String, Long> entry : expected.entrySet()) { |
| | | if (entry.getValue() == 0) continue; |
| | | requireIdle(conn); |
| | | try (Statement s = conn.createStatement()) { |
| | | s.executeUpdate("TRUNCATE TABLE `" + entry.getKey() + "`"); |
| | | } |
| | | System.out.println("CLEARED " + entry.getKey() + "=" + entry.getValue()); |
| | | } |
| | | inventory(conn, tables); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #mom-thzynew business data backup manifest |
| | | #Mon Oct 19 17:23:57 CST 2026 |
| | | database=mom-thzynew |
| | | row.crm_business=0 |
| | | row.crm_business_product=0 |
| | | row.crm_business_status=0 |
| | | row.crm_business_status_type=0 |
| | | row.crm_clue=0 |
| | | row.crm_contact=0 |
| | | row.crm_contact_business=0 |
| | | row.crm_contract=0 |
| | | row.crm_contract_config=0 |
| | | row.crm_contract_product=0 |
| | | row.crm_customer=0 |
| | | row.crm_customer_limit_config=0 |
| | | row.crm_customer_pool_config=0 |
| | | row.crm_follow_up_record=0 |
| | | row.crm_invoice=0 |
| | | row.crm_permission=0 |
| | | row.crm_receivable=0 |
| | | row.crm_receivable_plan=0 |
| | | row.crm_sale_quotation=0 |
| | | row.crm_sale_quotation_product=0 |
| | | row.crm_sales_target=0 |
| | | row.mes_pro_bag_code=1436003 |
| | | row.mes_pro_bag_code_replace=0 |
| | | row.mes_pro_batch=0 |
| | | row.mes_pro_batch_worker=0 |
| | | row.mes_pro_pallet=25600 |
| | | row.mes_pro_qr_export_task=16 |
| | | row.mes_pro_scan_event=0 |
| | | row.mes_qc_approver_config=0 |
| | | row.mes_qc_defect=0 |
| | | row.mes_qc_defect_record=0 |
| | | row.mes_qc_indicator=0 |
| | | row.mes_qc_indicator_result=0 |
| | | row.mes_qc_indicator_result_detail=0 |
| | | row.mes_qc_ipqc=0 |
| | | row.mes_qc_ipqc_line=0 |
| | | row.mes_qc_iqc=0 |
| | | row.mes_qc_iqc_line=0 |
| | | row.mes_qc_ncr=0 |
| | | row.mes_qc_oqc=0 |
| | | row.mes_qc_oqc_action=0 |
| | | row.mes_qc_oqc_line=0 |
| | | row.mes_qc_rqc=0 |
| | | row.mes_qc_rqc_line=0 |
| | | row.mes_qc_seed_indicator_range=0 |
| | | row.mes_qc_seed_quality=0 |
| | | row.mes_qc_seed_quality_action=0 |
| | | row.mes_qc_seed_quality_indicator=0 |
| | | row.mes_qc_seed_quality_source=0 |
| | | row.mes_qc_template=0 |
| | | row.mes_qc_template_indicator=0 |
| | | row.mes_qc_template_item=0 |
| | | row.mes_trace_anti_config=1 |
| | | row.mes_trace_consumer_scan_event=62 |
| | | row.mes_trace_cross_region_warning=0 |
| | | row.mes_trace_event=0 |
| | | row.mes_trace_integrity_record=62 |
| | | row.mes_wm_arrival_notice=0 |
| | | row.mes_wm_arrival_notice_line=0 |
| | | row.mes_wm_barcode=0 |
| | | row.mes_wm_barcode_config=0 |
| | | row.mes_wm_batch=0 |
| | | row.mes_wm_item_consume=0 |
| | | row.mes_wm_item_consume_detail=0 |
| | | row.mes_wm_item_consume_line=0 |
| | | row.mes_wm_item_receipt=0 |
| | | row.mes_wm_item_receipt_detail=0 |
| | | row.mes_wm_item_receipt_line=0 |
| | | row.mes_wm_material_stock=0 |
| | | row.mes_wm_material_stock_bag=0 |
| | | row.mes_wm_misc_issue=0 |
| | | row.mes_wm_misc_issue_detail=0 |
| | | row.mes_wm_misc_issue_line=0 |
| | | row.mes_wm_misc_receipt=0 |
| | | row.mes_wm_misc_receipt_detail=0 |
| | | row.mes_wm_misc_receipt_line=0 |
| | | row.mes_wm_outsource_issue=0 |
| | | row.mes_wm_outsource_issue_detail=0 |
| | | row.mes_wm_outsource_issue_line=0 |
| | | row.mes_wm_outsource_receipt=0 |
| | | row.mes_wm_outsource_receipt_detail=0 |
| | | row.mes_wm_outsource_receipt_line=0 |
| | | row.mes_wm_package=0 |
| | | row.mes_wm_package_line=0 |
| | | row.mes_wm_product_issue=0 |
| | | row.mes_wm_product_issue_detail=0 |
| | | row.mes_wm_product_issue_line=0 |
| | | row.mes_wm_product_produce=0 |
| | | row.mes_wm_product_produce_detail=0 |
| | | row.mes_wm_product_produce_line=0 |
| | | row.mes_wm_product_receipt=0 |
| | | row.mes_wm_product_receipt_detail=0 |
| | | row.mes_wm_product_receipt_line=0 |
| | | row.mes_wm_product_sales=0 |
| | | row.mes_wm_product_sales_detail=0 |
| | | row.mes_wm_product_sales_line=0 |
| | | row.mes_wm_return_issue=0 |
| | | row.mes_wm_return_issue_detail=0 |
| | | row.mes_wm_return_issue_line=0 |
| | | row.mes_wm_return_sales=0 |
| | | row.mes_wm_return_sales_detail=0 |
| | | row.mes_wm_return_sales_line=0 |
| | | row.mes_wm_return_vendor=0 |
| | | row.mes_wm_return_vendor_detail=0 |
| | | row.mes_wm_return_vendor_line=0 |
| | | row.mes_wm_sales_notice=0 |
| | | row.mes_wm_sales_notice_line=0 |
| | | row.mes_wm_sn=0 |
| | | row.mes_wm_stock_reserve=0 |
| | | row.mes_wm_stock_taking_plan=0 |
| | | row.mes_wm_stock_taking_plan_param=0 |
| | | row.mes_wm_stock_taking_task=0 |
| | | row.mes_wm_stock_taking_task_line=0 |
| | | row.mes_wm_stock_taking_task_result=0 |
| | | row.mes_wm_stock_warning=0 |
| | | row.mes_wm_transaction=0 |
| | | row.mes_wm_transfer=0 |
| | | row.mes_wm_transfer_detail=0 |
| | | row.mes_wm_transfer_line=0 |
| | | rows.total=1461744 |
| | | sha256=13f2a900bfcfac80d6aec291b23750a2482ae988d3e8888dacd9f41e46eaa6f5 |
| | | table.count=119 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # èµç 管ç大æ¹éçææ¹é æ¹æ¡ï¼å¼æ¥ä»»å¡åï¼ |
| | | |
| | | ## ä¸ãèæ¯ |
| | | |
| | | ãçæäºç»´ç æ°æ®å
ãï¼`POST /mes/pro/bag-code/generate`ï¼ä¸ãçæåå§æ°æ®ãï¼`POST /mes/pro/bag-code/raw-generate`ï¼å¨å å䏿¡æ°æ®éä¸ç´æ¥è¶
æ¶ï¼å端æ¶ä¸å°ååºã |
| | | |
| | | ç»ä»£ç å®ä½ï¼è¶
æ¶ä¸æ¯åä¸åå ï¼èæ¯ä¸å¤å å ï¼ |
| | | |
| | | | ç¶é¢ | ä½ç½® | 说æ | |
| | | |------|------|------| |
| | | | éæ¡å Redis è¦ç | `MesProBagCodeServiceImpl.java:143-144`ã`176-177` | æ¯ä¸ªè¢ç çæã追溯ç + ä¸å¡ç ã两次ï¼`MesCodeRedisDAO.generate()` æ¯æ¬¡æ§è¡ä¸æ¬¡ Redis `SADD` å¾è¿ï¼`MesCodeRedisDAO.java:53`ï¼ã30 ä¸è¢ = 60 䏿¬¡ç½ç»å¾è¿ï¼ä»
æ¤ä¸æ¥å³åé级 | |
| | | | åäºå¡å·¨éæå
¥ | `MesProBagCodeServiceImpl.java:154`ã`185` | `insertBatch` é»è®¤ 1000 æ¡/æ¹ï¼ä½æ´æ¹å¨åä¸ä¸ª `@Transactional` å
宿ï¼å åä¸è¡å½¢æè¶
大äºå¡ | |
| | | | 忥é¿è¯·æ± | Controller ç´æ¥è¿åçæç»æ | 请æ±çº¿ç¨ä»å¤´æ§è¡å°å°¾ï¼ç½å
³/æµè§å¨è¶
æ¶åè¿æ¥æå¼ï¼ä½æå¡ç«¯ä»ç»§ç»ååºãç¨æ·éè¯ä¼åçæä¸æ´å
é夿°æ®ï¼ä¸æ å¹çä¿æ¤ | |
| | | |
| | | æ¤å¤ï¼`selectMaxBagNo()` 对 `mes_pro_bag_code` å `MAX(bag_no)`ï¼å½å表没æ `bag_no` ç´¢å¼ï¼æ°æ®é䏿¥åæ¯æ¬¡çæé½è¦å
¨è¡¨æ«æã |
| | | |
| | | ## äºãç®æ ä¸éç®æ |
| | | |
| | | ### ç®æ |
| | | |
| | | 1. å å䏿¡ï¼å次ä¸é 100 ä¸ï¼çæä¸åä¾èµå次 HTTP è¯·æ±æ¶é¿ï¼æäº¤åç«å³è¿åä»»å¡ç¼å·ã |
| | | 2. çæè¿ç¨å¯è§è¿åº¦ãå¯åæ¶ï¼å¤±è´¥/åæ¶ä¸çä¸åæåæ°æ®å
ã |
| | | 3. æ¶é¤éæ¡ Redis å¾è¿ä¸è¶
大äºå¡ï¼çæåå䏿°æ®éè§£è¦ã |
| | | 4. å°æ¹éä¿æåæ¥ç§åï¼ä¸æ¹å使ç¨ä½éªã |
| | | 5. æ¥å£å
·å¤å¹çæ§ï¼ç½ç»éè¯ä¸ä¼éå¤çæã |
| | | |
| | | ### éç®æ |
| | | |
| | | - 䏿¹é ãç ´ææ¿æ¢ããç»å®æ¹æ¬¡/æçãçå
¶ä»èµç æä½ï¼å®ä»¬æ¬èº«æ¯å°éæ°æ®ççäºå¡ã |
| | | - ä¸åæå¡éå¯åçæç¹ç»è·ï¼è§ 5.7 åè说æï¼ã |
| | | - ä¸è°æ´ 16 ä½éæºç ççæè§åä¸ `biz:code:used` éåæºå¶ã |
| | | |
| | | ## ä¸ãæ»ä½æ¹æ¡ |
| | | |
| | | éç¨ä¸ç°æäºç»´ç 导åºä»»å¡ï¼`MesProQrExportTask`ï¼ä¸è´çãå°æ¹é忥ã大æ¹é弿¥ãåè·¯å¾ï¼ |
| | | |
| | | ``` |
| | | å端æäº¤çæ |
| | | â |
| | | ââ è¢ç æ»æ° ⤠sync-thresholdï¼é»è®¤ 20000ï¼ |
| | | â ââ 请æ±çº¿ç¨å
å®æï¼æ¹éåç â åæ®µè½åº â è¿ååæç»æï¼async=falseï¼ |
| | | â |
| | | ââ è¢ç æ»æ° > sync-threshold |
| | | ââ è½ä»»å¡è®°å½ï¼æ³¨åè¿è¡æï¼æéåå°çº¿ç¨æ± ï¼ç«å³è¿å taskId + uuidï¼async=trueï¼ |
| | | ââ å端æ taskId 轮询è¿åº¦ï¼å®æåå·æ°å表ï¼åæ¶åå é¤å·²çæçé¨åæ°æ® |
| | | ``` |
| | | |
| | | 忥ä¸å¼æ¥å
±ç¨åä¸å¥ãåæ®µçæãæ§è¡å¨ï¼é¿å
两份é»è¾ã |
| | | |
| | | ## åã任塿¨¡å |
| | | |
| | | ### 4.1 ç¶ææº |
| | | |
| | | | ç¶æå¼ | åç§° | 说æ | |
| | | |--------|------|------| |
| | | | 10 | æéä¸ | å·²è½åºï¼çå¾
çº¿ç¨æ± æ¥ç®¡ | |
| | | | 20 | çæä¸ | åå°çº¿ç¨æ§è¡ä¸ | |
| | | | 25 | åæ¶ä¸ | å·²æ¶å°åæ¶è¯·æ±ï¼æ£å¨åæ¢å¹¶æ¸
çé¨åæ°æ® | |
| | | | 30 | 已宿 | å
¨é¨è¢ç ï¼åæçï¼å·²è½åº | |
| | | | 40 | 已忶 | ç¨æ·åæ¶ï¼é¨åæ°æ®å·²æ¸
ç | |
| | | | 50 | 已失败 | å¼å¸¸ç»æ¢ï¼é¨åæ°æ®å·²æ¸
ç | |
| | | |
| | | ç»æï¼30 / 40 / 50ã |
| | | |
| | | ### 4.2 è¡¨ç»æ |
| | | |
| | | æ°å¢ `mes_pro_bag_code_generate_task`ï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | BIGINT | ä¸»é® | |
| | | | request_id | VARCHAR(64) | å¹çé®ï¼å¯ä¸ç´¢å¼ï¼åç«¯æ¯æ¬¡æäº¤çæä¸ä¸ª UUID | |
| | | | biz_type | VARCHAR(16) | `PACKAGE`ï¼æ°æ®å
ï¼/ `RAW`ï¼åå§æ°æ®ï¼ | |
| | | | uuid | VARCHAR(32) | æ¬æ¬¡çæç» UUIDï¼æäº¤æ¶å³çæï¼å¤±è´¥/åæ¶åç¨äºæ¸
çï¼ | |
| | | | item_id / item_code / item_name | BIGINT / VARCHAR | åç§å¿«ç
§ | |
| | | | unit_type | VARCHAR(32) | 计éåä½ç±»åï¼å¨ / 48è¢ / 100è¢ï¼ | |
| | | | per_pallet | INT | æ¯æè¢æ°ï¼å¨ç±»å为 0 | |
| | | | pallet_count | INT | æçæ°ï¼åå§æ°æ®ä¸º 0 | |
| | | | total_count | INT | è¢ç æ»æ° | |
| | | | finished_count | INT | å·²çæè¢ç æ° | |
| | | | status | TINYINT | ç¶æï¼è§ 4.1 | |
| | | | error_msg | VARCHAR(500) | 失败åå | |
| | | | finish_time | DATETIME | 宿/ç»æ¢æ¶é´ | |
| | | | creator / create_time / updater / update_time / deleted | â | æ²¿ç¨ `BaseDO` | |
| | | |
| | | ç´¢å¼ï¼ |
| | | |
| | | - `UNIQUE KEY uk_request_id (request_id)` |
| | | - `KEY idx_status (status)` |
| | | - `KEY idx_uuid (uuid)` |
| | | |
| | | è¿ç§»èæ¬ï¼`sql/mysql/mes_pro_bag_code_generate_task.sql`ã |
| | | |
| | | ### 4.3 è¢ç è¡¨ç´¢å¼ |
| | | |
| | | æ°å¢ `KEY idx_bag_no (bag_no)`ï¼ç¨äº `selectMaxBagNo()` ä¸è¢åºå·æåºã |
| | | |
| | | è¿ç§»èæ¬ï¼`sql/mysql/mes_pro_bag_code_bag_no_index.sql`ï¼å
æ¥ `information_schema.statistics` å建ï¼å
¼å®¹é夿§è¡ï¼ã |
| | | |
| | | ## äºã详ç»è®¾è®¡ |
| | | |
| | | ### 5.1 æ¥å£ |
| | | |
| | | #### 1ï¼çææ°æ®å
/ åå§æ°æ®ï¼æ²¿ç¨åè·¯å¾ï¼ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/mes/pro/bag-code/generate` | åå¹¶åçæ°æ®å
çæå
¥å£ | |
| | | | POST | `/mes/pro/bag-code/raw-generate` | åå§è¢ç çæå
¥å£ | |
| | | |
| | | 请æ±åæ°ä¸åãååºæ©å±ï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | async | Boolean | true=已转åå°ä»»å¡ï¼false=æ¬æ¬¡åæ¥å®æ | |
| | | | taskId | Long | 弿¥ä»»å¡ç¼å·ï¼åæ¥æ¶ä¸º null | |
| | | | uuid | String | çæç» UUIDï¼ä¸¤ç§æ¨¡å¼é½è¿å | |
| | | | type / perPallet | String / Integer | æ°æ®å
模å¼ä¸è¿å | |
| | | | totalCount | Integer | è¢ç æ»æ° | |
| | | | palletCount | Integer | æçæ»æ°ï¼åå§æ°æ®ä¸º 0 | |
| | | | palletIds | List\<Long\> | ä»
忥宿æ¶è¿åï¼å¼æ¥æ¨¡å¼ä¸º nullï¼é¿å
ä¸å个 ID æå¤§ååº | |
| | | |
| | | å端æ `totalCount` èªå¨åæµï¼å端ä¸éè¦åèªè®¡ç®éå¼ã |
| | | |
| | | #### 2ï¼ä»»å¡æ¥è¯¢ï¼è½®è¯¢ï¼ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | `/mes/pro/bag-code/generate-task/get?id=` | æ¥è¯¢ä»»å¡ç¶æä¸è¿åº¦ | |
| | | |
| | | ååºå段ï¼`id`ã`bizType`ã`status`ã`statusName`ã`progress`ï¼0~100ï¼ã`totalCount`ã`finishedCount`ã`palletCount`ã`itemId`ã`itemCode`ã`itemName`ã`unitType`ã`perPallet`ã`uuid`ã`errorMsg`ã`createTime`ã`finishTime`ã |
| | | |
| | | #### 3ï¼ä»»å¡åæ¶ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/mes/pro/bag-code/generate-task/cancel?id=` | 请æ±åæ¶ï¼å·²ç»æä»»å¡æç» | |
| | | |
| | | æéï¼æäº¤/åæ¶ç¨ `mes:pro-bag-code:create`ï¼æ¥è¯¢ç¨ `mes:pro-bag-code:query`ã |
| | | |
| | | ### 5.2 æäº¤é¶æ®µ |
| | | |
| | | 1. æ ¡éªåç§ã计éåä½ç±»åï¼æ¢ç®è¢ç æ»æ°ï¼ä¸åé»è¾ä¸è´ï¼ã |
| | | 2. æ°éæ ¡éªï¼`totalCount ⤠maxTaskCount`ï¼é»è®¤ 1,000,000ï¼ï¼è¶
åºç´æ¥æ¥éã |
| | | 3. å¹çå¤çï¼`requestId` é空æ¶å
æ¥ä»»å¡è¡¨ï¼å½ä¸åç´æ¥è¿åå·²æä»»å¡ï¼ä¸éå¤çæãå¹¶åéå¤æäº¤ç± `uk_request_id` å¯ä¸ç´¢å¼å
åºï¼æè·å²çªå忥ã |
| | | 4. æ°é ⤠`syncThreshold`ï¼èµ°åæ¥æ§è¡ï¼è¿å `async=false`ã |
| | | 5. æ°é > `syncThreshold`ï¼ |
| | | - çæä»»å¡ UUIDï¼ |
| | | - æå
¥ä»»å¡è®°å½ï¼ç¶æ=10ï¼ï¼ |
| | | - å
注åå
åè¿è¡æï¼åæéçº¿ç¨æ± ï¼ä¸å¯¼åºä»»å¡ä¸è´ï¼é¿å
æ¸
çä»»å¡è¯¯åæ¶ï¼ï¼ |
| | | - è¿å `async=true`ã`taskId`ã`uuid`ã`totalCount`ã`palletCount`ã |
| | | 6. å¹¶åé¸é¨ï¼å䏿¶é´æå¤ `maxConcurrentTasks`ï¼é»è®¤ 1ï¼ä¸ªçæä»»å¡ãå ç¨å¤±è´¥æ¶è¿åãå·²æçæä»»å¡è¿è¡ä¸ã并带ä¸å½åä»»å¡è¿åº¦ã |
| | | 7. çº¿ç¨æ± æç»æ¶æä»»å¡ç½®ä¸ºå¤±è´¥å¹¶å½è¿å¹¶å许å¯ã |
| | | |
| | | ### 5.3 æ¹éåç ï¼æ ¸å¿ä¼åï¼ |
| | | |
| | | å°ãéæ¡ `SADD`ãæ¹ä¸ºãæ¹éèæ¬åå
¥ãï¼ |
| | | |
| | | 1. Java ä¾§æ 5000 ä¸ªä¸æ¹çæåéç ï¼`ThreadLocalRandom` ç²¾ç¡®çæ 16 使°åï¼ã |
| | | 2. 䏿¬¡ Lua èæ¬åå宿ï¼é个 `SADD` å° `biz:code:used`ï¼è¿åæ¬æ¹å®é
åå
¥æåçç ã |
| | | 3. è¥æåæ°ä¸è¶³ï¼éæºç¢°æï¼æ¦çæä½ï¼ï¼åªå¯¹ç¼ºé¢ç»§ç»çæä¸ä¸æ¹ï¼ç´å°åé½ã |
| | | 4. 追溯ç ä¸ä¸å¡ç åèµ°ä¸æ¬¡æ¹éæµç¨ã |
| | | |
| | | ææï¼30 ä¸è¢ä» 60 䏿¬¡ç½ç»å¾è¿éå°çº¦ 120 æ¬¡ï¼æ¯æ¬¡ 5000 个ï¼ã |
| | | |
| | | > è¢ç 䏿çç é½èµ°è¯¥æ¹éæ¹æ³ï¼`generate()` ä¿çç»å
¶ä»å°éåºæ¯ã |
| | | |
| | | ### 5.4 åæ®µè½åº |
| | | |
| | | 1. **䏿¬¡æ§åé
è¢åºå·**ï¼ä»»å¡å¼å§æ¶è°ç¨ä¸æ¬¡ `allocateBagNoStart(totalCount, selectMaxBagNo())`ï¼åç»åæ®µç¨ `startNo + offset` æ¨ç®ãåæ¶äº§ççåºå·ç©ºæ´å
许åå¨ï¼åºå·åªè¦æ±å¯ä¸ãä¸åéï¼ï¼å æ¤ä¸è¦ææ®µéå¤åé
ã |
| | | 2. **æç**ï¼ä»
æ°æ®å
模å¼ï¼ï¼æ 1000 个/æ¹æå
¥ï¼ä¿ç `palletId` ä¸ `tempCode` 两个æ°ç»ä¾è¢ç 段å¼ç¨ï¼é¿å
å 积æ´è¡¨ DOã |
| | | 3. **è¢ç **ï¼æ `chunkSize`ï¼é»è®¤ 5000ï¼å段è½åºï¼æ¯æ®µè½åºååå䏿¬¡ `finished_count`ã |
| | | 4. **äºå¡è¾¹ç**ï¼æ¯ä¸ªå段çæå
¥å¨ç¬ç«ç `REQUIRES_NEW` äºå¡ä¸å®æï¼é¿å
å åä¸è¡åäºå¡ãåæ®µæ¹æ³å¿
é¡»ç±æ§è¡å¨è·¨ Bean è°ç¨ï¼ä¿è¯äºå¡ä»£ççæã |
| | | 5. æ¯æ®µå¼å§åæ£æ¥åæ¶æ è®°ä¸æé¿è¿è¡æ¶é´ï¼æ®µå
ä¸ååæ°æ®åºæ¥è¯¢ã |
| | | |
| | | ### 5.5 è¿åº¦ä¸èæµ |
| | | |
| | | - `finished_count` æ¯æ®µï¼5000 æ¡ï¼æ´æ°ä¸æ¬¡ï¼30 䏿¡çº¦ 60 æ¬¡æ´æ°ï¼å¼éå¯å¿½ç¥ã |
| | | - çæè¿ç¨ä»¥æ°æ®åºåå
¥ä¸ºä¸»ï¼ä¸å å CPU å¯éæä½ï¼å æ¤ä¸å CPU èæµï¼ä»
ä¿çæé¿è¿è¡æ¶é´ï¼é»è®¤ 30 åéï¼å
åºã |
| | | - æ¯æ®µåå
¥åæ£æ¥åæ¶æ è®°ã |
| | | |
| | | ### 5.6 åæ¶ |
| | | |
| | | 1. åæ¶æ¥å£æå
åä¸çåæ¶æ è®°ç½®ä½ï¼ä»»å¡è¿å
¥ãåæ¶ä¸ãã |
| | | 2. æ§è¡çº¿ç¨å¨å½ååæ®µç»æå忢ï¼éåï¼ |
| | | - ç©çå é¤è¯¥ `uuid` ä¸å·²çæçè¢ç ï¼`DELETE ... WHERE uuid = ? LIMIT 5000` 循ç¯ï¼ï¼ |
| | | - ç©çå 餿¬æ¬¡å·²å建çæçï¼æç表æ°å¢ `generate_uuid` è®°å½çæç»ï¼æ `DELETE ... WHERE generate_uuid = ? AND batch_id IS NULL AND code IS NULL LIMIT 5000` 循ç¯ã该æ¡ä»¶ç¡®ä¿åªå 仿ªå
¥æ¹æ¬¡çä¸´æ¶æçï¼ä¸ä¼è¯¯å ç¨æ·åç»å·²ç»å®çæçï¼ |
| | | - ä»»å¡ç½®ä¸ºã已忶ãã |
| | | 3. ä»»å¡ä¸å¨æ¬è¿ç¨è¿è¡ï¼è¿ç¨éå¯åçæ®çï¼æ¶ï¼åæ¶æ¥å£ç´æ¥æ¸
çæ®çæ°æ®å¹¶ç½®ä¸ºã已忶ãã |
| | | |
| | | > 说æï¼çæä¸åçæ°æ®å
ä¸å¯ç¨ï¼å¿
é¡»æ¸
çãè¿ééç¨ç©çå é¤èéé»è¾å é¤ï¼æ¯å 为è¿äºè¡ä»æªå¯¹å¤å±ç¤ºï¼é»è¾å é¤åªä¼çä¸å å䏿¡åå¾å¹¶æµªè´¹ç´¢å¼ç©ºé´ï¼ç¨æ·ä¸»å¨å é¤å·²çææ°æ®å
仿²¿ç¨åæé»è¾å é¤é»è¾ã |
| | | |
| | | ### 5.7 失败ä¸éå¯åæ¶ |
| | | |
| | | - æ§è¡å¼å¸¸ï¼æ¸
ç该 `uuid` çé¨åæ°æ®ï¼ä»»å¡ç½®ä¸ºã已失败ãï¼`error_msg` æªæå° 500 å符ã |
| | | - è¿ç¨éå¯ï¼å
åè¿è¡æä¸¢å¤±ãå¯å¨å䏿¯ 5 åé䏿¬¡çæ¸
ç任塿«æéç»æä»»å¡ï¼å¡ä¸å¨è¿è¡ææ³¨å表ä¸çï¼ä¸å¾ç½®ä¸ºã已失败ãå¹¶æ¸
çé¨åæ°æ®ã |
| | | - **ä¸å®ç°æç¹ç»è·**ï¼ç»è·éè¦ç²¾ç¡®å¤çåæ®µäºå¡è¾¹çãæçæ å°ä¸åæ¶ç©ºæ´ï¼å¤æåº¦é«ï¼èéæ°çæä¸æ¬¡çææ¬è¿ä½äºç»´æ¤ç»è·ç¶ææºãUI æç¡®æç¤ºâä»»å¡ä¸æï¼è¯·éæ°çæâã |
| | | |
| | | ### 5.8 å¹çä¸éå¤æäº¤ |
| | | |
| | | - åç«¯æ¯æ¬¡æå¼çæå¼¹çªçæä¸ä¸ª `requestId`ï¼å䏿¬¡æäº¤çéè¯å¤ç¨åä¸ä¸ªå¼ï¼å¦ç½ç»éè¯ï¼ã |
| | | - å端以 `requestId` å¯ä¸ç´¢å¼ä¿è¯åä¸è¯·æ±åªè½ä¸ä¸ªä»»å¡ï¼éå¤æäº¤ç´æ¥è¿åå·²æä»»å¡ã |
| | | - ä¸ä¼ `requestId` æ¶ä»å¯ç¨ï¼ä½å¤±å»ç½ç»çº§å¹çä¿æ¤ï¼å端å¿
é¡»ä¼ ã |
| | | |
| | | ### 5.9 é
置项 |
| | | |
| | | æ°å¢ `mes.bag-code.generate` åç¼é
ç½®ï¼ |
| | | |
| | | | é
ç½® | é»è®¤å¼ | 说æ | |
| | | |------|--------|------| |
| | | | sync-threshold | 20000 | ä¸è¶
è¿è¯¥æ°éåæ¥çæ | |
| | | | chunk-size | 5000 | æ¯æ®µè¢ç /æ¯æ¹åç æ°é | |
| | | | max-concurrent-tasks | 1 | åæ¶è¿è¡çåå°çæä»»å¡æ° | |
| | | | max-task-count | 1000000 | 忬¡çæè¢ç ä¸é | |
| | | | max-runtime-minutes | 30 | å任塿é¿è¿è¡æ¶é´ | |
| | | | poll-interval-seconds | 2 | ä»
ä½ä¸ºå端轮询建议å¼ä¸åï¼å¯éï¼ | |
| | | |
| | | ## å
ãå端èè°è¦ç¹ |
| | | |
| | | > æ¬èåªæè¿°è¡ä¸ºä¸å段ï¼ä¸æä¾å端代ç ã |
| | | |
| | | ### 6.1 æ¶åé¡µé¢ |
| | | |
| | | - èµç 管çå表页ï¼`mes/pro/bagCode`ï¼ |
| | | - ãçæäºç»´ç æ°æ®å
ãå¼¹çªï¼`generate-form.vue`ï¼ |
| | | - ãçæåå§æ°æ®ãå
¥å£ï¼å¦åå¨ç¬ç«å¼¹çªï¼åæ ·å¤çï¼ |
| | | |
| | | ### 6.2 æäº¤æµç¨ |
| | | |
| | | 1. æå¼çæå¼¹çªæ¶çæä¸ä¸ª `requestId`ï¼UUIDï¼ï¼å
³ééå¼åéæ°çæï¼å䏿¬¡æäº¤çèªå¨éè¯å¤ç¨åä¸ä¸ª `requestId`ã |
| | | 2. è°ç¨åçææ¥å£ï¼è¯·æ±ä½å¢å `requestId`ã |
| | | 3. ååº `async=false`ï¼ç»´æç°ææåæç¤ºä¸åè¡¨å·æ°è¡ä¸ºã |
| | | 4. ååº `async=true`ï¼å¼¹çªä¸ç«å³å
³éï¼å±ç¤ºè¿åº¦æ¡ï¼æ `poll-interval-seconds`ï¼å»ºè®® 2 ç§ï¼è½®è¯¢ä»»å¡æ¥è¯¢æ¥å£ï¼ç´å°ç¶æè¿å
¥ç»æã |
| | | - ç¶æ 30ï¼æç¤ºâçæå®æï¼å
± N æ¡è¢ç âï¼å
³éå¼¹çªå¹¶å·æ°å表ã |
| | | - ç¶æ 40ï¼æç¤ºâä»»å¡å·²åæ¶âã |
| | | - ç¶æ 50ï¼å±ç¤º `errorMsg`ï¼å
è®¸ç¨æ·éæ°æäº¤ï¼éæ°çæ `requestId`ï¼ã |
| | | 5. 轮询æé´æä¾âåæ¶çæâæé®ï¼è°ç¨åæ¶æ¥å£ï¼æé®ç¹å»åç½®ç°ï¼çå¾
ä»»å¡è¿å
¥ç»æã |
| | | 6. 页é¢å
³éæç»ä»¶å¸è½½æ¶åæ¢è½®è¯¢ï¼éæ°è¿å
¥é¡µé¢æ¶ï¼å¯éè¿ä»»å¡ç¼å·ç»§ç»æ¥è¯¢ï¼ä»»å¡ç¼å·å¯æå卿¬å°ç¶æï¼ã |
| | | |
| | | ### 6.3 åæ®µå±ç¤º |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | progress | çæå¼¹çª | è¿åº¦æ¡ç¾åæ¯ | |
| | | | finishedCount / totalCount | çæå¼¹çª | ææ¡âå·²çæ x / y æ¡â | |
| | | | statusName | çæå¼¹çª | ä»»å¡ç¶ææ ç¾ | |
| | | | palletCount | 宿æç¤º | æ°æ®å
æ¨¡å¼æç¤ºèªå¨çæçæçæ° | |
| | | | errorMsg | çæå¼¹çª | 失败åå | |
| | | |
| | | ### 6.4 注æäºé¡¹ |
| | | |
| | | - 弿¥æ¨¡å¼ååºä¸ `palletIds` 为 nullï¼å端ä¸è¦åç¨ `palletIds.length` 夿æçæ°ï¼æ¹ç¨ `palletCount`ã |
| | | - åä¸ç¨æ·åæ¶åªè½è¿è¡ä¸ä¸ªåå°çæä»»å¡ï¼è¢«æç»æ¶å端ä¼è¿åå½åä»»å¡è¿åº¦ï¼å端å¯ç´æ¥åæ¢ä¸ºæ¥è¯¢è¯¥ä»»å¡ã |
| | | - ä¸è¦ç¨âè¯·æ±æ¯å¦è¶
æ¶âæ¥å¤æä»»å¡å¤±è´¥ï¼æäº¤æ¥å£å¨è½¬å¼æ¥ååºå¿«éè¿åã |
| | | |
| | | ## ä¸ãæ§è½ä¼°ç®ä¸éªæ¶æ å |
| | | |
| | | ### 7.1 ä¼°ç® |
| | | |
| | | | ç¯è | æ¹é åï¼30 ä¸è¢ï¼ | æ¹é å | |
| | | |------|------------------|--------| |
| | | | Redis åç | 60 䏿¬¡ RTTï¼åé级 | 约 120 次 RTTï¼ç§çº§ | |
| | | | æ°æ®åºåå
¥ | 1 个è¶
大äºå¡ | 60 个 5000 è¡äºå¡ | |
| | | | HTTP ååº | çå¾
å
¨é¨å®æï¼å¿
ç¶è¶
æ¶ | æäº¤å 1 ç§å
è¿å taskId | |
| | | | ç¨æ·éè¯ | 产çé夿°æ®å
| å¹çé®å»é | |
| | | |
| | | ### 7.2 éªæ¶æ å |
| | | |
| | | 1. 30 ä¸è¢ç + å¯¹åºæçå¯å®æ´çæï¼è¿åº¦å¯ä» 0 æ¨è¿å° 100ã |
| | | 2. æäº¤æ¥å£å¨ 1 ç§å
è¿åï¼çæè¿ç¨ä¸åè¡¨å¯æ£å¸¸æ¥è¯¢å
¶ä»æ°æ®ã |
| | | 3. åæ¶åæ°æ®åºä¸è¯¥ `uuid` æ æ®çè¢ç 䏿çï¼ä»»å¡ç¶æä¸ºå·²åæ¶ã |
| | | 4. 模æè¿ç¨éå¯ï¼æ®çä»»å¡å¨ä¸æ¬¡æ¸
ç卿å
å为已失败ï¼é¨åæ°æ®è¢«æ¸
çã |
| | | 5. ç¸å `requestId` éå¤æäº¤åªäº§çä¸ä¸ªä»»å¡ãä¸ä¸ªçæç»ã |
| | | 6. 2 䏿¡ä»¥å
ä»ä¸ºåæ¥è¿åï¼ååºç»æä¸æ§çå
¼å®¹ï¼æ°å¢å段ä¸å½±åæ§å端解æï¼ã |
| | | 7. ç°æãç ´ææ¿æ¢ / ç»å®æ¹æ¬¡ / ç»å®æç / 导åºãåå½éè¿ã |
| | | |
| | | ## å
«ãé£é©ä¸åè |
| | | |
| | | | é£é© | åºå¯¹ | |
| | | |------|------| |
| | | | Redis `biz:code:used` éåæç»å¢é¿ï¼å åä¸çº§/æ¬¡ï¼ | ç°ç½å·²æè¯¥æºå¶ï¼éååªå 16 å符ç串ï¼å次å å䏿¡çº¦å å MBï¼åç»å¯è¯ä¼°æåç¼åçï¼æ¬æ¬¡ä¸å¨ | |
| | | | æ¸
ç 30 ä¸è¡é¨åæ°æ®èæ¶ | åæ®µ `DELETE ... LIMIT 5000`ï¼é¿å
é¿äºå¡ä¸ä¸»ä»å»¶è¿å°å³° | |
| | | | `bag_no` 为 INTï¼ç论ä¸é约 21 亿 | 忬¡ä¸é 100 ä¸ï¼æå½åéçº§å¯æ¯æå¤å¹´ï¼åç»å¦éé¿æä½¿ç¨åè¿ç§» BIGINT | |
| | | | 弿¥ä»»å¡æé´æå¡éå¯ | ä¸ç»è·ï¼æ 记失败并æ¸
çï¼æç¤ºéæ°çæ | |
| | | | åæ¥è·¯å¾ä»åç½å
³è¶
æ¶çº¦æ | éå¼å
忬¡æå¤ 2 䏿¡ï¼æ¹éåç åé¢è®¡ 1~3 ç§ï¼çè¶³ä½é | |
| | | |
| | | ## ä¹ã宿½æ¥éª¤ä¸ç¶æè¿½è¸ª |
| | | |
| | | | åºå· | å
容 | ç¶æ | |
| | | |------|------|------| |
| | | | 1 | æ¹æ¡ææ¡£ç¡®è®¤ | 已宿 | |
| | | | 2 | æ°å¢ä»»å¡è¡¨ãè¢ç `bag_no` ç´¢å¼ãæç `generate_uuid` è¿ç§»èæ¬ | 已宿 | |
| | | | 3 | æ°å¢ä»»å¡ DO / Mapper / ç¶ææä¸¾ / é
置项 | 已宿 | |
| | | | 4 | `MesCodeRedisDAO` å¢å æ¹éåç æ¹æ³ | 已宿 | |
| | | | 5 | æ½ååæ®µçææ§è¡å¨ï¼åæ¥/弿¥å
±ç¨ï¼ | 已宿 | |
| | | | 6 | æ°å¢ä»»å¡ Serviceï¼æäº¤ãæ¥è¯¢ãåæ¶ãåæ¶ï¼ä¸çº¿ç¨æ± é
ç½® | 已宿 | |
| | | | 7 | æ¹é ä¸¤ä¸ªçææ¥å£ç忥/弿¥åæµä¸ååºå段 | 已宿 | |
| | | | 8 | æ°å¢ä»»å¡æ¥è¯¢/åæ¶æ¥å£ä¸æé | 已宿 | |
| | | | 9 | å¯å¨ä¸å®æ¶æ¸
çä»»å¡ | 已宿 | |
| | | | 10 | å端èè°ï¼å¼¹çªè¿åº¦ã轮询ãåæ¶ï¼ | 已宿 | |
| | | | 11 | ç¼è¯æ£æ¥ä¸å¤§æ¹é宿µ | å·²å®æï¼æ¬å° mom-thzyï¼30 䏿¡çº¦ 76 ç§çæå®æ¯ï¼æäº¤ååº 0.18 ç§ï¼åæ¶æ¸
çæ æ®çï¼å¹çä¸å¹¶åé¸é¨åéªè¯éè¿ï¼ | |
| | | |
| | | ### è¿ç§»èæ¬ |
| | | |
| | | | èæ¬ | 说æ | |
| | | |------|------| |
| | | | `sql/mysql/mes_pro_bag_code_generate_task.sql` | ä»»å¡è¡¨ | |
| | | | `sql/mysql/mes_pro_bag_code_bag_no_index.sql` | è¢ç `bag_no` ç´¢å¼ | |
| | | | `sql/mysql/mes_pro_pallet_generate_uuid.sql` | æç `generate_uuid` åä¸ç´¢å¼ | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- èµç 管ç - è¢ç 表 bag_no ç´¢å¼ |
| | | -- selectMaxBagNo() ä¾èµ MAX(bag_no)ï¼å¤§æ¹éçæåæ ç´¢å¼ä¼å
¨è¡¨æ«æ |
| | | -- MySQL 8.4 éç¨ |
| | | -- ============================================= |
| | | |
| | | SET NAMES utf8mb4; |
| | | |
| | | SET @exists = (SELECT COUNT(*) FROM information_schema.statistics |
| | | WHERE table_schema = DATABASE() |
| | | AND table_name = 'mes_pro_bag_code' |
| | | AND index_name = 'idx_bag_no'); |
| | | SET @sql = IF(@exists = 0, |
| | | 'ALTER TABLE mes_pro_bag_code ADD INDEX idx_bag_no (bag_no)', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @sql; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- èµç 管ç - 大æ¹éçæä»»å¡è¡¨ |
| | | -- ç¨äºæ¿æ¥å å䏿¡è¢ç çåå°çæãè¿åº¦å±ç¤ºãåæ¶ä¸å¤±è´¥åæ¶ |
| | | -- MySQL 8.4 éç¨ |
| | | -- ============================================= |
| | | |
| | | SET NAMES utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS `mes_pro_bag_code_generate_task` ( |
| | | `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `request_id` VARCHAR(64) DEFAULT NULL COMMENT 'å¹çé®ï¼åç«¯æ¯æ¬¡æäº¤çæä¸ä¸ª UUIDï¼éè¯å¤ç¨ï¼', |
| | | `biz_type` VARCHAR(16) NOT NULL COMMENT 'ä¸å¡ç±»åï¼PACKAGE æ°æ®å
/ RAW åå§æ°æ®ï¼', |
| | | `uuid` VARCHAR(32) NOT NULL COMMENT 'çæç» UUIDï¼æäº¤æ¶çæï¼å¤±è´¥/åæ¶åç¨äºæ¸
çï¼', |
| | | `item_id` BIGINT NOT NULL COMMENT 'åç§ï¼ç©æï¼ç¼å·ï¼å
³è mdm_item.id', |
| | | `item_code` VARCHAR(64) DEFAULT NULL COMMENT 'åç§ç¼ç ï¼å¿«ç
§ï¼', |
| | | `item_name` VARCHAR(255) DEFAULT NULL COMMENT 'åç§åç§°ï¼å¿«ç
§ï¼', |
| | | `unit_type` VARCHAR(32) DEFAULT NULL COMMENT '计éåä½ç±»åï¼å¨ / 48è¢ / 100è¢ï¼', |
| | | `per_pallet` INT NOT NULL DEFAULT 0 COMMENT 'æ¯æçè¢æ°ï¼å¨ç±»å为 0ï¼', |
| | | `pallet_count` INT NOT NULL DEFAULT 0 COMMENT 'æçæ°ï¼åå§æ°æ®ä¸º 0ï¼', |
| | | `total_count` INT NOT NULL COMMENT 'è¢ç æ»æ°', |
| | | `finished_count` INT NOT NULL DEFAULT 0 COMMENT 'å·²çæè¢ç æ°', |
| | | `status` TINYINT NOT NULL DEFAULT 10 COMMENT 'ç¶æï¼10 æéä¸ 20 çæä¸ 25 åæ¶ä¸ 30 已宿 40 已忶 50 已失败ï¼', |
| | | `remark` VARCHAR(255) DEFAULT NULL COMMENT '夿³¨ï¼åå
¥çæçè¢ç ï¼', |
| | | `error_msg` VARCHAR(500) DEFAULT NULL COMMENT '失败åå ', |
| | | `finish_time` DATETIME DEFAULT NULL COMMENT '宿/ç»æ¢æ¶é´', |
| | | `creator` VARCHAR(64) DEFAULT '' COMMENT 'å建è
', |
| | | `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` VARCHAR(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_request_id` (`request_id`), |
| | | KEY `idx_status` (`status`), |
| | | KEY `idx_uuid` (`uuid`) |
| | | ) ENGINE = InnoDB COMMENT = 'MES è¢ç æ¹éçæä»»å¡'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- èµç 管ç - æç表æ°å¢çæç»æ è®° |
| | | -- æ¹éçæè¢ç æ°æ®å
æ¶åå
¥ï¼ç¨äºä»»å¡å¤±è´¥/忶忏
çæªç»å®çä¸´æ¶æç |
| | | -- MySQL 8.4 éç¨ï¼ADD COLUMN 䏿¯æ IF NOT EXISTSï¼å
æ¥ information_schemaï¼ |
| | | -- ============================================= |
| | | |
| | | SET NAMES utf8mb4; |
| | | |
| | | SET @exists = (SELECT COUNT(*) FROM information_schema.columns |
| | | WHERE table_schema = DATABASE() |
| | | AND table_name = 'mes_pro_pallet' |
| | | AND column_name = 'generate_uuid'); |
| | | SET @sql = IF(@exists = 0, |
| | | 'ALTER TABLE mes_pro_pallet ADD COLUMN generate_uuid VARCHAR(32) DEFAULT NULL COMMENT ''çæç» UUIDï¼æ¹éçææ¶åå
¥ï¼ç¨äºå¤±è´¥/åæ¶æ¸
çï¼'' AFTER temp_code', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @sql; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | | |
| | | SET @indexExists = (SELECT COUNT(*) FROM information_schema.statistics |
| | | WHERE table_schema = DATABASE() |
| | | AND table_name = 'mes_pro_pallet' |
| | | AND index_name = 'idx_generate_uuid'); |
| | | SET @sql = IF(@indexExists = 0, |
| | | 'ALTER TABLE mes_pro_pallet ADD INDEX idx_generate_uuid (generate_uuid)', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @sql; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateTaskRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeRawGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO; |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeTraceRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils; |
| | | import cn.iocoder.yudao.module.mes.framework.qrexport.MesProQrExportProperties; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeGenerateTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.qrexport.MesProQrLabelAssembler; |
| | |
| | | @Resource |
| | | private MesProBagCodeService bagCodeService; |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskService generateTaskService; |
| | | @Resource |
| | | private MesProBatchService batchService; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | |
| | | public CommonResult<MesProBagCodeGenerateRespVO> generateRawBagCodes( |
| | | @Valid @RequestBody MesProBagCodeRawGenerateReqVO rawGenerateReqVO) { |
| | | return success(bagCodeService.generateRawBagCodes(rawGenerateReqVO)); |
| | | } |
| | | |
| | | @GetMapping("/generate-task/get") |
| | | @Operation(summary = "è·å¾è¢ç çæä»»å¡ï¼å¤§æ¹éçæçè¿åº¦è½®è¯¢ï¼") |
| | | @Parameter(name = "id", description = "ä»»å¡ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:query')") |
| | | public CommonResult<MesProBagCodeGenerateTaskRespVO> getGenerateTask(@RequestParam("id") Long id) { |
| | | return success(buildGenerateTaskRespVO(generateTaskService.getTask(id))); |
| | | } |
| | | |
| | | @PostMapping("/generate-task/cancel") |
| | | @Operation(summary = "åæ¶è¢ç çæä»»å¡ï¼åæ¢çæå¹¶æ¸
çåæåï¼") |
| | | @Parameter(name = "id", description = "ä»»å¡ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:create')") |
| | | public CommonResult<Boolean> cancelGenerateTask(@RequestParam("id") Long id) { |
| | | generateTaskService.cancel(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/replace") |
| | |
| | | labelAssembler.buildBagBusinessLabels(list)); |
| | | } |
| | | |
| | | private MesProBagCodeGenerateTaskRespVO buildGenerateTaskRespVO(MesProBagCodeGenerateTaskDO task) { |
| | | MesProBagCodeGenerateTaskRespVO respVO = BeanUtils.toBean(task, MesProBagCodeGenerateTaskRespVO.class); |
| | | for (MesProBagCodeGenerateTaskStatusEnum value : MesProBagCodeGenerateTaskStatusEnum.values()) { |
| | | if (value.getStatus().equals(task.getStatus())) { |
| | | respVO.setStatusName(value.getName()); |
| | | break; |
| | | } |
| | | } |
| | | Integer total = task.getTotalCount(); |
| | | if (total == null || total <= 0) { |
| | | respVO.setProgress(0); |
| | | } else { |
| | | int finished = task.getFinishedCount() == null ? 0 : task.getFinishedCount(); |
| | | respVO.setProgress((int) Math.min(100L, (long) finished * 100 / total)); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | private List<MesProBagCodeRespVO> buildBagCodeRespVOList(List<MesProBagCodeDO> list) { |
| | | if (CollUtil.isEmpty(list)) { |
| | | return Collections.emptyList(); |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Positive; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - MES è¢ç ï¼äºç»´ç æ°æ®å
ï¼çæ Request VO") |
| | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å¹çé®ï¼åç«¯æ¯æ¬¡æäº¤çæä¸ä¸ª UUIDï¼ç½ç»éè¯å¤ç¨åä¸ä¸ªå¼", example = "a1b2c3") |
| | | @Size(max = 64, message = "å¹çé®é¿åº¦ä¸è½è¶
è¿ 64 个å符") |
| | | private String requestId; |
| | | |
| | | } |
| | |
| | | @Schema(description = "æ¬æ¬¡çæçè¢ç æ»æ°", example = "240") |
| | | private Integer totalCount; |
| | | |
| | | @Schema(description = "èªå¨çæçæçç¼å·å表ï¼ç±»å为 å¨ æ¶ä¸ºç©ºï¼") |
| | | @Schema(description = "èªå¨çæçæçç¼å·å表ï¼ä»
忥宿æ¶è¿åï¼ç±»å为 å¨ æ¶ä¸ºç©ºï¼") |
| | | private List<Long> palletIds; |
| | | |
| | | @Schema(description = "æ¯å¦å·²è½¬ä¸ºåå°ä»»å¡", example = "true") |
| | | private Boolean async; |
| | | |
| | | @Schema(description = "åå°ä»»å¡ç¼å·ï¼å¼æ¥æ¨¡å¼è¿åï¼", example = "1024") |
| | | private Long taskId; |
| | | |
| | | @Schema(description = "æçæ»æ°ï¼åå§æ°æ®ä¸º 0ï¼", example = "5") |
| | | private Integer palletCount; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - MES è¢ç çæä»»å¡ Response VO") |
| | | @Data |
| | | public class MesProBagCodeGenerateTaskRespVO { |
| | | |
| | | @Schema(description = "ä»»å¡ç¼å·", example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ä¸å¡ç±»åï¼PACKAGEï¼æ°æ®å
ï¼/ RAWï¼åå§æ°æ®ï¼", example = "PACKAGE") |
| | | private String bizType; |
| | | |
| | | @Schema(description = "ä»»å¡ç¶æ", example = "20") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "ä»»å¡ç¶æåç§°", example = "çæä¸") |
| | | private String statusName; |
| | | |
| | | @Schema(description = "è¿åº¦ç¾åæ¯ï¼0~100ï¼", example = "35") |
| | | private Integer progress; |
| | | |
| | | @Schema(description = "çæç» UUID") |
| | | private String uuid; |
| | | |
| | | @Schema(description = "åç§ç¼å·", example = "1") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "åç§ç¼ç ", example = "ITEM001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "åç§åç§°", example = "ç§å") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "计éåä½ç±»å", example = "48è¢") |
| | | private String unitType; |
| | | |
| | | @Schema(description = "æ¯æçè¢æ°ï¼å¨ç±»å为 0ï¼", example = "48") |
| | | private Integer perPallet; |
| | | |
| | | @Schema(description = "æçæ°ï¼åå§æ°æ®ä¸º 0ï¼", example = "5") |
| | | private Integer palletCount; |
| | | |
| | | @Schema(description = "è¢ç æ»æ°", example = "240") |
| | | private Integer totalCount; |
| | | |
| | | @Schema(description = "å·²çæè¢ç æ°", example = "120") |
| | | private Integer finishedCount; |
| | | |
| | | @Schema(description = "失败åå ") |
| | | private String errorMsg; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "宿/ç»æ¢æ¶é´") |
| | | private LocalDateTime finishTime; |
| | | |
| | | } |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Positive; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - MES åå§è¢ç çæ Request VOï¼ä»
åç§+æ°éï¼ä¸å
³è计éåä½ç±»åãä¸çææçï¼") |
| | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å¹çé®ï¼åç«¯æ¯æ¬¡æäº¤çæä¸ä¸ª UUIDï¼ç½ç»éè¯å¤ç¨åä¸ä¸ªå¼", example = "a1b2c3") |
| | | @Size(max = 64, message = "å¹çé®é¿åº¦ä¸è½è¶
è¿ 64 个å符") |
| | | private String requestId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * MES è¢ç æ¹éçæä»»å¡ DO |
| | | * <p> |
| | | * å å䏿¡è¢ç ä¸å¨è¯·æ±çº¿ç¨å
å®æï¼æäº¤åè½ä»»å¡ï¼ç±åå°çº¿ç¨å段è½åºï¼ |
| | | * åç«¯æ® {@link #finishedCount}/{@link #totalCount} å±ç¤ºè¿åº¦ï¼å¹¶æ¯æåæ¶ã |
| | | */ |
| | | @TableName("mes_pro_bag_code_generate_task") |
| | | @KeySequence("mes_pro_bag_code_generate_task_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class MesProBagCodeGenerateTaskDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * å¹çé®ï¼åç«¯æ¯æ¬¡æäº¤çæä¸ä¸ª UUIDï¼ç½ç»éè¯å¤ç¨åä¸ä¸ªå¼ |
| | | */ |
| | | private String requestId; |
| | | |
| | | /** |
| | | * ä¸å¡ç±»åï¼è§ {@link cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateBizTypeEnum} |
| | | */ |
| | | private String bizType; |
| | | |
| | | /** |
| | | * çæç» UUIDï¼æäº¤æ¶çæï¼å¤±è´¥/åæ¶åæ®æ¤æ¸
çé¨åæ°æ®ï¼ |
| | | */ |
| | | private String uuid; |
| | | |
| | | /** |
| | | * åç§ç¼å· |
| | | */ |
| | | private Long itemId; |
| | | |
| | | /** |
| | | * åç§ç¼ç ï¼å¿«ç
§ï¼ |
| | | */ |
| | | private String itemCode; |
| | | |
| | | /** |
| | | * åç§åç§°ï¼å¿«ç
§ï¼ |
| | | */ |
| | | private String itemName; |
| | | |
| | | /** |
| | | * 计éåä½ç±»åï¼å¨ / 48è¢ / 100è¢ï¼ |
| | | */ |
| | | private String unitType; |
| | | |
| | | /** |
| | | * æ¯æçè¢æ°ï¼å¨ç±»å为 0ï¼ |
| | | */ |
| | | private Integer perPallet; |
| | | |
| | | /** |
| | | * æçæ°ï¼åå§æ°æ®ä¸º 0ï¼ |
| | | */ |
| | | private Integer palletCount; |
| | | |
| | | /** |
| | | * è¢ç æ»æ° |
| | | */ |
| | | private Integer totalCount; |
| | | |
| | | /** |
| | | * å·²çæè¢ç æ° |
| | | */ |
| | | private Integer finishedCount; |
| | | |
| | | /** |
| | | * ç¶æï¼è§ {@link cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateTaskStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | | /** |
| | | * 夿³¨ï¼åå
¥çæçè¢ç ï¼ |
| | | */ |
| | | private String remark; |
| | | |
| | | /** |
| | | * 失败åå |
| | | */ |
| | | private String errorMsg; |
| | | |
| | | /** |
| | | * 宿/ç»æ¢æ¶é´ |
| | | */ |
| | | private LocalDateTime finishTime; |
| | | |
| | | } |
| | |
| | | * ä¸´æ¶æçç ï¼ç å¼ï¼PL-8ä½å
¨å±åºå·ï¼é¢çæäºç»´ç é¶æ®µä½¿ç¨ï¼ç»å®æ¹æ¬¡åä»å¯æ«ç è¿½æº¯ï¼ |
| | | */ |
| | | private String tempCode; |
| | | /** |
| | | * çæç» UUIDï¼æ¹éçæè¢ç æ°æ®å
æ¶åå
¥ï¼ç¨äºä»»å¡å¤±è´¥/忶忏
çæªç»å®çä¸´æ¶æçï¼ |
| | | */ |
| | | private String generateUuid; |
| | | /** æçåç§ç¼å·ï¼çææé¦æ¬¡è£
è¢æ¶å¸¦å
¥ã */ |
| | | private Long itemId; |
| | | /** |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES è¢ç æ¹éçæä»»å¡ Mapper |
| | | */ |
| | | @Mapper |
| | | public interface MesProBagCodeGenerateTaskMapper extends BaseMapperX<MesProBagCodeGenerateTaskDO> { |
| | | |
| | | default MesProBagCodeGenerateTaskDO selectByRequestId(String requestId) { |
| | | return selectOne(MesProBagCodeGenerateTaskDO::getRequestId, requestId); |
| | | } |
| | | |
| | | /** |
| | | * ä»
æ´æ°è¿åº¦ï¼é¿å
ææ´è¡å段é½ååï¼çæè¿ç¨ä¸é«é¢è°ç¨ï¼ |
| | | */ |
| | | default int updateProgress(Long id, Integer finishedCount) { |
| | | return update(null, new LambdaUpdateWrapper<MesProBagCodeGenerateTaskDO>() |
| | | .eq(MesProBagCodeGenerateTaskDO::getId, id) |
| | | .set(MesProBagCodeGenerateTaskDO::getFinishedCount, finishedCount)); |
| | | } |
| | | |
| | | /** |
| | | * æ´æ°ä»»å¡ç»æï¼null åæ®µä¸è¦ç |
| | | */ |
| | | default int updateStatus(Long id, Integer status, String errorMsg, LocalDateTime finishTime) { |
| | | return update(null, new LambdaUpdateWrapper<MesProBagCodeGenerateTaskDO>() |
| | | .eq(MesProBagCodeGenerateTaskDO::getId, id) |
| | | .set(MesProBagCodeGenerateTaskDO::getStatus, status) |
| | | .set(errorMsg != null, MesProBagCodeGenerateTaskDO::getErrorMsg, errorMsg) |
| | | .set(finishTime != null, MesProBagCodeGenerateTaskDO::getFinishTime, finishTime)); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢ææéç»æä»»å¡ï¼ç¨äºåæ¶è¿ç¨éå¯åæ®ççè®°å½ |
| | | * <p> |
| | | * æ¯å¦ä»å¨æ§è¡ç±è¿è¡ææ³¨å表å¤å®ï¼è¡¨éçéç»æè®°å½è¥ä¸å¨æ³¨å表ä¸ï¼ |
| | | * å°±ä¸å®æ²¡ææ§è¡çº¿ç¨å¨æ¨è¿å®ã |
| | | */ |
| | | default List<MesProBagCodeGenerateTaskDO> selectUnfinishedList(Collection<Integer> statuses) { |
| | | return selectList(new LambdaQueryWrapperX<MesProBagCodeGenerateTaskDO>() |
| | | .in(MesProBagCodeGenerateTaskDO::getStatus, statuses) |
| | | .orderByAsc(MesProBagCodeGenerateTaskDO::getId)); |
| | | } |
| | | |
| | | } |
| | |
| | | "WHERE deleted = 0 AND status <> 30 AND batch_id = #{batchId} AND item_id IS NOT NULL") |
| | | List<Long> selectDistinctItemIdsByBatchId(@Param("batchId") Long batchId); |
| | | |
| | | /** |
| | | * ç©çå é¤çæç»ä¸ç䏿¹è¢ç ï¼ä»
ç¨äºçæå¤±è´¥/åæ¶åçåæåæ¸
çï¼ |
| | | * <p> |
| | | * åæå仿ªå¯¹å¤å±ç¤ºï¼é»è¾å é¤åªä¼çä¸å å䏿¡åå¾å¹¶å ç¨ç´¢å¼ç©ºé´ï¼å æ¤è¿éç´æ¥ç©çå é¤ã |
| | | * |
| | | * @param uuid çæç» UUID |
| | | * @param limit 忬¡æå¤å é¤è¡æ° |
| | | * @return å®é
å é¤è¡æ° |
| | | */ |
| | | @org.apache.ibatis.annotations.Delete("DELETE FROM mes_pro_bag_code WHERE uuid = #{uuid} LIMIT #{limit}") |
| | | int deletePhysicallyByUuidChunk(@Param("uuid") String uuid, @Param("limit") int limit); |
| | | |
| | | default List<MesProBagCodeDO> selectListByIdsForUpdate(Collection<Long> ids) { |
| | | return selectList(new LambdaQueryWrapperX<MesProBagCodeDO>() |
| | | .in(MesProBagCodeDO::getId, ids) |
| | |
| | | @Select("SELECT IFNULL(MAX(pallet_no), 0) FROM mes_pro_pallet WHERE batch_id = #{batchId}") |
| | | Integer selectMaxPalletNo(@Param("batchId") Long batchId); |
| | | |
| | | /** |
| | | * ç©çå é¤æä¸ªçæç»ä¸å°æªç»å®æ¹æ¬¡çä¸´æ¶æçï¼ä»
ç¨äºçæå¤±è´¥/åæ¶åçåæåæ¸
çï¼ |
| | | * <p> |
| | | * å·²ç»å®æ¹æ¬¡çæçå¯è½å·²è¢«åç»ä¸å¡ä½¿ç¨ï¼å³ä½¿å¸¦æçæç»æ è®°ä¹ä¸è½å ã |
| | | * |
| | | * @param generateUuid çæç» UUID |
| | | * @param limit 忬¡æå¤å é¤è¡æ° |
| | | * @return å®é
å é¤è¡æ° |
| | | */ |
| | | @org.apache.ibatis.annotations.Delete("DELETE FROM mes_pro_pallet WHERE generate_uuid = #{generateUuid} " |
| | | + "AND batch_id IS NULL AND code IS NULL LIMIT #{limit}") |
| | | int deletePhysicallyByGenerateUuidChunk(@Param("generateUuid") String generateUuid, @Param("limit") int limit); |
| | | |
| | | @org.apache.ibatis.annotations.Update("UPDATE mes_pro_pallet SET sales_id = #{salesId}, outbound_time = #{outboundTime}, update_time = NOW() " |
| | | + "WHERE id = #{id} AND deleted = 0") |
| | | void markOutbound(@Param("id") Long id, @Param("salesId") Long salesId, |
| | |
| | | import org.springframework.data.redis.core.script.RedisScript; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | |
| | | * è¢åºå·èªå¢ KEYï¼å
¨å±è¢åºå·ï¼ä¿è¯å䏿¹/è·¨æ¹çææ¶ä¸éå¤ï¼ |
| | | */ |
| | | private static final String BAG_NO_SEQ_KEY = "mes:bag_no:seq"; |
| | | |
| | | /** |
| | | * 忬¡æ¹éåç çæå¤§ä¸ªæ° |
| | | * <p> |
| | | * å Redis åæ¡å½ä»¤åæ°è§æ¨¡ä¸ååºä½å¤§å°çº¦æï¼è¿å¤§åèå¢å 忬¡é»å¡æ¶é´ã |
| | | */ |
| | | public static final int MAX_BATCH_SIZE = 5000; |
| | | |
| | | /** |
| | | * æ¹éåå
¥å¹¶è¿åå®é
çæçç¼ç ã |
| | | * <p> |
| | | * é个 SADD å¨åä¸èæ¬å
宿ï¼ä¸è°ç¨æ¹éæ¡ SADD ç¸æ¯ï¼ç½ç»å¾è¿ä» N 次é为 1 æ¬¡ï¼ |
| | | * åæ¶å ä¸ºèæ¬ååæ§è¡ï¼å¹¶åè°ç¨ä¹é´ä¹ä¸ä¼äº§çéå¤ç¼ç ã |
| | | */ |
| | | private static final RedisScript<List> ADD_UNIQUE_CODES_SCRIPT = new DefaultRedisScript<>( |
| | | "local res = {} " |
| | | + "for i = 1, #ARGV do " |
| | | + " if redis.call('SADD', KEYS[1], ARGV[i]) == 1 then " |
| | | + " res[#res + 1] = ARGV[i] " |
| | | + " end " |
| | | + "end " |
| | | + "return res", |
| | | List.class); |
| | | |
| | | /** |
| | | * åååé
è¢åºå·åºé´ç Lua èæ¬ã |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ¹éçæ count 个å¯ä¸ç 16 ä½çº¯æ°åä¸å¡ç¼ç ï¼å
¨ç³»ç»å¯ä¸ï¼ |
| | | * <p> |
| | | * 大æ¹éçææ¶ä¸è¦å¾ªç¯è°ç¨ {@link #generate()}ï¼é£ä¼äº§ç N 次 Redis å¾è¿ï¼ |
| | | * å å䏿¡å³å¯ææ¥å£æå°åé级ãè¿éæ¯æ¹æå¤ {@link #MAX_BATCH_SIZE} 个åéç |
| | | * 䏿¬¡èæ¬åå
¥ï¼ç¢°æï¼æ¦çæä½ï¼ç缺é¢åè¡¥ä¸æ¹ã |
| | | * |
| | | * @param count éè¦çç¼ç ä¸ªæ° |
| | | * @return å¯ä¸ç¼ç å表ï¼é¡ºåºæ ä¿è¯ä½æ°éçäº count |
| | | */ |
| | | public List<String> generateBatch(int count) { |
| | | if (count <= 0) { |
| | | return List.of(); |
| | | } |
| | | List<String> result = new ArrayList<>(Math.min(count, 1024)); |
| | | // 16 ä½éæºç 空é´ä¸º 10^16ï¼æ£å¸¸æ
åµä¸ä¸è½®å³å¯å
¨é¨åå
¥ï¼éå¶è½®æ¬¡åªç¨äºå
åºå¼å¸¸ |
| | | for (int round = 0; round < 100 && result.size() < count; round++) { |
| | | int batchSize = Math.min(count - result.size(), MAX_BATCH_SIZE); |
| | | List<String> candidates = new ArrayList<>(batchSize); |
| | | for (int i = 0; i < batchSize; i++) { |
| | | candidates.add(BizNoUtils.randomCode()); |
| | | } |
| | | List<?> added = stringRedisTemplate.execute(ADD_UNIQUE_CODES_SCRIPT, |
| | | List.of(BizNoUtils.USED_SET_KEY), candidates.toArray(new String[0])); |
| | | if (added != null) { |
| | | added.forEach(code -> result.add(String.valueOf(code))); |
| | | } |
| | | } |
| | | if (result.size() < count) { |
| | | throw new IllegalStateException("æ¹éçæå¯ä¸ä¸å¡ç¼ç 失败ï¼è¯·éè¯"); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * åååé
䏿®µè¿ç»çè¢åºå·ï¼è¿ååºé´èµ·å§å¼ã |
| | | * <p> |
| | | * è¢åºå·åå
ç±ãæ¥åºå
æå¤§å¼å +1ãå¾å°ï¼å¹¶åçææ¶ä¸¤ä¸ªè¯·æ±ä¼è¯»å°åä¸ä¸ªæå¤§å¼ï¼ |
| | |
| | | ErrorCode PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED = new ErrorCode(1_040_752_012, "åç§ä¸»è®¡éå使ªé
置类åï¼ä»
æ¯æ 48è¢/100è¢/å¨"); |
| | | ErrorCode PRO_BAG_CODE_PALLET_COUNT_REQUIRED = new ErrorCode(1_040_752_013, "该åç§ææççæè¢ç ï¼æçæ°å¿
é¡»å¤§äº 0"); |
| | | ErrorCode PRO_BAG_CODE_BIND_BATCH_ITEM_MISMATCH = new ErrorCode(1_040_752_014, "å½å
¥æ¹æ¬¡çè¢ç å¿
é¡»å±äºåä¸åç§"); |
| | | ErrorCode PRO_BAG_CODE_GENERATE_COUNT_TOO_MANY = new ErrorCode(1_040_752_015, "忬¡çæè¢ç æ°éä¸è½è¶
è¿ {} æ¡"); |
| | | ErrorCode PRO_BAG_CODE_GENERATE_TASK_BUSY = new ErrorCode(1_040_752_016, "å·²æçæä»»å¡è¿è¡ä¸ï¼{}%ï¼ï¼è¯·çå¾
宿æå
åæ¶"); |
| | | ErrorCode PRO_BAG_CODE_GENERATE_TASK_NOT_EXISTS = new ErrorCode(1_040_752_017, "çæä»»å¡ä¸åå¨"); |
| | | ErrorCode PRO_BAG_CODE_GENERATE_TASK_STATUS_INVALID = new ErrorCode(1_040_752_018, "å½åçæä»»å¡ç¶æä¸å
许该æä½"); |
| | | |
| | | // ========== MES ç§åæ ç¾å½æ åè¦ç´ ï¼1-040-750-020ï¼ ========== |
| | | ErrorCode PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED = new ErrorCode(1_040_750_020, "æªé
置追溯ç½åï¼æ æ³çæäºç»´ç æ ç¾"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.pro; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * MES è¢ç æ¹éçæä¸å¡ç±»åæä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesProBagCodeGenerateBizTypeEnum implements ArrayValuable<String> { |
| | | |
| | | /** |
| | | * æ°æ®å
ï¼æè®¡éåä½ç±»åæ¢ç®ï¼N è¢ç±»ååæ¶çææç |
| | | */ |
| | | PACKAGE("PACKAGE", "æ°æ®å
"), |
| | | /** |
| | | * åå§æ°æ®ï¼ä»
ææ°éçæï¼ä¸çææç |
| | | */ |
| | | RAW("RAW", "åå§æ°æ®"); |
| | | |
| | | public static final String[] ARRAYS = Arrays.stream(values()) |
| | | .map(MesProBagCodeGenerateBizTypeEnum::getType).toArray(String[]::new); |
| | | |
| | | /** |
| | | * ç±»åæ è¯ |
| | | */ |
| | | private final String type; |
| | | /** |
| | | * ç±»ååç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public String[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.enums.pro; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * MES è¢ç æ¹éçæä»»å¡ç¶ææä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum MesProBagCodeGenerateTaskStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | /** |
| | | * å·²å建å¾
æ§è¡ï¼åå¹¶åé¸é¨éå¶æ¶çæåçè¯¥ç¶æï¼ |
| | | */ |
| | | PENDING(10, "æéä¸"), |
| | | /** |
| | | * çæä¸ |
| | | */ |
| | | RUNNING(20, "çæä¸"), |
| | | /** |
| | | * å·²æ¶å°åæ¶è¯·æ±ï¼æ£å¨åæ¢å¹¶æ¸
çå·²çææ°æ® |
| | | */ |
| | | CANCELING(25, "åæ¶ä¸"), |
| | | /** |
| | | * å
¨é¨æ°æ®çæå®æ |
| | | */ |
| | | FINISHED(30, "已宿"), |
| | | /** |
| | | * ç¨æ·åæ¶ï¼é¨åæ°æ®å·²æ¸
ç |
| | | */ |
| | | CANCELED(40, "已忶"), |
| | | /** |
| | | * å¼å¸¸ç»æ¢ï¼é¨åæ°æ®å·²æ¸
ç |
| | | */ |
| | | FAILED(50, "已失败"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(MesProBagCodeGenerateTaskStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * ç¶æ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * ç¶æåç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | /** |
| | | * æ¯å¦ä¸ºç»æï¼ä¸åååï¼ |
| | | */ |
| | | public boolean isFinal() { |
| | | return this == FINISHED || this == CANCELED || this == FAILED; |
| | | } |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.framework.bagcode; |
| | | |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| | | |
| | | import java.util.concurrent.ThreadPoolExecutor; |
| | | |
| | | /** |
| | | * è¢ç åå°çæçº¿ç¨æ± |
| | | * <p> |
| | | * åç¬å»ºæ± è䏿¯å¤ç¨å
Œ
±å¼æ¥æ± ï¼å¤§æ¹éè½åºæ¯é¿æ¶é´ãæ°æ®åºå¯éçä»»å¡ï¼ |
| | | * æ··è¿å
Œ
±æ± 伿å
¶ä»å¼æ¥ä¸å¡ä¸èµ·å µä½ã |
| | | */ |
| | | @Configuration(proxyBeanMethods = false) |
| | | public class MesProBagCodeGenerateExecutorConfig { |
| | | |
| | | private static final String THREAD_NAME_PREFIX = "mes-bag-code-generate-"; |
| | | |
| | | @Resource |
| | | private MesProBagCodeGenerateProperties properties; |
| | | |
| | | @Bean(name = "mesBagCodeGenerateExecutor") |
| | | public ThreadPoolTaskExecutor mesBagCodeGenerateExecutor() { |
| | | int concurrency = Math.max(1, properties.getMaxConcurrentTasks()); |
| | | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
| | | executor.setCorePoolSize(concurrency); |
| | | executor.setMaxPoolSize(concurrency); |
| | | executor.setQueueCapacity(concurrency); |
| | | executor.setThreadNamePrefix(THREAD_NAME_PREFIX); |
| | | // éåæ»¡å³æç»ï¼ç±è°ç¨æ¹æä»»å¡ç½®ä¸ºå¤±è´¥ï¼é¿å
任塿 声å°å å¨éåé |
| | | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); |
| | | executor.initialize(); |
| | | return executor; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.framework.bagcode; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * MES è¢ç æ¹éçæé
置项 |
| | | * <p> |
| | | * å°æ¹éä»å¨è¯·æ±çº¿ç¨å
忥宿ï¼ä¿è¯ç§åï¼å¤§æ¹é转åå°ä»»å¡ï¼å段çæãå¯åæ¶ã |
| | | */ |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties(prefix = "mes.bag-code.generate") |
| | | public class MesProBagCodeGenerateProperties { |
| | | |
| | | /** |
| | | * 忥/弿¥çåç线ï¼è¢ç æ°ï¼ |
| | | * <p> |
| | | * ä¸è¶
è¿è¯¥æ°éå¨è¯·æ±çº¿ç¨å
宿ï¼è¶
è¿åè½ä»»å¡è½¬åå°çæã |
| | | */ |
| | | private int syncThreshold = 20000; |
| | | |
| | | /** |
| | | * æ¯æ®µè½åºçè¢ç æ°éï¼åæ¶ä¹æ¯æ¯æ¹åç æ°éï¼ |
| | | */ |
| | | private int chunkSize = 5000; |
| | | |
| | | /** |
| | | * åæ¶å
许è¿è¡çåå°çæä»»å¡æ° |
| | | */ |
| | | private int maxConcurrentTasks = 1; |
| | | |
| | | /** |
| | | * 忬¡çæè¢ç ä¸é |
| | | */ |
| | | private int maxTaskCount = 1000000; |
| | | |
| | | /** |
| | | * å个åå°ä»»å¡çæé¿è¿è¡æ¶é´ï¼åéï¼ï¼è¶
æ¶èªå¨ç»æ¢å¹¶æ¸
ç |
| | | */ |
| | | private int maxRuntimeMinutes = 30; |
| | | |
| | | /** |
| | | * å端轮询建议é´éï¼ç§ï¼ï¼ä»
ç¨äºä¸åç»å端 |
| | | */ |
| | | private int pollIntervalSeconds = 2; |
| | | |
| | | /** |
| | | * æ¸
ç任塿¯è½®æå¤å é¤çè¡æ°ï¼é¿å
é¿æ¶é´å ç¨æ°æ®åº |
| | | * <p> |
| | | * åæ¹å é¤ï¼å次æ¸
çä¸éç¨äºæ§å¶å é¤ä»»å¡å¯¹çº¿ä¸åå
¥çå½±åã |
| | | */ |
| | | private int cleanupBatchSize = 5000; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.framework.bagcode; |
| | | |
| | | import jakarta.annotation.PostConstruct; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.Semaphore; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | /** |
| | | * è¢ç çæä»»å¡è¿è¡ææ³¨å表 |
| | | * <p> |
| | | * ä¿åãåªäºä»»å¡æ£å¨çæãä¸ãæ¯å¦å·²è¯·æ±åæ¶ããåæ¶æ è®°æ¾å
åèéæ°æ®åºï¼ |
| | | * æ¯å 为çæçº¿ç¨éè¦æ¯æ®µè½æ£æ¥ä¸æ¬¡åæ¶ï¼èµ°æ°æ®åºä¼äº§çå¤§éæ è°æ¥è¯¢ï¼æ¬é¡¹ç®ä¸ºåæºé¨ç½²ï¼ä¸¤è
åè¿ç¨ã |
| | | * <p> |
| | | * è¿ç¨éå¯ä¼è®©ææè¿è¡æä¸¢å¤±ï¼æ¤æ¶ç±æ¸
ç任塿æ®ççéç»æä»»å¡åæ¶ä¸ºå¤±è´¥ã |
| | | */ |
| | | @Component |
| | | public class MesProBagCodeGenerateTaskRegistry { |
| | | |
| | | @Resource |
| | | private MesProBagCodeGenerateProperties properties; |
| | | |
| | | /** |
| | | * æ£å¨è¿è¡çä»»å¡ï¼ä»»å¡ç¼å· â åæ¶æ è®° |
| | | */ |
| | | private final Map<Long, AtomicBoolean> runningTasks = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * å¹¶å许å¯ï¼çº¦æåå°çæä»»å¡æ° |
| | | */ |
| | | private Semaphore permits; |
| | | |
| | | @PostConstruct |
| | | public void init() { |
| | | permits = new Semaphore(Math.max(1, properties.getMaxConcurrentTasks())); |
| | | } |
| | | |
| | | /** |
| | | * å°è¯å ç¨ä¸ä¸ªè¿è¡åé¢ |
| | | */ |
| | | public boolean tryAcquire() { |
| | | return permits.tryAcquire(); |
| | | } |
| | | |
| | | /** |
| | | * éæ¾è¿è¡åé¢ï¼å®æã失败ãåæ¶ãæäº¤å¤±è´¥åéè°ç¨ï¼ |
| | | */ |
| | | public void release() { |
| | | permits.release(); |
| | | } |
| | | |
| | | /** |
| | | * å½åç±æ¬è¿ç¨ææç任塿°ï¼å«å建好ãå°æªè¢«çº¿ç¨æ¥ç®¡çï¼ |
| | | */ |
| | | public int getRunningCount() { |
| | | return runningTasks.size(); |
| | | } |
| | | |
| | | /** |
| | | * 注åä»»å¡å¹¶è¿åå
¶åæ¶æ è®° |
| | | * <p> |
| | | * å¿
é¡»å¨ä»»å¡è½åºåãæéçº¿ç¨æ± åè°ç¨ï¼é¿å
æ¸
ç任塿尿ªæ³¨åçè®°å½è¯¯å¤ä¸ºé坿®çã |
| | | */ |
| | | public AtomicBoolean register(Long taskId) { |
| | | AtomicBoolean flag = new AtomicBoolean(false); |
| | | runningTasks.put(taskId, flag); |
| | | return flag; |
| | | } |
| | | |
| | | /** |
| | | * è·åä»»å¡çåæ¶æ è®°ï¼ä»»å¡ä¸å±äºæ¬è¿ç¨æ¶è¿å null |
| | | */ |
| | | public AtomicBoolean get(Long taskId) { |
| | | return runningTasks.get(taskId); |
| | | } |
| | | |
| | | /** |
| | | * 任塿¯å¦ç±æ¬è¿ç¨ææ |
| | | */ |
| | | public boolean isRunning(Long taskId) { |
| | | return runningTasks.containsKey(taskId); |
| | | } |
| | | |
| | | /** |
| | | * 注éä»»å¡ |
| | | */ |
| | | public void unregister(Long taskId) { |
| | | runningTasks.remove(taskId); |
| | | } |
| | | |
| | | /** |
| | | * 请æ±åæ¶ï¼ä»»å¡å¨è·åç½®ä½åæ¶æ è®° |
| | | * |
| | | * @return true è¡¨ç¤ºåæ¶è¯·æ±å·²éè¾¾ï¼false 表示任å¡ä¸å¨æ¬è¿ç¨è¿è¡ |
| | | */ |
| | | public boolean requestCancel(Long taskId) { |
| | | AtomicBoolean flag = runningTasks.get(taskId); |
| | | if (flag == null) { |
| | | return false; |
| | | } |
| | | flag.set(true); |
| | | return true; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.job.pro; |
| | | |
| | | import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeGenerateTaskService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.boot.context.event.ApplicationReadyEvent; |
| | | import org.springframework.context.event.EventListener; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * è¢ç çæä»»å¡æ¸
ç |
| | | * <p> |
| | | * è¿ç¨éå¯åå
åè¿è¡æä¸¢å¤±ï¼è¡¨ä¸åå¨ãæéä¸/çæä¸/åæ¶ä¸ãçè®°å½ä¸ä¼åæçº¿ç¨æ¨è¿ï¼ |
| | | * ç±æ¬ä»»å¡åæ¶ä¸ºå¤±è´¥å¹¶æ¸
çå
¶åæåæ°æ®ãç¨ Spring èªå¸¦ç {@code @Scheduled} èé Quartz JobHandlerï¼ |
| | | * åè
éè¦å¨ç®¡çåå°æå·¥ç»è®°ï¼æ¼ç»è®°å°±ä¼éé»ä¸æ§è¡ï¼è¿éçæ¸
çå±äºå¿
é¡»å¼ç®±å³è·çå
åºé»è¾ã |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class MesProBagCodeGenerateCleanupTask { |
| | | |
| | | private final MesProBagCodeGenerateTaskService taskService; |
| | | |
| | | /** |
| | | * æ¯ 5 åé忶䏿¬¡å¼å¸¸ä¸æçä»»å¡ |
| | | */ |
| | | @Scheduled(cron = "0 */5 * * * ?") |
| | | public void cleanup() { |
| | | try { |
| | | int tasks = taskService.recycleStaleTasks(); |
| | | if (tasks > 0) { |
| | | log.info("[cleanup][åæ¶æ®çè¢ç çæä»»å¡ {} 个]", tasks); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[cleanup][åæ¶æ®çè¢ç çæä»»å¡å¤±è´¥]", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯å¨æ¶ç«å»åæ¶ä¸æ¬¡ï¼é¿å
æ®çä»»å¡ä»¥ãçæä¸ãç¶æä¸ç´æå¨çé¢ä¸ |
| | | */ |
| | | @EventListener(ApplicationReadyEvent.class) |
| | | public void recycleOnStartup() { |
| | | try { |
| | | int tasks = taskService.recycleStaleTasks(); |
| | | if (tasks > 0) { |
| | | log.info("[recycleOnStartup][å¯å¨åæ¶æ®çè¢ç çæä»»å¡ {} 个]", tasks); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[recycleOnStartup][å¯å¨åæ¶è¢ç çæä»»å¡å¤±è´¥]", e); |
| | | } |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateBizTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.time.Duration; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | /** |
| | | * è¢ç æ¹éçææ§è¡å¨ï¼åæ¥ä¸å¼æ¥å
±ç¨ï¼ |
| | | * <p> |
| | | * 䏿§å®ç°çå
³é®å·®å¼ï¼ |
| | | * <ul> |
| | | * <li>ç¼ç æ¹éå Redis ç³è¯·ï¼é¿å
æ¯ä¸ªè¢ç 两次ç½ç»å¾è¿ï¼</li> |
| | | * <li>è¢åºå·ä¸æ¬¡æ§åé
ï¼å段åªåå移计ç®ï¼</li> |
| | | * <li>æ¯æ®µç¬ç«äºå¡è½åºï¼å¹¶å¨æ®µé´ååè¿åº¦ãæ£æ¥åæ¶ä¸è¶
æ¶ã</li> |
| | | * </ul> |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | public class MesProBagCodeBatchGenerator { |
| | | |
| | | @Resource |
| | | private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskMapper taskMapper; |
| | | @Resource |
| | | private MesCodeRedisDAO codeRedisDao; |
| | | @Resource |
| | | private MesProBagCodeChunkWriter chunkWriter; |
| | | @Resource |
| | | private MesProBagCodeGenerateProperties properties; |
| | | |
| | | /** |
| | | * æ§è¡çæ |
| | | * |
| | | * @param spec çæåæ° |
| | | * @param taskId ä»»å¡ç¼å·ï¼ä¸º null è¡¨ç¤ºåæ¥çæï¼ä¸ååè¿åº¦ |
| | | * @param cancelFlag åæ¶æ è®° |
| | | * @return çæç»æï¼è¢ç æ° + æçç¼å·ï¼ |
| | | */ |
| | | public GenerateResult generate(MesProBagCodeGenerateSpec spec, Long taskId, AtomicBoolean cancelFlag) { |
| | | assertNotCancelled(cancelFlag); |
| | | long deadline = System.currentTimeMillis() |
| | | + Duration.ofMinutes(Math.max(1, properties.getMaxRuntimeMinutes())).toMillis(); |
| | | // 1. å
¨å±è¢åºå·ä¸æ¬¡æ§åé
ï¼åç»å段åªååç§»ï¼é¿å
æ¯æ®µé½æ¥åºå
æå¤§å¼ |
| | | int startNo = codeRedisDao.allocateBagNoStart(spec.totalCount(), bagCodeMapper.selectMaxBagNo()); |
| | | // 2. æçï¼ä»
æ°æ®å
模å¼ä¸æ N è¢æ¢ç®æ¶çæï¼ |
| | | PalletRefs palletRefs = createPallets(spec, cancelFlag, deadline); |
| | | // 3. è¢ç åæ®µè½åº |
| | | int chunkSize = Math.max(1, properties.getChunkSize()); |
| | | int finished = 0; |
| | | while (finished < spec.totalCount()) { |
| | | assertNotCancelled(cancelFlag); |
| | | assertNotTimeout(deadline); |
| | | int size = Math.min(chunkSize, spec.totalCount() - finished); |
| | | // æ¯ä¸ªè¢ç éè¦è¿½æº¯ç + ä¸å¡ç |
| | | List<String> codes = codeRedisDao.generateBatch(size * 2); |
| | | List<MesProBagCodeDO> chunk = new ArrayList<>(size); |
| | | for (int i = 0; i < size; i++) { |
| | | int index = finished + i; |
| | | PalletRef pallet = palletRefs.resolve(spec, index); |
| | | chunk.add(new MesProBagCodeDO() |
| | | .setUuid(spec.uuid()) |
| | | .setCode(codes.get(i * 2)) |
| | | .setBusinessCode(codes.get(i * 2 + 1)) |
| | | .setBagNo(startNo + index) |
| | | .setItemId(spec.itemId()) |
| | | .setItemCode(spec.itemCode()) |
| | | .setItemName(spec.itemName()) |
| | | .setPalletId(pallet == null ? null : pallet.id()) |
| | | .setPalletCode(pallet == null ? null : pallet.tempCode()) |
| | | .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus()) |
| | | .setRemark(spec.remark())); |
| | | } |
| | | chunkWriter.insertBagCodes(chunk); |
| | | finished += size; |
| | | if (taskId != null) { |
| | | taskMapper.updateProgress(taskId, finished); |
| | | } |
| | | } |
| | | return new GenerateResult(finished, palletRefs.ids()); |
| | | } |
| | | |
| | | /** |
| | | * æéå建æçï¼æ°æ®å
模å¼ä¸è¢ç æãæ¯æ N è¢ãååç»å® |
| | | */ |
| | | private PalletRefs createPallets(MesProBagCodeGenerateSpec spec, |
| | | AtomicBoolean cancelFlag, long deadline) { |
| | | if (spec.bizType() != MesProBagCodeGenerateBizTypeEnum.PACKAGE || spec.palletCount() <= 0) { |
| | | return PalletRefs.EMPTY; |
| | | } |
| | | int chunkSize = Math.max(1, properties.getChunkSize()); |
| | | List<Long> ids = new ArrayList<>(spec.palletCount()); |
| | | List<String> tempCodes = new ArrayList<>(spec.palletCount()); |
| | | int created = 0; |
| | | while (created < spec.palletCount()) { |
| | | assertNotCancelled(cancelFlag); |
| | | assertNotTimeout(deadline); |
| | | int size = Math.min(chunkSize, spec.palletCount() - created); |
| | | // æ¯ä¸ªæçéè¦ä¸´æ¶ç + ä¸å¡ç |
| | | List<String> codes = codeRedisDao.generateBatch(size * 2); |
| | | List<MesProPalletDO> pallets = new ArrayList<>(size); |
| | | for (int i = 0; i < size; i++) { |
| | | pallets.add(new MesProPalletDO() |
| | | .setTempCode(codes.get(i * 2)) |
| | | .setBusinessCode(codes.get(i * 2 + 1)) |
| | | .setBagCount(spec.perPallet()) |
| | | .setAutoGenerated(true) |
| | | .setItemId(spec.itemId()) |
| | | .setGenerateUuid(spec.uuid()) |
| | | .setStatus(MesProPalletStatusEnum.IN_USE.getStatus())); |
| | | } |
| | | chunkWriter.insertPallets(pallets); |
| | | pallets.forEach(pallet -> { |
| | | ids.add(pallet.getId()); |
| | | tempCodes.add(pallet.getTempCode()); |
| | | }); |
| | | created += size; |
| | | } |
| | | return new PalletRefs(ids, tempCodes); |
| | | } |
| | | |
| | | private void assertNotCancelled(AtomicBoolean cancelFlag) { |
| | | if (cancelFlag != null && cancelFlag.get()) { |
| | | throw new CancelledException(); |
| | | } |
| | | } |
| | | |
| | | private void assertNotTimeout(long deadline) { |
| | | if (System.currentTimeMillis() > deadline) { |
| | | throw new IllegalStateException("çæä»»å¡è¶
è¿æé¿è¿è¡æ¶é´ï¼" |
| | | + properties.getMaxRuntimeMinutes() + " åéï¼"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æçå¼ç¨ï¼åªä¿çè¢ç éè¦ååçä¸¤ä¸ªåæ®µï¼é¿å
å¨å
åä¸å 积æ´è¡¨ DO |
| | | */ |
| | | private record PalletRefs(List<Long> ids, List<String> tempCodes) { |
| | | |
| | | private static final PalletRefs EMPTY = new PalletRefs(List.of(), List.of()); |
| | | |
| | | private PalletRef resolve(MesProBagCodeGenerateSpec spec, int bagIndex) { |
| | | if (ids.isEmpty() || spec.perPallet() <= 0) { |
| | | return null; |
| | | } |
| | | int palletIndex = bagIndex / spec.perPallet(); |
| | | return new PalletRef(ids.get(palletIndex), tempCodes.get(palletIndex)); |
| | | } |
| | | |
| | | } |
| | | |
| | | private record PalletRef(Long id, String tempCode) { |
| | | } |
| | | |
| | | /** |
| | | * çæç»æï¼åæ¥è·¯å¾éè¦æçç¼å·ç¨äºååºï¼å¼æ¥è·¯å¾åªå
³å¿å®ææ°é |
| | | */ |
| | | public record GenerateResult(int finishedCount, List<Long> palletIds) { |
| | | } |
| | | |
| | | /** |
| | | * åæ¶ä¿¡å·ï¼çæçº¿ç¨æ£æµå°åæ¶æ¶æåºï¼ç±è°ç¨æ¹ç»ä¸æ¸
çåæå |
| | | */ |
| | | public static class CancelledException extends RuntimeException { |
| | | |
| | | public CancelledException() { |
| | | super("çæä»»å¡å·²åæ¶"); |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.transaction.annotation.Propagation; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * è¢ç /æçåæ®µåå
¥å¨ |
| | | * <p> |
| | | * æ¯æ®µä½¿ç¨ç¬ç«äºå¡ï¼REQUIRES_NEWï¼ï¼å åä¸è¡æ¾å¨åä¸ä¸ªäºå¡éä¼å½¢æè¶
大äºå¡ï¼ |
| | | * é¿æ¶é´å ç¨ undo/redo ä¸è¡éï¼å¤±è´¥æ¶åæ»ææ¬ä¹æé«ã |
| | | * <p> |
| | | * ç¬ç«æ Bean æ¯ä¸ºäºè®© {@code REQUIRES_NEW} ç» Spring 代ççæï¼é¿å
åç±»èªè°ç¨ç»è¿äºå¡ã |
| | | */ |
| | | @Component |
| | | public class MesProBagCodeChunkWriter { |
| | | |
| | | /** |
| | | * åæ¡ INSERT è¯å¥çè¡æ°ï¼ä¸å段大å°åå¼ï¼æ§å¶åæ¡ SQL çå
ä½å¤§å° |
| | | */ |
| | | private static final int INSERT_BATCH_SIZE = 1000; |
| | | |
| | | @Resource |
| | | private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource |
| | | private MesProPalletMapper palletMapper; |
| | | |
| | | /** |
| | | * åå
¥ä¸æ®µè¢ç |
| | | */ |
| | | @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) |
| | | public void insertBagCodes(List<MesProBagCodeDO> bagCodes) { |
| | | bagCodeMapper.insertBatch(bagCodes, INSERT_BATCH_SIZE); |
| | | } |
| | | |
| | | /** |
| | | * åå
¥ä¸æ®µæçï¼æå
¥å ID ä¼åå¡«å°å®ä½ä¸ï¼ |
| | | */ |
| | | @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) |
| | | public void insertPallets(List<MesProPalletDO> pallets) { |
| | | palletMapper.insertBatch(pallets, INSERT_BATCH_SIZE); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * è¢ç çæåæåæ¸
çå¨ |
| | | * <p> |
| | | * çæå¤±è´¥æåæ¶æ¶ä¼çä¸é¨åè¢ç 䏿çãè¿äºæ°æ®ä»æªå¯¹å¤å±ç¤ºï¼ä¸åé»è¾å é¤ï¼ |
| | | * ç´æ¥æ {@code uuid} åæ®µç©çå é¤ï¼é¿å
çä¸å å䏿¡åå¾è¡ã |
| | | * <p> |
| | | * åæ¡ DELETE èªå¸¦ååæ§ï¼ä¸å·²ç¨ LIMIT æ§å¶å次影åè¡æ°ï¼å æ¤ä¸éè¦é¢å¤äºå¡ã |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | public class MesProBagCodeGenerateCleaner { |
| | | |
| | | @Resource |
| | | private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource |
| | | private MesProPalletMapper palletMapper; |
| | | @Resource |
| | | private MesProBagCodeGenerateProperties properties; |
| | | |
| | | /** |
| | | * æ¸
çæä¸ªçæç»çææåæåæ°æ® |
| | | * |
| | | * @param uuid çæç» UUID |
| | | */ |
| | | public void cleanup(String uuid) { |
| | | int batchSize = Math.max(1, properties.getCleanupBatchSize()); |
| | | int deleted; |
| | | do { |
| | | deleted = bagCodeMapper.deletePhysicallyByUuidChunk(uuid, batchSize); |
| | | } while (deleted >= batchSize); |
| | | do { |
| | | deleted = palletMapper.deletePhysicallyByGenerateUuidChunk(uuid, batchSize); |
| | | } while (deleted >= batchSize); |
| | | log.info("[cleanup][çæç» {} åæåæ¸
ç宿]", uuid); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateTaskRegistry; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | /** |
| | | * è¢ç çæä»»å¡æ§è¡å¨ |
| | | * <p> |
| | | * å¨åå°çº¿ç¨å
宿忮µçæï¼å¹¶è´è´£ï¼ååè¿åº¦ãååºåæ¶ã失败/忶忏
çåæåã |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | public class MesProBagCodeGenerateRunner { |
| | | |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskMapper taskMapper; |
| | | @Resource |
| | | private MesProBagCodeBatchGenerator generator; |
| | | @Resource |
| | | private MesProBagCodeGenerateCleaner cleaner; |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskRegistry registry; |
| | | |
| | | /** |
| | | * æ§è¡çæä»»å¡ï¼ç±çº¿ç¨æ± è°ç¨ï¼å¿å¨è¯·æ±çº¿ç¨å
ç´æ¥è°ç¨ï¼ |
| | | * |
| | | * @param taskId ä»»å¡ç¼å· |
| | | * @param spec çæåæ° |
| | | */ |
| | | public void run(Long taskId, MesProBagCodeGenerateSpec spec) { |
| | | // åæ¶æ è®°å¨æäº¤æ¶å·²æ³¨åï¼è¿éåªåç¨ï¼åä¸å°è¯´ææ³¨åç¯è被ç»è¿ï¼è¡¥ä¸ä¸ªä»¥ä¿è¯åæ¶å¯ç¨ |
| | | AtomicBoolean cancelFlag = registry.get(taskId); |
| | | if (cancelFlag == null) { |
| | | cancelFlag = registry.register(taskId); |
| | | } |
| | | try { |
| | | taskMapper.updateStatus(taskId, MesProBagCodeGenerateTaskStatusEnum.RUNNING.getStatus(), |
| | | null, null); |
| | | int finished = generator.generate(spec, taskId, cancelFlag).finishedCount(); |
| | | // æå䏿®µåå
¥åã任塿¶å°¾åææ¶å°åæ¶ï¼ä»æåæ¶å¤çï¼ä¿è¯"åæ¶åæ åæå" |
| | | if (cancelFlag.get()) { |
| | | throw new MesProBagCodeBatchGenerator.CancelledException(); |
| | | } |
| | | taskMapper.updateProgress(taskId, finished); |
| | | taskMapper.updateStatus(taskId, MesProBagCodeGenerateTaskStatusEnum.FINISHED.getStatus(), |
| | | null, LocalDateTime.now()); |
| | | log.info("[run][çæä»»å¡ {} 宿ï¼å
± {} æ¡è¢ç ]", taskId, finished); |
| | | } catch (MesProBagCodeBatchGenerator.CancelledException e) { |
| | | cleanup(taskId, spec.uuid()); |
| | | taskMapper.updateStatus(taskId, MesProBagCodeGenerateTaskStatusEnum.CANCELED.getStatus(), |
| | | null, LocalDateTime.now()); |
| | | log.info("[run][çæä»»å¡ {} 已忶]", taskId); |
| | | } catch (Exception e) { |
| | | cleanup(taskId, spec.uuid()); |
| | | taskMapper.updateStatus(taskId, MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus(), |
| | | truncateError(e), LocalDateTime.now()); |
| | | log.error("[run][çæä»»å¡ {} çæå¤±è´¥]", taskId, e); |
| | | } finally { |
| | | registry.unregister(taskId); |
| | | registry.release(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ¸
çåæåï¼æ¸
ç失败åªè®°å½æ¥å¿ï¼ä¸è½å½±åä»»å¡ç»æååï¼å¦åä»»å¡ä¼ä¸ç´æå¨"çæä¸" |
| | | */ |
| | | private void cleanup(Long taskId, String uuid) { |
| | | try { |
| | | cleaner.cleanup(uuid); |
| | | } catch (Exception e) { |
| | | log.error("[cleanup][çæä»»å¡ {} åæåæ¸
ç失败ï¼uuid={}]", taskId, uuid, e); |
| | | } |
| | | } |
| | | |
| | | private String truncateError(Exception e) { |
| | | String message = e.getMessage(); |
| | | if (message == null || message.isBlank()) { |
| | | message = e.getClass().getSimpleName(); |
| | | } |
| | | return message.length() > 500 ? message.substring(0, 500) : message; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateBizTypeEnum; |
| | | |
| | | /** |
| | | * è¢ç æ¹éçæåæ°ï¼åæ¥ä¸å¼æ¥å
±ç¨ï¼ |
| | | * |
| | | * @param bizType ä¸å¡ç±»å |
| | | * @param uuid çæç» UUID |
| | | * @param itemId åç§ç¼å· |
| | | * @param itemCode åç§ç¼ç |
| | | * @param itemName åç§åç§° |
| | | * @param unitType 计éåä½ç±»å |
| | | * @param perPallet æ¯æçè¢æ°ï¼å¨ç±»å为 0ï¼ |
| | | * @param palletCount æçæ°ï¼åå§æ°æ®ä¸º 0ï¼ |
| | | * @param totalCount è¢ç æ»æ° |
| | | * @param remark 夿³¨ |
| | | */ |
| | | public record MesProBagCodeGenerateSpec( |
| | | MesProBagCodeGenerateBizTypeEnum bizType, |
| | | String uuid, |
| | | Long itemId, |
| | | String itemCode, |
| | | String itemName, |
| | | String unitType, |
| | | int perPallet, |
| | | int palletCount, |
| | | int totalCount, |
| | | String remark) { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; |
| | | |
| | | /** |
| | | * è¢ç æ¹éçæä»»å¡ Service |
| | | * <p> |
| | | * åªæ¿æ
è¶
åºåæ¥éå¼ççæè¯·æ±ï¼å°æ¹éä»å¨è¯·æ±çº¿ç¨å
忥宿ï¼ä¸è¿ä»»å¡ã䏿éã |
| | | */ |
| | | public interface MesProBagCodeGenerateTaskService { |
| | | |
| | | /** |
| | | * æäº¤åå°çæä»»å¡ |
| | | * |
| | | * @param spec çæåæ° |
| | | * @param requestId å¹çé®ï¼å¯ä¸ºç©ºï¼é空æ¶åä¸é®åªä¼å建ä¸ä¸ªä»»å¡ |
| | | * @return å·²å建æå·²åå¨çä»»å¡ |
| | | */ |
| | | MesProBagCodeGenerateTaskDO submit(MesProBagCodeGenerateSpec spec, String requestId); |
| | | |
| | | /** |
| | | * è·å¾çæä»»å¡ |
| | | * |
| | | * @param id ä»»å¡ç¼å· |
| | | * @return ä»»å¡ï¼ä¸å卿¶æä¸å¡å¼å¸¸ |
| | | */ |
| | | MesProBagCodeGenerateTaskDO getTask(Long id); |
| | | |
| | | /** |
| | | * æå¹çé®è·å¾çæä»»å¡ |
| | | * |
| | | * @param requestId å¹çé® |
| | | * @return ä»»å¡ï¼ä¸å卿¶è¿å null |
| | | */ |
| | | MesProBagCodeGenerateTaskDO getTaskByRequestId(String requestId); |
| | | |
| | | /** |
| | | * åæ¶çæä»»å¡ |
| | | * <p> |
| | | * 任塿£å¨æ¬è¿ç¨æ§è¡æ¶ç½®ä½åæ¶æ è®°ï¼ç±æ§è¡çº¿ç¨åæ¢å¹¶æ¸
çé¨åæ°æ®ï¼ |
| | | * å·²å¤äºç»æçä»»å¡ä¸å
è®¸åæ¶ã |
| | | * |
| | | * @param id ä»»å¡ç¼å· |
| | | */ |
| | | void cancel(Long id); |
| | | |
| | | /** |
| | | * åæ¶å æå¡é坿å¼å¸¸ä¸æèæ®çå¨éç»æçä»»å¡ |
| | | * |
| | | * @return åæ¶ç任塿° |
| | | */ |
| | | int recycleStaleTasks(); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.bagcode; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateTaskRegistry; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.dao.DuplicateKeyException; |
| | | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.concurrent.RejectedExecutionException; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_BUSY; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_STATUS_INVALID; |
| | | |
| | | /** |
| | | * è¢ç æ¹éçæä»»å¡ Service å®ç° |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | @Validated |
| | | public class MesProBagCodeGenerateTaskServiceImpl implements MesProBagCodeGenerateTaskService { |
| | | |
| | | /** |
| | | * éç»æç¶æéå |
| | | */ |
| | | private static final List<Integer> UNFINISHED_STATUSES = List.of( |
| | | MesProBagCodeGenerateTaskStatusEnum.PENDING.getStatus(), |
| | | MesProBagCodeGenerateTaskStatusEnum.RUNNING.getStatus(), |
| | | MesProBagCodeGenerateTaskStatusEnum.CANCELING.getStatus()); |
| | | |
| | | /** |
| | | * ç»æç¶æéå |
| | | */ |
| | | private static final List<Integer> FINAL_STATUSES = List.of( |
| | | MesProBagCodeGenerateTaskStatusEnum.FINISHED.getStatus(), |
| | | MesProBagCodeGenerateTaskStatusEnum.CANCELED.getStatus(), |
| | | MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus()); |
| | | |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskMapper taskMapper; |
| | | @Resource |
| | | private MesProBagCodeGenerateRunner runner; |
| | | @Resource |
| | | private MesProBagCodeGenerateCleaner cleaner; |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskRegistry registry; |
| | | |
| | | @Resource(name = "mesBagCodeGenerateExecutor") |
| | | private ThreadPoolTaskExecutor executor; |
| | | |
| | | @Override |
| | | public MesProBagCodeGenerateTaskDO submit(MesProBagCodeGenerateSpec spec, String requestId) { |
| | | // 1. å¹çï¼ç½ç»éè¯å¤ç¨åä¸ä¸ª requestId æ¶ç´æ¥è¿åå·²æä»»å¡ï¼ä¸åçæ |
| | | MesProBagCodeGenerateTaskDO existing = findExisting(requestId); |
| | | if (existing != null) { |
| | | return existing; |
| | | } |
| | | // 2. å¹¶åé¸é¨ï¼å䏿¶é´åªè·ä¸ä¸ªåå°çæä»»å¡ï¼è¢«æ¡ä½æ¶å¸¦ä¸å½åä»»å¡è¿åº¦ |
| | | if (!registry.tryAcquire()) { |
| | | // å¹¶åéå¤æäº¤æ¶ï¼å¦ä¸è¯·æ±å¯è½å·²æ¢å
è½åº |
| | | existing = findExisting(requestId); |
| | | if (existing != null) { |
| | | return existing; |
| | | } |
| | | throw exception(PRO_BAG_CODE_GENERATE_TASK_BUSY, resolveRunningProgress()); |
| | | } |
| | | // æé䏿¦äº¤ç»æ§è¡å¨ï¼å°±ç±æ§è¡çº¿ç¨å¨æ¶å°¾æ¶å½è¿ï¼æ¤åä»»ä½å¤±è´¥é½å¿
é¡»å¨è¿éå½è¿ |
| | | boolean handedOver = false; |
| | | try { |
| | | MesProBagCodeGenerateTaskDO task = MesProBagCodeGenerateTaskDO.builder() |
| | | .requestId(requestId) |
| | | .bizType(spec.bizType().getType()) |
| | | .uuid(spec.uuid()) |
| | | .itemId(spec.itemId()) |
| | | .itemCode(spec.itemCode()) |
| | | .itemName(spec.itemName()) |
| | | .unitType(spec.unitType()) |
| | | .perPallet(spec.perPallet()) |
| | | .palletCount(spec.palletCount()) |
| | | .totalCount(spec.totalCount()) |
| | | .finishedCount(0) |
| | | .status(MesProBagCodeGenerateTaskStatusEnum.PENDING.getStatus()) |
| | | .remark(spec.remark()) |
| | | .build(); |
| | | try { |
| | | taskMapper.insert(task); |
| | | } catch (DuplicateKeyException e) { |
| | | // å¯ä¸ç´¢å¼å
åºå¹¶åéå¤æäº¤ |
| | | existing = findExisting(requestId); |
| | | if (existing != null) { |
| | | return existing; |
| | | } |
| | | throw e; |
| | | } |
| | | // 3. å
注åè¿è¡æåæéï¼é¿å
ãå·²è½åºä½æªæ³¨åãççªå£è¢«æ¸
çä»»å¡è¯¯åæ¶ |
| | | registry.register(task.getId()); |
| | | handedOver = true; |
| | | try { |
| | | executor.execute(() -> runner.run(task.getId(), spec)); |
| | | } catch (RejectedExecutionException e) { |
| | | registry.unregister(task.getId()); |
| | | handedOver = false; |
| | | taskMapper.updateStatus(task.getId(), |
| | | MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus(), |
| | | "çæçº¿ç¨ç¹å¿ï¼è¯·ç¨åéè¯", LocalDateTime.now()); |
| | | throw exception(PRO_BAG_CODE_GENERATE_TASK_BUSY, 0); |
| | | } |
| | | return task; |
| | | } finally { |
| | | if (!handedOver) { |
| | | registry.release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public MesProBagCodeGenerateTaskDO getTask(Long id) { |
| | | MesProBagCodeGenerateTaskDO task = taskMapper.selectById(id); |
| | | if (task == null) { |
| | | throw exception(PRO_BAG_CODE_GENERATE_TASK_NOT_EXISTS); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | @Override |
| | | public MesProBagCodeGenerateTaskDO getTaskByRequestId(String requestId) { |
| | | return findExisting(requestId); |
| | | } |
| | | |
| | | @Override |
| | | public void cancel(Long id) { |
| | | MesProBagCodeGenerateTaskDO task = getTask(id); |
| | | if (FINAL_STATUSES.contains(task.getStatus())) { |
| | | throw exception(PRO_BAG_CODE_GENERATE_TASK_STATUS_INVALID); |
| | | } |
| | | if (registry.requestCancel(id)) { |
| | | // æ§è¡çº¿ç¨ä¼å¨å½ååæ®µç»æååæ¢ãæ¸
çåæåå¹¶ç½®ä¸ºå·²åæ¶ |
| | | taskMapper.updateStatus(id, MesProBagCodeGenerateTaskStatusEnum.CANCELING.getStatus(), |
| | | null, null); |
| | | return; |
| | | } |
| | | // ä¸å¨æ¬è¿ç¨è¿è¡ï¼è¿ç¨éå¯åçæ®çä»»å¡ï¼ï¼ç´æ¥æ¸
çæ®çå¹¶æ¶å°¾ |
| | | cleanupQuietly(task); |
| | | taskMapper.updateStatus(id, MesProBagCodeGenerateTaskStatusEnum.CANCELED.getStatus(), |
| | | null, LocalDateTime.now()); |
| | | } |
| | | |
| | | @Override |
| | | public int recycleStaleTasks() { |
| | | List<MesProBagCodeGenerateTaskDO> list = taskMapper.selectUnfinishedList(UNFINISHED_STATUSES); |
| | | // æäº¤çº¿ç¨ãè½åº â æ³¨åè¿è¡æãä¹é´å卿ççªå£ï¼è·³è¿åå建çä»»å¡é¿å
è¯¯åæ¶ |
| | | LocalDateTime recent = LocalDateTime.now().minusMinutes(1); |
| | | int count = 0; |
| | | for (MesProBagCodeGenerateTaskDO task : list) { |
| | | if (task.getCreateTime() != null && task.getCreateTime().isAfter(recent)) { |
| | | continue; |
| | | } |
| | | if (registry.isRunning(task.getId())) { |
| | | continue; |
| | | } |
| | | cleanupQuietly(task); |
| | | taskMapper.updateStatus(task.getId(), MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus(), |
| | | "ä»»å¡å æå¡é坿å¼å¸¸ä¸æèç»æ¢ï¼è¯·éæ°çæ", LocalDateTime.now()); |
| | | count++; |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | // ==================== ç§ææ¹æ³ ==================== |
| | | |
| | | private MesProBagCodeGenerateTaskDO findExisting(String requestId) { |
| | | if (StrUtil.isBlank(requestId)) { |
| | | return null; |
| | | } |
| | | return taskMapper.selectByRequestId(requestId); |
| | | } |
| | | |
| | | /** |
| | | * æ¸
çåæåï¼æ¸
ç失败ä¸è½é»æä»»å¡ç»æååï¼å¦åä»»å¡ä¼ä¸ç´æå¨éç»æ |
| | | */ |
| | | private void cleanupQuietly(MesProBagCodeGenerateTaskDO task) { |
| | | try { |
| | | cleaner.cleanup(task.getUuid()); |
| | | } catch (Exception e) { |
| | | log.error("[cleanupQuietly][çæä»»å¡ {} åæåæ¸
ç失败ï¼uuid={}]", |
| | | task.getId(), task.getUuid(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ£å¨è¿è¡ä»»å¡çè¿åº¦ç¾åæ¯ï¼ç¨äºãå·²æä»»å¡è¿è¡ä¸ãçæç¤º |
| | | */ |
| | | private int resolveRunningProgress() { |
| | | List<MesProBagCodeGenerateTaskDO> tasks = taskMapper.selectUnfinishedList(UNFINISHED_STATUSES); |
| | | return tasks.stream().findFirst().map(this::calcProgress).orElse(0); |
| | | } |
| | | |
| | | private int calcProgress(MesProBagCodeGenerateTaskDO task) { |
| | | Integer total = task.getTotalCount(); |
| | | if (total == null || total <= 0) { |
| | | return 0; |
| | | } |
| | | int finished = task.getFinishedCount() == null ? 0 : task.getFinishedCount(); |
| | | return (int) Math.min(100L, (long) finished * 100 / total); |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateBizTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; |
| | | import jakarta.annotation.Resource; |
| | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService integrityService; |
| | | |
| | | @Resource |
| | | private MesProBagCodeBatchGenerator batchGenerator; |
| | | @Resource |
| | | private MesProBagCodeGenerateCleaner generateCleaner; |
| | | @Resource |
| | | private MesProBagCodeGenerateTaskService generateTaskService; |
| | | @Resource |
| | | private MesProBagCodeGenerateProperties generateProperties; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesProBagCodeGenerateRespVO generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) { |
| | | // 1. æ ¡éªåç§å¹¶è¯»å主计éåä½ç±»å |
| | | MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId()); |
| | |
| | | // 2. æç±»åæ¢ç®è¢ç æ°éï¼Nè¢ = æ¯æ N è¢ Ã æçæ°ï¼å¨ = ç´æ¥è¾å
¥è¢ç æ°ï¼æ æçï¼ |
| | | boolean ton = UNIT_TYPE_TON.equals(type); |
| | | int perPallet = ton ? 0 : parseBagsPerPallet(type); |
| | | int totalCount; |
| | | long totalCount; |
| | | int palletCount = 0; |
| | | if (ton) { |
| | | if (generateReqVO.getBagCount() == null || generateReqVO.getBagCount() <= 0) { |
| | |
| | | throw exception(PRO_BAG_CODE_PALLET_COUNT_REQUIRED); |
| | | } |
| | | palletCount = generateReqVO.getPalletCount(); |
| | | totalCount = perPallet * palletCount; |
| | | // ç¨ long 计ç®ï¼é¿å
大æçæ°ç¸ä¹æº¢åº int |
| | | totalCount = (long) perPallet * palletCount; |
| | | } |
| | | assertGenerateCountAllowed(totalCount); |
| | | |
| | | // 3. çæé¶æ®µï¼è¢ç ç´æ¥ç»å®åç§ï¼çäº§æ¹æ¬¡å¨åç»å½å
¥ |
| | | String uuid = IdUtil.fastSimpleUUID(); |
| | | List<MesProPalletDO> pallets = new ArrayList<>(palletCount); |
| | | if (!ton) { |
| | | for (int i = 0; i < palletCount; i++) { |
| | | pallets.add(new MesProPalletDO() |
| | | .setTempCode(codeRedisDao.generate()) |
| | | .setBusinessCode(codeRedisDao.generate()) |
| | | .setBagCount(perPallet) |
| | | .setAutoGenerated(true) |
| | | .setItemId(item.getId()) |
| | | .setStatus(MesProPalletStatusEnum.IN_USE.getStatus())); |
| | | } |
| | | palletMapper.insertBatch(pallets); |
| | | } |
| | | // è¢åºå·åååé
ï¼å
ååºå
æå¤§åºå·ä½åºåï¼åç± Redis ååèªå¢ï¼é¿å
å¹¶åçæäº§çéå¤è¢åºå· |
| | | int startNo = codeRedisDao.allocateBagNoStart(totalCount, bagCodeMapper.selectMaxBagNo()); |
| | | List<MesProBagCodeDO> bagCodes = new ArrayList<>(totalCount); |
| | | for (int i = 0; i < totalCount; i++) { |
| | | int bagNo = startNo + i; |
| | | MesProPalletDO pallet = ton ? null : pallets.get(i / perPallet); |
| | | bagCodes.add(new MesProBagCodeDO() |
| | | .setUuid(uuid) |
| | | .setCode(codeRedisDao.generate()) |
| | | .setBusinessCode(codeRedisDao.generate()) |
| | | .setBagNo(bagNo) |
| | | .setItemId(item.getId()) |
| | | .setItemCode(item.getCode()) |
| | | .setItemName(item.getName()) |
| | | .setPalletId(pallet == null ? null : pallet.getId()) |
| | | .setPalletCode(pallet == null ? null : pallet.getTempCode()) |
| | | .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus()) |
| | | .setRemark(generateReqVO.getRemark())); |
| | | } |
| | | bagCodeMapper.insertBatch(bagCodes); |
| | | MesProBagCodeGenerateSpec spec = new MesProBagCodeGenerateSpec( |
| | | MesProBagCodeGenerateBizTypeEnum.PACKAGE, IdUtil.fastSimpleUUID(), |
| | | item.getId(), item.getCode(), item.getName(), type, |
| | | perPallet, palletCount, (int) totalCount, generateReqVO.getRemark()); |
| | | |
| | | // 4. å°æ¹é忥宿ï¼å¤§æ¹é转åå°ä»»å¡ |
| | | if (totalCount <= generateProperties.getSyncThreshold()) { |
| | | MesProBagCodeBatchGenerator.GenerateResult result = runSyncGenerate(spec); |
| | | return new MesProBagCodeGenerateRespVO() |
| | | .setUuid(uuid) |
| | | .setUuid(spec.uuid()) |
| | | .setType(type) |
| | | .setPerPallet(perPallet) |
| | | .setTotalCount(totalCount) |
| | | .setPalletIds(pallets.stream().map(MesProPalletDO::getId).toList()); |
| | | .setTotalCount(result.finishedCount()) |
| | | .setPalletCount(result.palletIds().size()) |
| | | .setPalletIds(result.palletIds()) |
| | | .setAsync(false); |
| | | } |
| | | MesProBagCodeGenerateTaskDO task = generateTaskService.submit(spec, generateReqVO.getRequestId()); |
| | | return new MesProBagCodeGenerateRespVO() |
| | | .setUuid(task.getUuid()) |
| | | .setType(task.getUnitType()) |
| | | .setPerPallet(task.getPerPallet()) |
| | | .setTotalCount(task.getTotalCount()) |
| | | .setPalletCount(task.getPalletCount()) |
| | | .setAsync(true) |
| | | .setTaskId(task.getId()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesProBagCodeGenerateRespVO generateRawBagCodes(MesProBagCodeRawGenerateReqVO generateReqVO) { |
| | | // åå§è¢ç ä¸ä¾èµè®¡éåä½ç±»å䏿çï¼ä»
ææ°éçæï¼è¢ç 带åç§åä½ï¼åç»å¯æ£å¸¸å½å
¥æ¹æ¬¡/ç»å®æç |
| | | MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId()); |
| | | String uuid = IdUtil.fastSimpleUUID(); |
| | | // è¢åºå·åååé
ï¼å
ååºå
æå¤§åºå·ä½åºåï¼åç± Redis ååèªå¢ï¼é¿å
å¹¶åçæäº§çéå¤è¢åºå· |
| | | int startNo = codeRedisDao.allocateBagNoStart(generateReqVO.getCount(), bagCodeMapper.selectMaxBagNo()); |
| | | List<MesProBagCodeDO> bagCodes = new ArrayList<>(generateReqVO.getCount()); |
| | | for (int i = 0; i < generateReqVO.getCount(); i++) { |
| | | int bagNo = startNo + i; |
| | | bagCodes.add(new MesProBagCodeDO() |
| | | .setUuid(uuid) |
| | | .setCode(codeRedisDao.generate()) |
| | | .setBusinessCode(codeRedisDao.generate()) |
| | | .setBagNo(bagNo) |
| | | .setItemId(item.getId()) |
| | | .setItemCode(item.getCode()) |
| | | .setItemName(item.getName()) |
| | | .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus()) |
| | | .setRemark(generateReqVO.getRemark())); |
| | | } |
| | | bagCodeMapper.insertBatch(bagCodes); |
| | | long totalCount = generateReqVO.getCount(); |
| | | assertGenerateCountAllowed(totalCount); |
| | | |
| | | MesProBagCodeGenerateSpec spec = new MesProBagCodeGenerateSpec( |
| | | MesProBagCodeGenerateBizTypeEnum.RAW, IdUtil.fastSimpleUUID(), |
| | | item.getId(), item.getCode(), item.getName(), null, |
| | | 0, 0, (int) totalCount, generateReqVO.getRemark()); |
| | | |
| | | if (totalCount <= generateProperties.getSyncThreshold()) { |
| | | MesProBagCodeBatchGenerator.GenerateResult result = runSyncGenerate(spec); |
| | | return new MesProBagCodeGenerateRespVO() |
| | | .setUuid(uuid) |
| | | .setTotalCount(bagCodes.size()) |
| | | .setPalletIds(List.of()); |
| | | .setUuid(spec.uuid()) |
| | | .setTotalCount(result.finishedCount()) |
| | | .setPalletCount(0) |
| | | .setPalletIds(List.of()) |
| | | .setAsync(false); |
| | | } |
| | | MesProBagCodeGenerateTaskDO task = generateTaskService.submit(spec, generateReqVO.getRequestId()); |
| | | return new MesProBagCodeGenerateRespVO() |
| | | .setUuid(task.getUuid()) |
| | | .setTotalCount(task.getTotalCount()) |
| | | .setPalletCount(task.getPalletCount()) |
| | | .setAsync(true) |
| | | .setTaskId(task.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 忥çæï¼å¤±è´¥æ¶æ¸
çå·²åå
¥çåæ®µï¼é¿å
çä¸ä¸å®æ´çæ°æ®å
|
| | | */ |
| | | private MesProBagCodeBatchGenerator.GenerateResult runSyncGenerate(MesProBagCodeGenerateSpec spec) { |
| | | try { |
| | | return batchGenerator.generate(spec, null, new AtomicBoolean(false)); |
| | | } catch (RuntimeException e) { |
| | | try { |
| | | generateCleaner.cleanup(spec.uuid()); |
| | | } catch (Exception cleanupException) { |
| | | log.error("[runSyncGenerate][çæç» {} åæåæ¸
ç失败]", spec.uuid(), cleanupException); |
| | | } |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå次çæè¢ç æ°éä¸è¶
è¿ä¸é |
| | | */ |
| | | private void assertGenerateCountAllowed(long totalCount) { |
| | | int maxCount = generateProperties.getMaxTaskCount(); |
| | | if (totalCount > maxCount) { |
| | | throw exception(PRO_BAG_CODE_GENERATE_COUNT_TOO_MANY, maxCount); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeReplaceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO; |
| | | import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; |
| | | import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | |
| | | private final MesProBatchMapper batches = mock(MesProBatchMapper.class); |
| | | private final MesProBagCodeReplaceMapper replacements = mock(MesProBagCodeReplaceMapper.class); |
| | | private final MdmUnitMeasureApi units = mock(MdmUnitMeasureApi.class); |
| | | private final MesProBagCodeChunkWriter writer = mock(MesProBagCodeChunkWriter.class); |
| | | private final MesProBagCodeBatchGenerator generator = new MesProBagCodeBatchGenerator(); |
| | | private final MesProBagCodeGenerateProperties properties = new MesProBagCodeGenerateProperties(); |
| | | |
| | | @BeforeEach void setup() { |
| | | ReflectionTestUtils.setField(service, "bagCodeMapper", bags); |
| | |
| | | ReflectionTestUtils.setField(service, "batchMapper", batches); |
| | | ReflectionTestUtils.setField(service, "replaceMapper", replacements); |
| | | ReflectionTestUtils.setField(service, "unitMeasureApi", units); |
| | | ReflectionTestUtils.setField(service, "batchGenerator", generator); |
| | | ReflectionTestUtils.setField(service, "generateProperties", properties); |
| | | ReflectionTestUtils.setField(service, "generateCleaner", mock(MesProBagCodeGenerateCleaner.class)); |
| | | ReflectionTestUtils.setField(service, "generateTaskService", mock(MesProBagCodeGenerateTaskService.class)); |
| | | // åæ®µæ§è¡å¨ä½¿ç¨çå®å®ç°ï¼ä»
æäºå¡åå
¥å¨ä¸ Redis åç æ¿æ¢ä¸º mock |
| | | ReflectionTestUtils.setField(generator, "bagCodeMapper", bags); |
| | | ReflectionTestUtils.setField(generator, "taskMapper", mock(MesProBagCodeGenerateTaskMapper.class)); |
| | | ReflectionTestUtils.setField(generator, "codeRedisDao", codes); |
| | | ReflectionTestUtils.setField(generator, "chunkWriter", writer); |
| | | ReflectionTestUtils.setField(generator, "properties", properties); |
| | | doAnswer(invocation -> { |
| | | bags.insertBatch(invocation.getArgument(0)); |
| | | return null; |
| | | }).when(writer).insertBagCodes(anyList()); |
| | | doAnswer(invocation -> { |
| | | List<MesProPalletDO> pallets = invocation.getArgument(0); |
| | | for (int i = 0; i < pallets.size(); i++) { |
| | | pallets.get(i).setId((long) (i + 1)); |
| | | } |
| | | return null; |
| | | }).when(writer).insertPallets(anyList()); |
| | | |
| | | when(items.validateItemExists(1L)).thenReturn(new MesMdmItemDO().setId(1L).setUnitMeasureId(2L)); |
| | | AtomicLong sequence = new AtomicLong(1000000000000000L); |
| | | when(codes.generate()).thenAnswer(invocation -> String.valueOf(sequence.incrementAndGet())); |
| | | when(codes.generateBatch(anyInt())).thenAnswer(invocation -> { |
| | | int count = invocation.getArgument(0); |
| | | List<String> codes = new ArrayList<>(count); |
| | | for (int i = 0; i < count; i++) { |
| | | codes.add(String.valueOf(sequence.incrementAndGet())); |
| | | } |
| | | return codes; |
| | | }); |
| | | when(codes.allocateBagNoStart(anyInt(), any())).thenReturn(1); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Test @SuppressWarnings({"unchecked", "rawtypes"}) void automaticPalletCarriesVariety() { |
| | | var pallets=mock(cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper.class); |
| | | ReflectionTestUtils.setField(service,"palletMapper",pallets); |
| | | when(units.getUnitMeasure(2L)).thenReturn(CommonResult.success(new MdmUnitMeasureRespDTO().setType("2åå
"))); |
| | | when(units.getUnitMeasure(2L)).thenReturn(CommonResult.success(new MdmUnitMeasureRespDTO().setType("48è¢"))); |
| | | var req=new MesProBagCodeGenerateReqVO();req.setItemId(1L);req.setPalletCount(1); |
| | | service.generateBagCodes(req); |
| | | ArgumentCaptor<Collection<cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO>> saved=ArgumentCaptor.forClass((Class)Collection.class); |
| | | verify(pallets).insertBatch(saved.capture());assertEquals(1L,saved.getValue().iterator().next().getItemId()); |
| | | ArgumentCaptor<List<MesProPalletDO>> saved=ArgumentCaptor.forClass((Class)List.class); |
| | | verify(writer).insertPallets(saved.capture());assertEquals(1L,saved.getValue().iterator().next().getItemId()); |
| | | } |
| | | |
| | | @Test void replacementIssuesNewPairInsteadOfReusingOldBusinessCode() { |
| | |
| | | # ä¸´æ¶ ZIP ç®å½ï¼ç空åä½¿ç¨ ${java.io.tmpdir}/mes-qr-export |
| | | temp-dir: '' |
| | | |
| | | # è¢ç æ¹éçæï¼å°æ¹é忥宿ï¼å¤§æ¹é转åå°ä»»å¡å¹¶å段è½åº |
| | | bag-code: |
| | | generate: |
| | | # 忥/åå°åç线ï¼è¢ç æ°ï¼ãä¸è¶
è¿å请æ±çº¿ç¨å
宿 |
| | | sync-threshold: 20000 |
| | | # æ¯æ®µè¢ç /æ¯æ¹åç æ°é |
| | | chunk-size: 5000 |
| | | # åæ¶å
许è¿è¡çåå°çæä»»å¡æ° |
| | | max-concurrent-tasks: 1 |
| | | # 忬¡çæè¢ç ä¸é |
| | | max-task-count: 1000000 |
| | | # å个åå°ä»»å¡æé¿è¿è¡æ¶é´ï¼åéï¼ï¼è¶
æ¶èªå¨ç»æ¢å¹¶æ¸
ç |
| | | max-runtime-minutes: 30 |
| | | # å端轮询建议é´éï¼ç§ï¼ |
| | | poll-interval-seconds: 2 |
| | | # åæåæ¸
çæ¯è½®å é¤è¡æ° |
| | | cleanup-batch-size: 5000 |
| | | |
| | | spring: |
| | | application: |
| | | name: yudao-server |