onvif-go(mickeyzzc/onvif-go v2)完整参考手册:从建工程到写出自己的 NVR 接入

简介: `onvif-go/v2` 是 MiBee 生态核心 ONVIF 协议库,同时支持客户端(发现/认证/取流/PTZ/事件)与服务端(虚拟相机),零依赖、Go 原生实现。已真机兼容海康、大华、Axis 等主流品牌及 ESP32 最小系统,含诊断工具链与完整示例,开箱即用。(239字)

onvif-go 是 MiBee 相机接入链路里管「连相机」的库:你的程序要用 ONVIF 去发现、认证、取流、控制一台摄像机,或者反过来把一个进程装成摄像机让 NVR 来连,都用它。它同时提供客户端和虚拟相机 server 两套角色,MiBeeNvr 的 ONVIF 接入就跑在这上面,兼容性矩阵(海康、Axis、大华、Bosch、Amcrest、HiSilicon OEM、ESP32 最小实现)是真机抓包喂出来的。

这篇按「能直接抄去用」的标准写:每个功能一节,节里是完整可运行的程序加逐段讲解,读完就能在自己的项目里落地把对应部分。代码来自仓库 README 和 examples,接口签名与仓库当前发布版核对过。

安装与工程准备

库本体零第三方依赖,Module 路径带 /v2 后缀,要求 Go 1.26 以上:

mkdir my-nvr && cd my-nvr
go mod init example.com/my-nvr
go get github.com/mickeyzzc/onvif-go/v2

工程里通常只需要 import "github.com/mickeyzzc/onvif-go/v2/onvif" 一个包——客户端门面、发现、错误哨兵都从它出发;发现和虚拟相机 server 分别在 discovery/ 和 server/ 子包,用到再引。

仓库还带四个命令行工具,发版时提供 linux/macOS/Windows 预编译二进制,写代码前先用它们探一探相机很省事:

go install github.com/mickeyzzc/onvif-go/v2/cmd/onvif-diagnostics@v2.2.0
onvif-diagnostics -host 192.168.1.100 -user admin -pass camera-password
  • discover:全网段 ONVIF 发现;
  • onvif-quick:一台相机的主要动作一次跑完;
  • onvif-diagnostics:一次扫十一个主要操作出 JSON 报告,README 明说「提 issue 前先跑这个」;
  • onvif-server:起一台虚拟相机。

五分钟跑通第一台相机

前置条件:一台开着 ONVIF 的相机。到相机 Web 管理界面把 ONVIF 服务打开,设一个独立密码(别用管理员密码);海康系相机还要留意系统时间对不对,认证一节会讲为什么。

完整程序:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/mickeyzzc/onvif-go/v2/onvif"
)

func main() {
   
    client, err := onvif.NewClient("192.168.1.100",
        onvif.WithCredentials("admin", "camera-password"),
        onvif.WithAutoClockSkew())
    if err != nil {
   
        log.Fatal(err)
    }
    if err := client.Initialize(context.Background()); err != nil {
   
        log.Fatal(err)
    }

    info, err := client.Device().GetDeviceInformation(context.Background())
    if err != nil {
   
        log.Fatal(err)
    }
    fmt.Printf("%s %s (fw %s)\n", info.Manufacturer, info.Model, info.FirmwareVersion)

    profiles, err := client.Media().GetProfiles(context.Background())
    if err != nil {
   
        log.Fatal(err)
    }
    mainToken := onvif.SelectMainProfile(profiles)
    uri, err := client.Media().GetStreamURI(context.Background(), mainToken)
    if err != nil {
   
        log.Fatal(err)
    }
    fmt.Println("主码流:", uri.URI)
}

跑起来(输出随相机型号不同):

Hikvision DS-2CD3T46 (fw 5.5.53)
主码流: rtsp://192.168.1.100:554/Streaming/Channels/101

逐段说:

  • NewClient 的第一个参数接受三种写法:完整 URL、IP:端口、裸 IP——后两种自动补成 /onvif/device_service。WithCredentials 给出凭据,WithAutoClockSkew 让客户端先无认证读一次设备时间、算出偏差再参与后续签名,海康系的 401 十有八九是它治的。
  • Initialize 做三件事:测时钟偏差、取 capabilities、解析各服务的 XAddr。第三件事很有用——不少相机在 capabilities 里报的是过期 IP,库会把错误地址修掉再存下来。
  • client.Device()、client.Media() 是 service facade:每个 ONVIF 服务一个长生命周期入口,还有 PTZ()、Imaging()、Events()、Media2()、Analytics()、DeviceIO()、Security()。
  • SelectMainProfile 不等于 profiles[0]:它按分辨率挑主码流,命名提示(main/主流/sub/辅流)做决胜,全都没分辨率信息时才回退第一个。这行的存在就是因为大量相机把子码流排在 profiles[0]。
  • uri.URI 是个 RTSP 地址。拿 VLC 或 ffplay 立刻验证:ffplay -rtsp_transport tcp "rtsp://192.168.1.100:554/Streaming/Channels/101"。

