diff --git a/pom.xml b/pom.xml index 39247a32..fc2d0ab1 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,18 @@ ruoyi-common ${ruoyi.version} + + cn.hutool + hutool-all + 5.8.40 + + + + com.demo + dahua-sdk-demo + 1.0.0 + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyDeviceInfoController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyDeviceInfoController.java index 00e1bbc1..5a664223 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyDeviceInfoController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyDeviceInfoController.java @@ -2,16 +2,11 @@ package com.ruoyi.web.controller.system; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.system.service.DahuaSnapService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -34,6 +29,8 @@ public class NtzyDeviceInfoController extends BaseController @Autowired private INtzyDeviceInfoService ntzyDeviceInfoService; + private final DahuaSnapService service = + new DahuaSnapService("D:/capture/", "http://192.168.1.96:8080/capture/"); /** * 查询设备信息列表 */ @@ -101,4 +98,20 @@ public class NtzyDeviceInfoController extends BaseController { return toAjax(ntzyDeviceInfoService.deleteNtzyDeviceInfoByIds(ids)); } + + + /** + * 抓拍成功后保存图片文件 + * 抓拍成功后 + * 立即把 JPG 保存到本地 + * 把“文件访问 URI”返回给调用方——相当于“返回图片链接”效果。 + */ + @GetMapping("/snap") + public AjaxResult snap(@RequestParam String ip, + @RequestParam(defaultValue = "37777") int port, + @RequestParam String user, + @RequestParam String pwd){ + String url = service.snap(ip, port, user, pwd); + return success(url); + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyFileDownloadController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyFileDownloadController.java new file mode 100644 index 00000000..8f95a246 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/NtzyFileDownloadController.java @@ -0,0 +1,103 @@ +package com.ruoyi.web.controller.system; + +import cn.hutool.core.io.IoUtil; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.system.service.INtzyFileService; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +/** + * 录像拍照文件下载Controller + * + * @author ruoyi + * @date 2025-12-09 + */ +@RestController +@RequestMapping("/capture") +public class NtzyFileDownloadController extends BaseController +{ + @Autowired + private INtzyFileService ntzyFileService; + + /** + * 文件下载 + */ + @GetMapping("/**") + @Operation(summary = "下载文件") + public void getFileContent(HttpServletRequest request, HttpServletResponse response) throws Exception + { + // 获取请求的路径 + String path = StrUtil.subAfter(request.getRequestURI(), "/capture/", false); + if (StrUtil.isEmpty(path)) { + throw new IllegalArgumentException("结尾的 path 路径必须传递"); + } + // 解码,解决中文路径的问题 + path = URLUtil.decode(path); + // 读取内容 + byte[] content = readFileAsBytes("D:/capture/" + path); + if (content == null) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return; + } + writeAttachment(response, getFileName(path), content); + } + + /** + * 返回附件 + * + * @param response 响应 + * @param filename 文件名 + * @param content 附件内容 + */ + public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException { + // 设置 header 和 contentType + response.setContentType("image/jpeg"); + // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html + // 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论 + response.setHeader("Content-Disposition", "inline;filename=" + encode(filename)); + // 输出附件 + IoUtil.write(response.getOutputStream(), false, content); + } + + public static String encode(String raw) throws UnsupportedEncodingException { + return URLEncoder.encode(raw, "utf-8"); + } + + /** + * 从完整路径中提取文件名(含扩展名) + * @param path 路径字符串 + * @return 文件名;若路径为空或仅目录则返回 "" + */ + public static String getFileName(String path) { + if (path == null || path.isEmpty()) return ""; + return Paths.get(path).getFileName().toString(); + } + + /** + * 读取文件并返回 byte[] + * @param path 文件路径(相对或绝对) + * @return 文件全部字节 + * @throws IOException 读取失败时抛出 + */ + public static byte[] readFileAsBytes(String path) throws IOException { + Path p = Paths.get(path); + return Files.readAllBytes(p); + } + +} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index d15705c4..d546577f 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -90,6 +90,8 @@ spring: # token配置 token: + enable: false + anonymous: true # 令牌自定义标识 header: Authorization # 令牌密钥 @@ -97,6 +99,10 @@ token: # 令牌有效期(默认30分钟) expireTime: 30 +security: + excludes: + - /capture/** + # MyBatis配置 mybatis: # 搜索指定包别名 diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index b04beffb..b02caa1d 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -111,7 +111,7 @@ public class SecurityConfig .authorizeHttpRequests((requests) -> { permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - requests.antMatchers("/login", "/register", "/captchaImage").permitAll() + requests.antMatchers("/login", "/register", "/captchaImage", "/capture/**").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() @@ -120,7 +120,7 @@ public class SecurityConfig }) // 添加Logout filter .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)) - // 添加JWT filter +// // 添加JWT filter .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) // 添加CORS filter .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index ba8b1a8b..48efe281 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -22,7 +22,10 @@ com.ruoyi ruoyi-common - + + com.demo + dahua-sdk-demo + \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/DahuaSnapService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/DahuaSnapService.java new file mode 100644 index 00000000..9a0117e3 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/DahuaSnapService.java @@ -0,0 +1,104 @@ +package com.ruoyi.system.service; + +import com.netsdk.lib.NetSDKLib; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class DahuaSnapService { + +// static { +// // 1. 加载大华 SDK +// System.loadLibrary("dhnetsdk"); +// } + + private final NetSDKLib netsdk = NetSDKLib.NETSDK_INSTANCE; + private final String saveRoot; // 根目录 + private final String httpBaseUrl; // nginx 映射前缀 + + public DahuaSnapService(String saveRoot, String httpBaseUrl) { + this.saveRoot = saveRoot; + this.httpBaseUrl = httpBaseUrl; + netsdk.CLIENT_Init(null, null); // 初始化 + } + + /** + * 抓拍并返回“图片链接” + * @param ip 相机 IP + * @param port 37777 + * @param user admin + * @param pwd 密码 + * @return 可访问的 JPG 地址(http) + */ + public String snap(String ip, int port, String user, String pwd) { + // 2. 登录 + NetSDKLib.NET_DEVICEINFO_Ex deviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex(); + IntByReference nError = new IntByReference(0); + NetSDKLib.LLong loginId = netsdk.CLIENT_LoginEx2(ip, port, user, pwd, + NetSDKLib.EM_LOGIN_SPAC_CAP_TYPE.EM_LOGIN_SPEC_CAP_TCP, + null, deviceInfo, nError); + if (loginId.longValue() == 0) { + throw new RuntimeException("登录失败, error=" + netsdk.CLIENT_GetLastError()); + } + + // 3. 准备保存路径 + String cameraId = ip.replace(".", "-"); + File dir = new File(saveRoot + cameraId); + if (!dir.exists()) dir.mkdirs(); + String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".jpg"; + String localPath = dir.getAbsolutePath() + File.separator + fileName; + + // 4. 设置抓图回调(一次性,可放构造函数) + SnapRevCallBack snapCb = new SnapRevCallBack(localPath); + netsdk.CLIENT_SetSnapRevCallBack(snapCb, null); + + // 5. 请求抓拍 + NetSDKLib.SNAP_PARAMS params = new NetSDKLib.SNAP_PARAMS(); + params.Channel = 0; // 通道 0 + params.Quality = 6; // 画质 1-6 + params.ImageSize = 2; // 0-主码流 2-子码流 + params.mode = 0; + if (!netsdk.CLIENT_SnapPictureEx(loginId, params, null)) { + netsdk.CLIENT_Logout(loginId); + throw new RuntimeException("抓拍指令失败, error=" + netsdk.CLIENT_GetLastError()); + } + + // 6. 等待回调写完(简单 CountDownLatch 2s 超时) + try { snapCb.latch.await(2, TimeUnit.SECONDS); } + catch (InterruptedException e) { Thread.currentThread().interrupt(); } + + // 7. 登出 + netsdk.CLIENT_Logout(loginId); + + // 8. 返回可访问的 http 链接 + return httpBaseUrl + cameraId + "/" + fileName; + } + + /* ===================== 回调 ===================== */ + private static class SnapRevCallBack implements NetSDKLib.fSnapRev { + private final String savePath; + private final CountDownLatch latch = new CountDownLatch(1); + + SnapRevCallBack(String savePath) { this.savePath = savePath; } + + @Override + public void invoke(NetSDKLib.LLong lLoginID, Pointer pBuf, int revLen, + int encodeType, int cmdSerial, Pointer dwUser) { + if (pBuf == null || revLen <= 0) return; + // encodeType == 10 → JPG + try (FileOutputStream fos = new FileOutputStream(savePath)) { + fos.write(pBuf.getByteArray(0, revLen)); + latch.countDown(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/ruoyi-system/src/main/resources/lib/netsdk-1.0-demo.jar b/ruoyi-system/src/main/resources/lib/netsdk-1.0-demo.jar new file mode 100644 index 00000000..ea609eac Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/netsdk-1.0-demo.jar differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/ImageAlg.dll b/ruoyi-system/src/main/resources/lib/win-x64/ImageAlg.dll new file mode 100644 index 00000000..06c5c75e Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/ImageAlg.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/Infra.dll b/ruoyi-system/src/main/resources/lib/win-x64/Infra.dll new file mode 100644 index 00000000..19524ec3 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/Infra.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/IvsDrawer.dll b/ruoyi-system/src/main/resources/lib/win-x64/IvsDrawer.dll new file mode 100644 index 00000000..740af9d0 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/IvsDrawer.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/StreamConvertor.dll b/ruoyi-system/src/main/resources/lib/win-x64/StreamConvertor.dll new file mode 100644 index 00000000..4b0de983 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/StreamConvertor.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/avnetsdk.dll b/ruoyi-system/src/main/resources/lib/win-x64/avnetsdk.dll new file mode 100644 index 00000000..e97834aa Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/avnetsdk.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/dhconfigsdk.dll b/ruoyi-system/src/main/resources/lib/win-x64/dhconfigsdk.dll new file mode 100644 index 00000000..999b2c59 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/dhconfigsdk.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/dhnetsdk.dll b/ruoyi-system/src/main/resources/lib/win-x64/dhnetsdk.dll new file mode 100644 index 00000000..c8076281 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/dhnetsdk.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/libeay32.dll b/ruoyi-system/src/main/resources/lib/win-x64/libeay32.dll new file mode 100644 index 00000000..4fb41d28 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/libeay32.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/play.dll b/ruoyi-system/src/main/resources/lib/win-x64/play.dll new file mode 100644 index 00000000..bca234c0 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/play.dll differ diff --git a/ruoyi-system/src/main/resources/lib/win-x64/ssleay32.dll b/ruoyi-system/src/main/resources/lib/win-x64/ssleay32.dll new file mode 100644 index 00000000..6ebb5bc8 Binary files /dev/null and b/ruoyi-system/src/main/resources/lib/win-x64/ssleay32.dll differ