package org.yzh.web.service; import io.netty.buffer.ByteBuf; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; import org.yzh.commons.util.DateUtils; import org.yzh.commons.util.Exceptions; import org.yzh.commons.util.IOUtils; import org.yzh.protocol.jsatl12.DataPacket; import org.yzh.protocol.jsatl12.T1210; import org.yzh.protocol.jsatl12.T1211; import org.yzh.protocol.t808.T0200; import org.yzh.protocol.t808.T0801; import org.yzh.web.config.JTProperties; import org.yzh.web.model.entity.DeviceDO; import org.yzh.web.model.enums.SessionKey; import java.io.*; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.*; /** * @author yezhihao * https://gitee.com/yezhihao/jt808-server */ @Slf4j @Service public class FileService { @Resource private JTProperties jtProperties; private static final Comparator comparator = Comparator.comparingLong((long[] a) -> a[0]).thenComparingLong(a -> a[1]); private String getDir(T1210 alarmId) { StringBuilder sb = new StringBuilder(80); sb.append(jtProperties.getT9208().getPath()).append('/'); sb.append(alarmId.getClientId()).append('_'); DateUtils.yyMMddHHmmss.formatTo(alarmId.getDateTime(), sb); sb.append('_').append(alarmId.getSerialNo()).append('/'); return sb.toString(); } /** 创建报警目录及 附件列表日志 */ public void createDir(T1210 alarmId) { String dirPath = getDir(alarmId); new File(dirPath).mkdirs(); List items = alarmId.getItems(); StringBuilder fileList = new StringBuilder(items.size() * 50); fileList.append(dirPath).append('\n'); for (T1210.Item item : items) fileList.append(item.getName()).append('\t').append(item.getSize()).append('\n'); log.warn("文件列表: {}", fileList); Exceptions.ignore(() -> FileCopyUtils.copy(fileList.toString().getBytes(StandardCharsets.UTF_8), new File(dirPath, "fs.txt"))); } /** 将数据块写入到报警文件,并记录日志 */ public void writeFile(T1210 alarmId, DataPacket fileData) { String dir = getDir(alarmId); String name = dir + fileData.getName().trim(); int offset = fileData.getOffset(); int length = fileData.getLength(); byte[] buffer = ByteBuffer.allocate(8) .putInt(offset).putInt(length).array(); RandomAccessFile file = null; FileOutputStream filelog = null; ByteBuf data = fileData.getData(); try { file = new RandomAccessFile(name + ".tmp", "rw"); filelog = new FileOutputStream(name + ".log", true); data.readBytes(file.getChannel(), offset, data.readableBytes()); filelog.write(buffer); } catch (IOException e) { log.error("写入报警文件", e); } finally { IOUtils.close(file, filelog); } } public void writeFileSingle(T1210 alarmId, DataPacket fileData) { String dir = getDir(alarmId); String name = dir + fileData.getName().trim(); int offset = fileData.getOffset(); RandomAccessFile file = null; ByteBuf data = fileData.getData(); try { file = new RandomAccessFile(name, "rw"); data.readBytes(file.getChannel(), offset, data.readableBytes()); } catch (IOException e) { log.error("写入报警文件", e); } finally { IOUtils.close(file); } } /** 根据日志检查文件完整性,并返回缺少的数据块信息 */ public int[] checkFile(T1210 alarmId, T1211 fileInfo) { String dir = getDir(alarmId); File logFile = new File(dir + fileInfo.getName() + ".log"); byte[] bytes; FileInputStream in = null; try { in = new FileInputStream(logFile); bytes = new byte[in.available()]; in.read(bytes); } catch (FileNotFoundException e) { return null; } catch (IOException e) { log.error("检查文件完整性", e); return null; } finally { IOUtils.close(in); } int size = bytes.length / 8; long[][] items = new long[size + 2][2]; items[size + 1][0] = fileInfo.getSize(); ByteBuffer buffer = ByteBuffer.wrap(bytes); for (int i = 1; i <= size; i++) { items[i][0] = buffer.getInt(); items[i][1] = buffer.getInt(); } List result = new ArrayList<>(items.length); int len = items.length - 1; Arrays.sort(items, 1, len, comparator); for (int i = 0; i < len; ) { long a = items[i][0] + items[i][1]; long b = items[++i][0] - a; if (b > 0) { result.add((int) a); result.add((int) b); } } if (result.isEmpty()) { File file = new File(dir + fileInfo.getName() + ".tmp"); File dest = new File(dir + fileInfo.getName()); if (file.renameTo(dest)) { logFile.delete(); } return null; } return result.stream().filter(Objects::nonNull).mapToInt(i -> i).toArray(); } /** 多媒体数据上传 */ public boolean saveMediaFile(T0801 message) { DeviceDO device = message.getSession().getAttribute(SessionKey.Device); T0200 location = message.getLocation(); StringBuilder filename = new StringBuilder(32); filename.append(type(message.getType())).append('_'); DateUtils.yyMMddHHmmss.formatTo(location.getDeviceTime(), filename); filename.append('_'); filename.append(message.getChannelId()).append('_'); filename.append(message.getEvent()).append('_'); filename.append(message.getId()).append('.'); filename.append(suffix(message.getFormat())); String deviceId; if (device == null) deviceId = message.getClientId(); else deviceId = device.getDeviceId(); File dir = new File(jtProperties.getT0801().getPath() + '/' + deviceId); dir.mkdirs(); ByteBuf packet = message.getPacket(); FileOutputStream fos = null; try { fos = new FileOutputStream(new File(dir, filename.toString())); packet.readBytes(fos.getChannel(), 0, packet.readableBytes()); return true; } catch (IOException e) { log.error("多媒体数据保存失败", e); return false; } finally { IOUtils.close(fos); packet.release(); } } private static String type(int type) { return switch (type) { case 0 -> "image"; case 1 -> "audio"; case 2 -> "video"; default -> "unknown"; }; } private static String suffix(int format) { return switch (format) { case 0 -> "jpg"; case 1 -> "tif"; case 2 -> "mp3"; case 3 -> "wav"; case 4 -> "wmv"; default -> "bin"; }; } }