库在链路里的位置

mermaid diagram

左边是你写的程序,onvif-go 往下分两个角色:作为客户端向右连真相机;作为 server 向下当一台虚拟相机——测试 NVR 的发现、取流、PTZ 逻辑时不用插真机。两个角色共享同一套线格式代码,互为对方的第一批测试用例。

发现相机:主动、被动与定向

主动组播发现,同网段一发一收:

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/mickeyzzc/onvif-go/v2/discovery"
)

func main() {
   
    ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
    defer cancel()

    devices, err := discovery.Discover(ctx, 5*time.Second)
    if err != nil {
   
        log.Fatal(err)
    }
    for _, d := range discovery.FilterONVIFDevices(devices) {
   
        fmt.Printf("%-14s %-20s %s\n", d.GetName(), d.Hardware, d.GetDeviceEndpoint())
    }
}

FilterONVIFDevices 把 Synology、Windows、打印机这类「什么 Probe 都应答」的幽灵设备滤掉,实际网段里这一步基本必需。发现结果要补齐设备名、位置、序列号时,接一步并行补全:

enriched, err := discovery.EnrichDevices(ctx, filtered) // 默认并发 8

Device 的字段值得认识全:

字段 含义
EndpointRef WS-Discovery 的 urn:uuid 形式端点标识,传输层稳定 ID
Name / Hardware / Location 从 scopes 解析出的友好名、硬件型号、位置,没广告就为空
XAddrs 设备服务地址列表,连它
Types / Scopes / MetadataVersion 设备类型、原始 scopes、元数据版本

一个容易踩的坑写在字段注释里:EndpointRef 不是序列号。跨协议关联同一台相机(ONVIF 这边、GB28181 那边)要用 d.Info.SerialNumber,拿 EndpointRef 对序列号永远对不上。

被动监听是另一种思路——相机上电会发 Hello,别的 NVR 探测时也有应答可听,程序开着就能攒设备清单:

listener, err := discovery.NewListener("eth0", func(d *discovery.Device) {
   
    fmt.Println("上线:", d.GetName(), d.GetDeviceEndpoint())
})
if err != nil {
   
    log.Fatal(err)
}
if err := listener.Start(ctx); err != nil {
   
    log.Fatal(err)
}
<-listener.Done() // Stop() 后关闭

定向 HTTP 探测处理组播到不了的场景(路由器隔离、AP 隔离):已知 IP 直接问它的 ONVIF 端口,

if d := discovery.ProbeEndpoint(ctx, "192.168.1.64", 80, 2*time.Second); d != nil {
   
    fmt.Println("是一台 ONVIF 设备:", d.GetDeviceEndpoint())
}
serial, ok := discovery.ProbeSerial(ctx, "192.168.1.64", nil) // nil 用默认端口集

认证与时钟偏差

mermaid diagram

库支持四档认证:

档位 线上行为 适用
AuthDigest WS-Security UsernameToken,PasswordDigest(默认) 绝大多数相机
AuthPasswordText UsernameToken,密码明文进报文 老固件只认这个
AuthHTTPBasic HTTP Basic 头 个别 HTTP 层认证的设备
AuthNone 不带凭据 免认证设备、ESP32 最小实现

真实世界的相机什么怪癖都有,所以有认证梯队——主模式失败且错误属于认证类(HTTP 401/403、NotAuthorized fault、带 fault 的 200)时按序换档,第一个成功的档位会粘住记住:

client, err := onvif.NewClient(endpoint,
    onvif.WithCredentials("admin", "camera-password"),
    onvif.WithAuthFallback(onvif.AuthPasswordText, onvif.AuthHTTPBasic, onvif.AuthNone))

梯队耗尽返回 onvif.ErrUnauthorized,errors.Is 可靠判定。

时钟偏差为什么能打死 digest:PasswordDigest 的签名串里有 Created 时间戳,设备拿它算有效期,偏差超过窗口(各家几十秒到几分钟)直接判无效——表现为怎么改密码都 401。WithAutoClockSkew 在 Initialize 时先无认证调一次 GetSystemDateAndTime,算出偏差参与后续签名。

