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