private CallableStatement cstm = null;

本文探讨了用于执行SQL存储过程的接口及其在不同RDBMS中的通用语法,包括参数类型、设置值的方法以及返回结果的方式。重点介绍了如何通过JDBC API调用存储过程,并在执行前后处理结果集和更新计数。

The interface used to execute SQL stored procedures. The JDBC API provides a stored procedure SQL escape syntax that allows stored procedures to be called in a standard way for all RDBMSs. This escape syntax has one form that includes a result parameter and one that does not. If used, the result parameter must be registered as an OUT parameter. The other parameters can be used for input, output or both. Parameters are referred to sequentially, by number, with the first parameter being 1.

   {?= call <procedure-name>[<arg1>,<arg2>, ...]}
   {call <procedure-name>[<arg1>,<arg2>, ...]}
 

IN parameter values are set using the set methods inherited from PreparedStatement. The type of all OUT parameters must be registered prior to executing the stored procedure; their values are retrieved after execution via the get methods provided here.

A CallableStatement can return one ResultSet object or multiple ResultSet objects. Multiple ResultSet objects are handled using operations inherited from Statement.

For maximum portability, a call's ResultSet objects and update counts should be processed prior to getting the values of output parameters.

See Also:
Connection.prepareCall(java.lang.String), ResultSet
package com.taixin.dvbc2; import com.google.gson.Gson; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.net.*; import java.sql.*; import java.util.*; import java.util.concurrent.*; /** * SSE + Servlet 3.0 异步版本(含 PMT/ES 细节、入库去重) * - 每个 (ip,port) 唯一一个 ChannelWorker * - 多个订阅者共享该 worker 的统计输出 * - 输出字段:name/no/rate + pmt/vid/vtype/a1/a1t/a2/a2t * - 仅当节目结构或PID/类型变化时触发 SaveProgramData(签名去重) */ public class LoadProgramServlet extends HttpServlet { private static final Gson GSON = new Gson(); // 推送间隔(纳秒):8 秒 private static final long REPORT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(8); // 心跳间隔:15 秒(SSE 注释行) private static final long HEARTBEAT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(15); // 所有工作线程:Key -> ChannelWorker private static final Map<ChannelKey, ChannelWorker> WORKERS = new ConcurrentHashMap<>(); // ===== Servlet 入口 ===== @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { final String ip = req.getParameter("ip"); final String portStr = req.getParameter("port"); final String idStr = req.getParameter("id"); final String inputChannelName = req.getParameter("name"); if (ip == null || portStr == null) { resp.setStatus(400); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().write("{\"error\":\"缺少参数 ip 或 port\"}"); return; } final int port = Integer.parseInt(portStr); final Integer inputChannelId = (idStr == null || idStr.trim().isEmpty()) ? null : Integer.valueOf(idStr); final ChannelKey key = new ChannelKey(ip, port); // 设置 SSE 头 resp.setStatus(200); resp.setContentType("text/event-stream;charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Cache-Control", "no-cache"); resp.setHeader("Connection", "keep-alive"); resp.setHeader("X-Accel-Buffering", "no"); // 开启异步 final AsyncContext ac = req.startAsync(req, resp); ac.setTimeout(0); // 永不超时 final PrintWriter writer = resp.getWriter(); final Subscriber sub = new Subscriber(writer, ac); // 获取/创建 worker,并登记订阅者 ChannelWorker worker = WORKERS.compute(key, (k, w) -> { if (w == null || !w.isAliveWorker()) { ChannelWorker nw = new ChannelWorker( k, REPORT_INTERVAL_NS, HEARTBEAT_INTERVAL_NS, inputChannelId, inputChannelName ); try { nw.start(); } catch (Exception e) { safeSendSSE(sub, "{\"error\":\"通道启动失败: " + e.getMessage() + "\"}"); completeQuietly(ac); return null; } w = nw; } else { // 已有 worker,补齐一次元信息(为空时才设置,避免覆盖) w.setChannelMetaIfEmpty(inputChannelId, inputChannelName); } w.addSubscriber(sub); return w; }); if (worker == null) { return; // 上面已通知错误 } // 监听清理 ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) { cleanup(); } @Override public void onTimeout(AsyncEvent event) { cleanup(); } @Override public void onError(AsyncEvent event) { cleanup(); } @Override public void onStartAsync(AsyncEvent event) {} private void cleanup() { ChannelWorker w = WORKERS.get(key); if (w != null) { w.removeSubscriber(sub); if (w.subscriberCount() == 0) { w.stopWorker(); WORKERS.remove(key); } } } }); // 立即发一条“已连接” safeSendSSE(sub, "{\"status\":\"connected\"}"); } // ====== SSE 订阅者对象 ====== static final class Subscriber { final PrintWriter writer; final AsyncContext ac; volatile long lastHeartbeatNs = System.nanoTime(); Subscriber(PrintWriter writer, AsyncContext ac) { this.writer = writer; this.ac = ac; } synchronized void sendData(String json) { if (writer.checkError()) { complete(); return; } writer.write("data: " + json + "\n\n"); // SSE 以空行分隔 writer.flush(); if (writer.checkError()) { complete(); } } synchronized void heartbeatIfNeeded(long now, long heartbeatIntervalNs) { if (now - lastHeartbeatNs < heartbeatIntervalNs) return; if (writer.checkError()) { complete(); return; } writer.write(": keepalive\n\n"); writer.flush(); lastHeartbeatNs = now; if (writer.checkError()) { complete(); } } synchronized void complete() { completeQuietly(ac); } } // ====== 唯一标识每个组播通道 ====== static final class ChannelKey { final String ip; final int port; ChannelKey(String ip, int port) { this.ip = ip; this.port = port; } @Override public boolean equals(Object o) { if (!(o instanceof ChannelKey)) return false; ChannelKey that = (ChannelKey)o; return this.port == that.port && this.ip.equals(that.ip); } @Override public int hashCode() { return ip.hashCode() * 31 + port; } @Override public String toString() { return ip + ":" + port; } } // ====== 每个通道一个工作线程:接收 TS、解析 PSI、聚合速率、广播 ====== static final class ChannelWorker extends Thread { private final ChannelKey key; private final long reportIntervalNs; private final long heartbeatIntervalNs; private final Set<Subscriber> subscribers = ConcurrentHashMap.newKeySet(); // —— 请求元数据(来自 doGet)—— private volatile Integer inputChannelId; // InputChannelId private volatile String inputChannelName; // InputChannelName // —— 每个通道独有的解析状态(不要 static)—— private final Map<Integer, Integer> progToPmtPid = new HashMap<>(); // program -> PMT PID private final Map<Integer, String> programNames = new HashMap<>(); // program -> name(优先 SDT) private final Map<Integer, Integer> pidToProgram = new HashMap<>(); // pid -> program(用于速率归集) private final Map<Integer, Long> pidBytesCounter = new HashMap<>(); // pid -> bytes since last report private final Map<Integer, SectionAssembler> assemblers = new HashMap<>(); // 新增:每个节目下的 ES 列表(带类型/编码) private final Map<Integer, List<EsInfo>> programEsInfo = new HashMap<>(); // 去重签名:记录“最近一次已保存”的结构快照(字符串签名) private volatile String lastSavedSignature = null; // —— ES 描述结构 —— static final class EsInfo { final int pid; final int streamType; final String kind; // video / audio / other final String codec; // H.264/HEVC/AAC/AC-3 等 EsInfo(int pid, int st, String kind, String codec) { this.pid = pid; this.streamType = st; this.kind = kind; this.codec = codec; } } private volatile boolean running = true; private MulticastSocket sock; private InetAddress group; ChannelWorker(ChannelKey key, long reportIntervalNs, long heartbeatIntervalNs, Integer inputChannelId, String inputChannelName) { super("ChannelWorker-" + key); this.key = key; this.reportIntervalNs = reportIntervalNs; this.heartbeatIntervalNs = heartbeatIntervalNs; this.inputChannelId = inputChannelId; this.inputChannelName = inputChannelName; setDaemon(true); } // 复用已存在的 worker 时补齐元信息 void setChannelMetaIfEmpty(Integer id, String name) { if (this.inputChannelId == null && id != null) this.inputChannelId = id; if ((this.inputChannelName == null || this.inputChannelName.isEmpty()) && name != null) { this.inputChannelName = name; } } boolean isAliveWorker() { return running && isAlive(); } void addSubscriber(Subscriber s) { subscribers.add(s); } void removeSubscriber(Subscriber s) { subscribers.remove(s); } int subscriberCount() { return subscribers.size(); } void stopWorker() { running = false; try { if (sock != null) sock.close(); } catch (Throwable ignore) {} } @Override public void run() { try { sock = new MulticastSocket(key.port); group = InetAddress.getByName(key.ip); sock.joinGroup(group); final int MTU = 7 * 188; byte[] buf = new byte[MTU]; DatagramPacket pkt = new DatagramPacket(buf, buf.length); long lastReport = System.nanoTime(); while (running) { if (subscribers.isEmpty()) { Thread.sleep(80); continue; } sock.receive(pkt); int len = pkt.getLength(); int off = 0; while (off + 188 <= len) { parseTsPacket(buf, off); off += 188; } long now = System.nanoTime(); // 心跳 for (Subscriber s : subscribers) s.heartbeatIfNeeded(now, heartbeatIntervalNs); // 定时汇报 if (now - lastReport >= reportIntervalNs) { String json = buildProgramReportJson(reportIntervalNs); if (json != null) broadcast(json); lastReport = now; } } } catch (SocketException ignore) { // close() 时会来这 } catch (Throwable t) { broadcast("{\"error\":\"worker-crashed: " + safeMsg(t) + "\"}"); } finally { try { if (group != null && sock != null) sock.leaveGroup(group); } catch (Throwable ignore) {} try { if (sock != null) sock.close(); } catch (Throwable ignore) {} for (Subscriber s : subscribers) completeQuietly(s.ac); subscribers.clear(); } } private void broadcast(String json) { for (Subscriber s : subscribers) s.sendData(json); } // === TS/PSI 解析 === private void parseTsPacket(byte[] buf, int off) { if ((buf[off] & 0xFF) != 0x47) return; // sync int tei = (buf[off + 1] & 0x80) >>> 7; int pusi = (buf[off + 1] & 0x40) >>> 6; int pid = ((buf[off + 1] & 0x1F) << 8) | (buf[off + 2] & 0xFF); int afc = (buf[off + 3] & 0x30) >>> 4; if (tei == 1) return; if (pid != 0x1FFF) { Long old = pidBytesCounter.get(pid); pidBytesCounter.put(pid, (old == null ? 0L : old) + 188L); } int i = off + 4; if (afc == 2) return; // 只有 AF,无 payload if (afc == 3) { // 跳过 AF int afl = buf[i] & 0xFF; i += 1 + afl; } if (i >= off + 188) return; // 只重组 PAT/PMT/SDT if (pid == 0x0000 || pid == 0x0011 || containsValue(progToPmtPid, pid)) { SectionAssembler sa = assemblers.get(pid); if (sa == null) { sa = new SectionAssembler(); assemblers.put(pid, sa); } sa.push(buf, i, off + 188 - i, pusi == 1, (buf[off + 3] & 0x0F)); while (sa.hasSection()) { byte[] sec = sa.pollSection(); parseSection(pid, sec); } } } private boolean containsValue(Map<Integer, Integer> map, int val) { for (Integer v : map.values()) if (v != null && v == val) return true; return false; } private void parseSection(int pid, byte[] sec) { if (sec.length < 3) return; int tableId = sec[0] & 0xFF; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); if (sectionLength + 3 != sec.length) return; if (pid == 0x0000 && tableId == 0x00) { parsePAT(sec); } else if (pid == 0x0011 && (tableId == 0x42 || tableId == 0x46)) { parseSDT(sec); } else if (tableId == 0x02) { parsePMT(sec); } } private void parsePAT(byte[] sec) { int pos = 8, end = sec.length - 4; while (pos + 4 <= end) { int programNumber = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int pmtPid = ((sec[pos + 2] & 0x1F) << 8) | (sec[pos + 3] & 0xFF); pos += 4; if (programNumber == 0) continue; progToPmtPid.put(programNumber, pmtPid); pidToProgram.put(pmtPid, programNumber); } } private void parsePMT(byte[] sec) { int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); // 占位名(后续由 SDT 覆盖) if (!programNames.containsKey(programNumber)) { programNames.put(programNumber, "Program " + programNumber); } int progInfoLen = ((sec[10] & 0x0F) << 8) | (sec[11] & 0xFF); int pos = 12 + progInfoLen; int end = sec.length - 4; List<EsInfo> list = new ArrayList<>(); while (pos + 5 <= end) { int streamType = sec[pos] & 0xFF; int esPid = ((sec[pos + 1] & 0x1F) << 8) | (sec[pos + 2] & 0xFF); int esInfoLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); pos += 5; // ES 描述符区域 byte[] esDesc = null; if (esInfoLen > 0 && pos + esInfoLen <= end) { esDesc = Arrays.copyOfRange(sec, pos, pos + esInfoLen); } pos += esInfoLen; // 归属 program,便于速率归集 pidToProgram.put(esPid, programNumber); // 识别类型/编码 String[] cls = classifyStream(streamType, esDesc); list.add(new EsInfo(esPid, streamType, cls[0], cls[1])); } programEsInfo.put(programNumber, list); } private void parseSDT(byte[] sec) { int pos = 11, end = sec.length - 4; while (pos + 5 <= end) { int serviceId = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int descriptorsLoopLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); int descPos = pos + 5; int descEnd = descPos + descriptorsLoopLen; String name = null; while (descPos + 2 <= descEnd && descEnd <= end) { int tag = sec[descPos] & 0xFF; int len = sec[descPos + 1] & 0xFF; if (descPos + 2 + len > descEnd) break; if (tag == 0x48 && len >= 5) { // service_descriptor int base = descPos + 2; int provLen = sec[base + 1] & 0xFF; int nameLenPos = base + 2 + provLen; if (nameLenPos < descPos + 2 + len) { int nameLen = sec[nameLenPos] & 0xFF; int nameStart = nameLenPos + 1; int nameEnd = Math.min(nameStart + nameLen, descPos + 2 + len); byte[] nameBytes = Arrays.copyOfRange(sec, nameStart, nameEnd); name = decodeDvbString(nameBytes); } } descPos += 2 + len; } if (name != null && !name.isEmpty()) { programNames.put(serviceId, name); } pos = descEnd; } } // === 码率汇总 + JSON 输出(含“变更才保存”) === private String buildProgramReportJson(long intervalNs) { if (pidBytesCounter.isEmpty()) return null; Map<Integer, Long> programBytes = new HashMap<>(); for (Map.Entry<Integer, Long> e : pidBytesCounter.entrySet()) { int pid = e.getKey(); long bytes = e.getValue(); Integer prog = pidToProgram.get(pid); if (prog != null) { Long old = programBytes.get(prog); programBytes.put(prog, (old == null ? 0L : old) + bytes); } } pidBytesCounter.clear(); if (programBytes.isEmpty()) return null; List<Map<String, Object>> programs = new ArrayList<>(); for (Map.Entry<Integer, Long> e : programBytes.entrySet()) { int program = e.getKey(); long bytes = e.getValue(); double mbps = (bytes * 8.0) / (intervalNs / 1e9) / 1_000_000.0; String name = programNames.get(program); if (name == null) name = "(解析中…)"; Integer pmtPid = progToPmtPid.get(program); EsInfo v = null, a1 = null, a2 = null; List<EsInfo> list = programEsInfo.get(program); if (list != null) { for (EsInfo es : list) { if ("video".equals(es.kind)) { if (v == null) v = es; } else if ("audio".equals(es.kind)) { if (a1 == null) a1 = es; else if (a2 == null) a2 = es; } } } Map<String, Object> one = new LinkedHashMap<>(); one.put("name", name); one.put("no", program); one.put("rate", mbps); one.put("pmt", pmtPid == null ? null : pmtPid); one.put("vid", v == null ? null : v.pid); one.put("vtype", v == null ? null : v.codec); one.put("a1", a1 == null ? null : a1.pid); one.put("a1t", a1 == null ? null : a1.codec); one.put("a2", a2 == null ? null : a2.pid); one.put("a2t", a2 == null ? null : a2.codec); programs.add(one); // ==== 入库去重:仅当签名变化时才保存 ==== // 只保存“首个节目”(你的需求:一个输入通道只有一个节目) final String signature = makeSignature( name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid), (a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid), (a2 == null ? null : a2.codec) ); // System.out.println(signature); // 比较并保存 if (lastSavedSignature == null || !signature.equals(lastSavedSignature)) { lastSavedSignature = signature; // 先更新,避免并发重复 try { SaveProgramData( this.inputChannelId, this.inputChannelName, this.key.ip, this.key.port, name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid),(a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid),(a2 == null ? null : a2.codec) ); } catch (Exception ex) { // 保存失败不影响 SSE System.err.println("[SaveProgramData][ERROR] " + ex.getMessage()); } } // 一个输入通道只有一个节目——只处理第一个 break; } return GSON.toJson(programs); } // 生成签名(只关注结构/类型/PID变化,速率不参与) private static String makeSignature(String name, Integer progNo, Integer pmt, Integer vid, String vtype, Integer a1, String a1t, Integer a2, String a2t) { return String.valueOf( Objects.hash( safe(name), progNo, pmt, vid, safe(vtype), a1, safe(a1t), a2, safe(a2t) ) ); } private static String safe(String s) { return (s == null ? "" : s); } // === stream_type + 描述符 → 类型/编码 === private static String[] classifyStream(int streamType, byte[] esDesc) { switch (streamType & 0xFF) { case 0x01: return new String[]{"video", "MPEG-1 Video"}; case 0x02: return new String[]{"video", "MPEG-2 Video"}; case 0x10: return new String[]{"video", "MPEG-4 Visual"}; case 0x1B: return new String[]{"video", "H.264/AVC"}; case 0x24: return new String[]{"video", "HEVC/H.265"}; case 0x03: return new String[]{"audio", "MPEG-1 Layer II"}; case 0x04: return new String[]{"audio", "MPEG-2 Audio"}; case 0x0F: return new String[]{"audio", "AAC"}; case 0x11: return new String[]{"audio", "LATM/LOAS AAC"}; case 0x06: if (hasDescriptor(esDesc, (byte)0x6A)) return new String[]{"audio", "AC-3"}; if (hasDescriptor(esDesc, (byte)0x7A)) return new String[]{"audio", "E-AC-3"}; if (hasDescriptor(esDesc, (byte)0x7B)) return new String[]{"audio", "DTS"}; return new String[]{"other", "Private data"}; default: return new String[]{"other", String.format("stream_type 0x%02X", streamType & 0xFF)}; } } private static boolean hasDescriptor(byte[] esDesc, byte tag) { if (esDesc == null) return false; int i = 0, n = esDesc.length; while (i + 2 <= n) { int t = esDesc[i] & 0xFF; int l = esDesc[i+1] & 0xFF; if (i + 2 + l > n) break; if ((byte)t == tag) return true; i += 2 + l; } return false; } // === DVB 字符串解码 === private static String decodeDvbString(byte[] bs) { if (bs == null || bs.length == 0) return ""; int offset = 0; String charset = "GB18030"; int first = bs[0] & 0xFF; if (first == 0x10 && bs.length >= 3) { charset = "UTF-16BE"; offset = 1; } else if (first == 0x15 || first == 0x14) { charset = "GB2312"; offset = 1; } else if (first == 0x1F) { charset = "UTF-8"; offset = 1; } else if (first < 0x20) { offset = 1; } try { return new String(bs, offset, bs.length - offset, charset).trim(); } catch (Exception e) { try { return new String(bs, offset, bs.length - offset, "GB2312").trim(); } catch (Exception ignore) {} try { return new String(bs, offset, bs.length - offset, "UTF-8").trim(); } catch (Exception ignore) {} return ""; } } // === 安全的 section 重组器 === static final class SectionAssembler { private final ByteArrayOutputStream cur = new ByteArrayOutputStream(); private Integer expectedLen = null; private final Queue<byte[]> ready = new ArrayDeque<>(); void push(byte[] src, int off, int len, boolean payloadStart, int continuityCounter) { final int end = off + len; int i = off; if (payloadStart) { if (i >= end) return; int pointer = src[i] & 0xFF; i += 1; if (i + pointer > end) return; i += pointer; startNew(); } while (i < end) { int remaining = end - i; if (expectedLen == null) { if (remaining < 3) { write(src, i, remaining); return; } int sl = ((src[i+1] & 0x0F) << 8) | (src[i+2] & 0xFF); expectedLen = sl + 3; // total including header } int need = expectedLen - cur.size(); if (need <= 0) { startNew(); continue; } int copy = Math.min(need, remaining); write(src, i, copy); i += copy; if (cur.size() == expectedLen) { ready.add(cur.toByteArray()); startNew(); } } } private void write(byte[] src, int off, int len) { if (len > 0) cur.write(src, off, len); } boolean hasSection() { return !ready.isEmpty(); } byte[] pollSection() { return ready.poll(); } private void startNew() { cur.reset(); expectedLen = null; } } } // ====== 工具方法 ====== private static void safeSendSSE(Subscriber s, String json) { try { s.sendData(json); } catch (Throwable ignore) {} } private static void completeQuietly(AsyncContext ac) { try { ac.complete(); } catch (Throwable ignore) {} } private static String safeMsg(Throwable t) { String m = t.getMessage(); return (m == null ? t.getClass().getSimpleName() : m).replace("\"","'"); } // ====== 实际入库(你可以在此调用存储过程) ====== // 这里默认只打印,避免直接写库;你可以改为JDBC调用: // CallableStatement c = conn.prepareCall("{call dbo.sp_UpsertProgram_ByChannel(?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"); // ... set 参数 ... // c.execute(); static void SaveProgramData(Integer inputChannelId, String inputChannelName, String ip, int port, String name, Integer progNo, Integer pmtPid, Integer videoPid, String videoType, Integer audio1Pid, String audio1Type, Integer audio2Pid, String audio2Type) throws Exception { Properties props = new Properties(); try { InputStream inputStream = LoadProgramServlet.class.getClassLoader().getResourceAsStream("jdbc.properties"); if (inputStream != null) { props.load(inputStream); String DB_URL = props.getProperty("jdbc.url"); String DB_USER = props.getProperty("jdbc.username"); String DB_PASSWORD = props.getProperty("jdbc.password"); // 注册数据库驱动 Class.forName(props.getProperty("jdbc.driverClassName")); Connection c = null; CallableStatement cs = null; try { c = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD); String sql = "{call sp_SaveProgramData(?, ?, ?, ?, ?,?,?, ?, ?, ?, ?,?,?)}"; // 调用存储过程 cs = c.prepareCall(sql); // 设置存储过程参数 cs.setString(1, inputChannelName); cs.setInt(2,port); cs.setString(3, ip); cs.setString(4, name); // 当 out_mod 为 256 时 cs.setInt(5, progNo); // 当 out_mod 为 256 时 cs.setInt(6, pmtPid); // 当 out_mod 为 256 时 cs.setInt(7, videoPid); // 当 out_mod 为 256 时 cs.setString(8, videoType); // 当 out_mod 为 256 时 cs.setInt(9, audio1Pid == null ? 0 : audio1Pid); cs.setString(10, audio1Type == null ? "-" : audio1Type); cs.setInt(11, audio2Pid == null ? 0 : audio2Pid); cs.setString(12, audio2Type == null ? "-" : audio2Type); cs.registerOutParameter(13, Types.INTEGER); // @ReturnValue cs.execute(); // 获取输出参数 int returnValue = cs.getInt(13); // 读取 @ReturnValue if (returnValue == 1) { // 插入成功 // System.out.println("保存"+name+"成功"); } else { } } catch (SQLException e) { } finally { // 资源释放 try { if (cs != null) cs.close(); } catch (Exception ignore) { } try { if (c != null) c.close(); } catch (Exception ignore) { } } } else { } } catch (Exception e) { throw new ServletException("Failed to load database properties", e); } } } function getRateMbps(p) { if (typeof p?.rate === 'number' && isFinite(p.rate)) return p.rate; if (typeof p?.rate === 'string') { const n = parseFloat(p.rate); return isFinite(n) ? n : 0; } return 0; } function formatRate(mbps) { if (!isFinite(mbps) || mbps <= 0) return '0'; if (mbps >= 1) return (mbps).toFixed(2) + ' M'; if (mbps >= 0.001) return (mbps * 1000).toFixed(2) + ' K'; return Math.round(mbps * 1e6) + ' bps'; } // 给每个 group 挂一个 es 实例 & 正在解析标记 function startParseForGroup(group) { // 已经自动启动过并且连接还活着,直接返回,避免重复创建 if (group._autostarted && group.es && group.es.readyState === 1) { return; } group._autostarted = true; // 已有连接先关(防止旧连接残留) if (group.es) { try { group.es.close(); } catch (_) {} group.es = null; } group.parsing = true; // ⚠️ 确认你的后端路径是否真叫 loadPramgermServlet,别拼错了 const url = `loadPramgermServlet?ip=${encodeURIComponent(group.ip)}&port=${encodeURIComponent(group.port)}&id=${encodeURIComponent(group.id)}&name=${encodeURIComponent(group.name)}`; const es = new EventSource(url); group.es = es; es.onmessage = (event) => { try { const data = JSON.parse(event.data); if (!Array.isArray(data)) return; group.programs = data.map(p => ({ name: p.name, no: p.no, rate: Number(p.rate) || 0, pmt: p.pmt ?? '-', vid: p.vid ?? '-', vtype: p.vtype ?? '-', a1: p.a1 ?? '-', a1t: p.a1t ?? '-', a2: p.a2 ?? '-', a2t: p.a2t ?? '-' })); group.parsing = false; renderTree(); } catch (e) { console.error('解析 SSE 数据失败:', e, event.data); } }; es.onerror = (err) => { console.error('SSE 连接出错', err); group.parsing = false; try { es.close(); } catch (_) {} group.es = null; // 失败后允许再次自动启动 group._autostarted = false; }; } // (可选)为了节省资源,开始解析前关闭其它通道的 SSE function stopOtherParsers(exceptId) { groups.forEach(g => { if (String(g.id) !== String(exceptId) && g.es) { try { g.es.close(); } catch (_) {} g.es = null; g.parsing = false; } }); } // ✅ 按钮事件:只启动选中通道的解析,且只更新该通道 document.getElementById('btnParse').addEventListener('click', () => { const selectedGroup = groups.find(g => g.selected); if (!selectedGroup) { alert('请先选择一个输入通道'); return; } // // 可选:关掉其它通道的 SSE,避免多路同时开 // stopOtherParsers(selectedGroup.id); // 启动这个通道的解析 startParseForGroup(selectedGroup); }); 如果我有好几百个通道 一个通道一个节目100多个通道 我需要每个都展示速率 怎么才能坐到 而且不崩
09-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值