排错不要猜,用诊断接口:

diag, err := client.DiagnoseAuth(context.Background())
fmt.Println(diag.Status, diag.ClockSkew, diag.Detail)

Status 三态对应的处理:AuthStatusOK 什么都不用做;AuthStatusClockSkew 修相机 NTP(短期让 WithAutoClockSkew 顶着);AuthStatusBadCredentials 才是真改密码。凭据也可以运行时换:client.SetCredentials(user, pass),怀疑梯队粘住了旧档位就 client.ResetAuthLadder(),怀疑 capabilities 缓存过期(相机换过 IP)就 client.InvalidateCapabilitiesCache() 后重跑 Initialize。

取流与抓图

取流的正确姿势永远是先选 profile 再拿地址:

profiles, err := client.Media().GetProfiles(ctx)
if err != nil {
   
    log.Fatal(err)
}
for _, p := range profiles {
   
    fmt.Printf("profile %-10s %dx%d\n", p.Token, p.VideoEncoderConfiguration.Resolution.Width, p.VideoEncoderConfiguration.Resolution.Height)
}

mainToken := onvif.SelectMainProfile(profiles)
subToken := onvif.SelectSubProfile(profiles, mainToken)

uri, err := client.Media().GetStreamURI(ctx, mainToken)
snap, _ := client.Media().GetSnapshotURI(ctx, mainToken)
fmt.Println(uri.URI, "|", snap.URI)

两个选择函数各管一件事:SelectMainProfile 挑分辨率最高的;SelectSubProfile 在主流之下挑严格更小的最大者——同分辨率的第二个 profile 不算子流,Amcrest 有机型双 token 指向同一路流,这个函数就是为它写的。

GetStreamURI 默认要 RTP-Unicast/RTSP;要走 HTTP 隧道或组播用 GetStreamURIWithOptions 传 StreamSetup。拿到地址后 SetSynchronizationPoint 让相机立刻出 I 帧,多路轮显时很省等待;组播场景还有 StartMulticastStreaming/StopMulticastStreaming。

拿到的 uri.URI 直接喂给任何 RTSP 播放器验证:

ffplay -rtsp_transport tcp "rtsp://192.168.1.100:554/Streaming/Channels/101"

媒体面的收发(比如接 pion 做录制或转发)在自己的程序里做——这个库负责把地址、profile、编码参数这些「问相机」的事全部办妥。

PTZ 与预置位

ptz := client.PTZ()

// 向右匀速转 3 秒再停
err := ptz.ContinuousMove(ctx, mainToken, &onvif.PTZSpeed{
   
    PanTilt: &onvif.Vector2D{
   X: 0.5, Y: 0},
    Zoom:    &onvif.Vector1D{
   X: 0},
}, nil)
if err != nil {
   
    log.Fatal(err)
}
time.Sleep(3 * time.Second)
_ = ptz.Stop(ctx, mainToken, true, true) // 同时停云台和变焦

// 预置位:设、走、删
presetToken, err := ptz.SetPreset(ctx, mainToken, "大门口", "")
if err != nil {
   
    log.Fatal(err)
}
_ = ptz.GotoPreset(ctx, mainToken, presetToken)
presets, _ := ptz.GetPresets(ctx, mainToken)
for _, p := range presets {
   
    fmt.Println(p.Token, p.Name)
}

// 限位与状态
cfgs, _ := ptz.GetConfigurations(ctx, mainToken) // pan/tilt/zoom 空间限位
status, _ := ptz.GetStatus(ctx, mainToken)

PTZSpeed 的 X/Y 取值 -1 到 1,是归一化速度,相机自己换算成实际角速度;绝对定位走 AbsoluteMove(传 PTZVector 目标位置),相对微调走 RelativeMove。回家位(GotoHomePosition/SetHomePosition)覆盖「看完门口回原位」这类需求。聚焦在 client.Imaging():Move/StopFocus 配合自动对焦模式,GetImagingSettings/SetImagingSettings 读写亮度、对比度、曝光。

事件订阅

托管订阅把长轮询、续订、断线这些全包了:

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/mickeyzzc/onvif-go/v2/onvif"
)

func main() {
   
    client, err := onvif.NewClient("192.168.1.100",
        onvif.WithCredentials("admin", "camera-password"),
        onvif.WithAutoClockSkew())
    if err != nil {
   
        log.Fatal(err)
    }
    if err := client.Initialize(context.Background()); err != nil {
   
        log.Fatal(err)
    }

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    stream, err := client.Events().SubscribeEvents(ctx, func(msg onvif.NotificationMessage) {
   
        fmt.Printf("[%s] %s %s\n",
            msg.Message.UtcTime.Format("15:04:05"),
            msg.Topic, msg.Message.PropertyOperation)
        for _, item := range msg.Message.Data {
   
            fmt.Printf("    %s = %s\n", item.Name, item.Value)
        }
    }, &onvif.SubscribeEventsOptions{
   SubscriptionDuration: time.Hour})
    if errors.Is(err, onvif.ErrEventsNotSupported) {
   
        log.Fatal("这台相机没有事件服务")
    } else if err != nil {
   
        log.Fatal(err)
    }

    <-ctx.Done()
    _ = stream.Unsubscribe(context.Background())
}

跑起来后,触发一次移动侦测会看到:

[14:32:07] tns1:VideoSource/MotionAlarm Changed
    State = true
    Score = 87

SubscribeEventsOptions 的可调项:

字段 默认 含义
SubscriptionDuration 1h 订阅总时长,到期前自动续
RenewMargin 5m 提前多久续订
PullTimeout 30s 单次长轮询等待
MessageLimit 10 单次拉取条数上限
Filter 空 订阅主题过滤

收到的事件结构是 NotificationMessage{Topic, Message(EventMessage), ProducerAddress, SubscriptionID},EventMessage 里 PropertyOperation(Created/Changed/Deleted)、UtcTime 和三组 SimpleItem(Source/Key/Data,每项 Name+Value)。一条线格式细节值得知道:合规相机把 tt:Message 包在 wsnt:Message 里,穿透这层包装才能拿到 MotionAlarm 的 State 和 Score——托管订阅已处理;绕过它直接调 CreatePullPointSubscription/PullMessages/RenewSubscription/Unsubscribe 原语时得自己拆这两层,原语留给需要精细控制轮询节奏的场景。

把一个进程装成相机

server 角色的完整程序——一台有凭据、会报移动事件的虚拟相机:

package main

import (
    "context"
    "log"
    "time"

    "github.com/mickeyzzc/onvif-go/v2/server"
)

func main() {
   
    config := server.DefaultConfig()
    config.Port = 8081
    config.Username = "admin"
    config.Password = "nvr-test"
    config.SupportEvents = true

    srv, err := server.New(config)
    if err != nil {
   
        log.Fatal(err)
    }
    ctx := context.Background()
    if err := srv.Start(ctx); err != nil {
   
        log.Fatal(err)
    }
    log.Printf("虚拟相机: %s", srv.ListenAddr())

    // 每 30 秒推一条移动事件给在订的 NVR
    for range time.Tick(30 * time.Second) {
   
        srv.PublishEvent(server.Event{
   
            Topic: "tns1:VideoSource/MotionAlarm",
            Data: []server.SimpleItem{
   
                {
   Name: "State", Value: "true"},
                {
   Name: "Score", Value: "87"},
            },
        })
    }
}

NVR 那边手动添加 http://<你的IP>:8081 这台「相机」,就能发现它、读设备信息、拿到流地址、收到事件。PublishEvent 扇出到所有在订的 PullPoint 订阅,没有订阅者时是安全的空操作;PropertyOperation 留空默认 Changed,UtcTime 发布时自动盖。

开发 NVR 时它就是测试桩——不用插真机就能测发现、取流、PTZ、事件的全部逻辑。几个进阶开关:

config.Profiles = []server.ProfileConfig{
    /* 多镜头:Token/Name/VideoSource/VideoEncoder... */ }
config.AdvertiseHostProvider = func() string {
    return detectOutboundIP() } // DHCP 换 IP 后 XAddr 跟着走
config.AuthProtectedActions = []string{
   "SystemReboot"}                     // 额外保护的写动作
config.TLSCertFile, config.TLSKeyFile = "cert.pem", "key.pem"              // HTTPS(Profile T 传输基线)

流地址运行时会变(比如转码后换端口)用 srv.UpdateStreamURI(profileToken, "rtsp://127.0.0.1:8554/live");要按请求上下文自定义某个动作的应答,srv.RegisterContextHandler(action, func(rc *soap.RequestContext, body []byte)) 挂进去。server 的认证策略是「配了凭据只保护写型动作」(Set*/Remove*/Create*/Go* 加 SystemReboot),读操作开放。

最省事的验证方式是自测闭环:拿这个库的客户端连自己的 server,发现、认证、取流、事件全链路一遍过——examples/ 里的 conformance 回环测试干的就是这件事。

生产项目里的用法

生态里 ONVIF 客户端的大户是各类 NVR/VMS——海康、Axis 这些厂商的录像机互相都靠它对接,ONVIF Device Manager 则是大家常用的手动探查工具;onvif-go 的兼容矩阵(海康、Axis、大华、Bosch、Amcrest、HiSilicon OEM、ESP32 最小实现)就是对着这些真机抓包喂出来的。抓包的来源,是下面这些在生产里每天跑的项目:

功能 生产项目 用法
发现(主动 / 被动 / 定向) MiBeeNvr 相机接入的发现层:扫网段列设备、常驻监听攒清单、跨网段定向探测
认证 + 时钟偏差 MiBeeNvr 连商用相机的第一道关卡,WithAutoClockSkew 是对海康系 401 的默认防线
取流 / 抓图 / 主辅码流 MiBeeNvr ONVIF 信令拿到 RTSP 地址与编码参数,接它自己的录像、直播与回放管线
虚拟相机 server mibee-eye-go 把 Linux 板子的 UVC 相机变成一台 ONVIF Profile S 相机;RTSP gateway 模式把已有 RTSP 流包装成相机挂进商用 NVR

看完整实现有两个入口。NVR 侧(客户端怎么连真相机)看 MiBeeNvr——自托管录像机,单二进制跑在低功耗 ARM 上,ONVIF 接入就是这个库,兼容矩阵里每个机型背后都有它的联调记录。设备侧(server 怎么当相机)看 mibee-eye-go——纯 Go 零 CGO,USB/UVC 采集直出,onvif-go server 的每个接缝在真实产品里的样子,那里都有现成答案。

排错清单

现象 原因 处理
海康相机恒 401,报 "sender not authorized" 设备时钟偏差超出签名窗口 WithAutoClockSkew 顶着,DiagnoseAuth 确认,根治是修相机 NTP
请求返回 200 但设备什么都不做 旧版请求载荷命名空间错位,严格设备解析出空载荷 升级到 v2.1.0 以上(这版起线格式以官方 WSDL 为准)
GetStreamURI 返回错误 相机响应里没有 Uri 元素 错误为 ErrEmptyMediaURI,消息里带 512 字节响应摘要;换 profile 再试
发现列表里设备名是空的 相机没在 scopes 里广告名字 用 EnrichDevices 补全,或读 GetDeviceInformation
跨协议关联不上同一台设备 拿 EndpointRef 对了序列号 用 d.Info.SerialNumber 关联,EndpointRef 只是发现层标识
换了密码还是旧认证在跑 认证梯队粘住了成功档 client.ResetAuthLadder()
相机换 IP 后连不上 capabilities 缓存里的旧地址 重跑 Initialize 或 InvalidateCapabilitiesCache()
use-go/onvif 的老代码编译不过 上游停更,新 Go 版本语法冲突 迁移到 v2:import 路径换 github.com/mickeyzzc/onvif-go/v2/onvif,多数代码原样可编

API 速查表

需求 入口 说明
建客户端 onvif.NewClient(endpoint, opts...) URL/IP:port/裸 IP 均可
初始化 client.Initialize(ctx) 测偏差、取 capabilities、修 XAddr
认证 WithCredentials + WithAuthFallback 四档梯队,成功档粘住
时钟偏差 WithAutoClockSkew / DiagnoseAuth 前者自动容偏差,后者出诊断报告
运行时调整 SetCredentials / ResetAuthLadder / InvalidateCapabilitiesCache 换凭据、重置梯队、清缓存
发现 discovery.Discover / NewListener / ProbeEndpoint 主动 / 被动 / 跨网段定向
过滤补全 FilterONVIFDevices / EnrichDevices 滤幽灵设备、并发补全信息
选码流 onvif.SelectMainProfile / SelectSubProfile 分辨率优先,防双 token 同流陷阱
取流/抓图 Media().GetStreamURI / GetSnapshotURI 返回 URI,媒体面自己做
强制 I 帧 Media().SetSynchronizationPoint 多路轮显省等待
云台 PTZ().ContinuousMove / GotoPreset 等 速度 -1..1;限位在 GetConfigurations
事件 Events().SubscribeEvents 托管订阅,自动续订
H.265/Profile M Media2() / Analytics() / metadata.Parse 编码配置面 + 元数据流解析
当相机 server.New(server.DefaultConfig()) PublishEvent 注入事件,TLS 成对配置
动态流地址 server.UpdateStreamURI 转码、端口变化场景
自定义动作 server.RegisterContextHandler 按请求上下文应答

相关链接

相关文章
|
4月前
|
人工智能 开发框架 开发工具
TIOBE 2026年6月TOP15编程语言排行榜
Python连续近5年稳居TIOBE榜首(18.96%),PYPL亦居第一,实现“双榜制霸”。AI大模型爆发使其成为绝对核心:驱动近半数新增AI项目,OpenAI、Claude及文心一言、通义千问等主流框架均以Python为首选开发语言。入局AI,Python是唯一必选站。
|
编解码 API 开发工具
FFmpeg入门及编译 1
FFmpeg入门及编译
743 1
|
22天前
|
人工智能
AI Agent结合浏览器有哪些可落地任务
AI Agent结合浏览器后,最容易尝试的任务往往是已有网页入口、需要人工反复读取、结果又便于检查的工作。
|
22天前
|
存储 缓存 监控
银行卡二三四要素-银行卡四要素-银行卡二要素-银行卡三要素-银行卡实名认证接口
本文从技术视角梳理银行卡二、三、四要素实名核验接口的接入流程、参数规范与调用示例。二要素校验银行卡号与持卡人姓名一致性,三要素增加证件号校验,四要素进一步加入绑定手机号,构成递进的校验维度。文章给出 GET 请求与 APPCODE 鉴权方式、完整 Query 参数表、Python 与 Node.js 调用示例及返回字段结构,并覆盖在线调试要点、调用限制与规范、错误码排查与工程实践(前置校验、幂等去重、熔断降级、结果缓存、监控),以及合规与数据安全要求,供金融风控、账户安全与合规登记场景参考。
129 0
银行卡二三四要素-银行卡四要素-银行卡二要素-银行卡三要素-银行卡实名认证接口
|
23天前
|
供应链 监控 前端开发
拆解1688供应链星级升级:B2B平台把口碑与履约写进评分卡
1688供应链星级评价体系于2026年10月13日升级:聚焦履约拆解(支揽率、异常率等)、询盘数前置、金额加权、免责机制及多渠道能力考核,推动B2B评价从“交易记账”迈向“经营画像”,助力商家精细化运营与数字供应链演进。
|
23天前
|
安全 网络安全 数据安全/隐私保护
数据加密软件,别急着全加密,先分清哪些丢不得
模具企业陈总常问:“全加密就安全?”答案是否定的——盲目全加密易被成本拖垮。本文提出分层防护策略:先锁定“丢不得”的标书、客户名单、图纸源码;再补落地加密、剪贴板防泄、部门级密钥隔离;高敏文件进保密沙箱;辅以离线管控与多维水印。重在精准、可行、可持续。
|
23天前
|
消息中间件 前端开发 Java
Java 云 HIS 源码技术拆解:前后端分离架构与核心组件
云HIS是基于云计算、大数据等技术的区域化医院信息系统,采用SaaS多租户架构,支持跨机构数据共享与协同。相比传统HIS,具备可扩展、易部署、低成本、强协同等优势,涵盖门急诊、住院、药房、电子病历等核心模块,助力医疗信息化向广域网升级。
86 0
|
23天前
|
数据安全/隐私保护
禁止插入私人U盘,靠哪几道关拦得住?
温州某低压电器出口厂因未管控私人U盘,致报价单外泄。案例揭示:风险不在U盘本身,而在无登记、不可溯的私盘。需建立U盘黑白名单、实时报警、全操作留痕,并同步封堵手机OTG等旁路,实现精准可控的数据防泄漏。
108 0
|
24天前
|
存储 人工智能 安全
千问办公收费贵吗?有价目表吗?最新阿里千问办公个人版及企业版收费价格
阿里云千问办公(QwenWork)提供个人免费版(赠2000积分)及付费版(个人标准/高级版78/158元/月),企业版分标准/旗舰版(198/298元/月/席),支持VPC部署与统一管理,兼顾灵活试用与安全合规。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
4月前
|
人工智能 前端开发 API
会议全周期Agent:会前拉人排日程、会中实时纪要、会后自动派任务
本文揭秘一款会议全周期AI助手,覆盖会前智能排期、会中实时转录与待办识别、会后自动纪要与任务分发。它将传统耗时6小时的需求评审,压缩至1小时高效讨论,真正让会议回归“达成共识、推动执行”的本质。(239字)
584 0