一、为什么"萤石云"是海康的官方上云通道
海康威视 2015 年推出"萤石云"(ezviz),是海康官方的 IoT 平台。接入萤石云开放平台有几个典型收益:
- 远程观看:自带萤石云 App / Web 客户端,无需自建流媒体服务
- P2P 打洞:摄像头无需公网 IP,穿透内网直接访问
- 录像 / 告警 / 云存储:海康官方运维,不占本地存储
- 多端 SDK:Web / iOS / Android / 小程序 SDK 全有
对接的核心是萤石云开放平台(open.ys7.com)——海康的所有摄像头都能接(不只是萤石云品牌)。
二、硬件准备
2.1 摄像头配置
海康摄像头默认已经支持萤石云接入——只需在摄像头后台开启"萤石云服务":
1
2
3
4
5
6
| 1. 摄像头 Web 后台(默认 http://192.168.1.64)
2. 网络 → 平台接入
3. 启用"萤石云"
4. 记下两个关键参数:
- 设备序列号(9 位数字 / 字母)
- 验证码(6 位大写字母,印在机身标签上)
|
关键:设备序列号的字母必须大写——这是萤石云 API 强制的。
2.2 网络要求
- 摄像头能访问公网(萤石云域名
open.ys7.com) - 不需要固定公网 IP(P2P 打洞)
- 不要把摄像头放在 DMZ——萤石云有白名单校验
三、注册萤石云开放平台账号
3.1 注册入口
地址:https://open.ys7.com/
1
2
3
4
5
6
| 1. 注册开发者账号
2. 进入"我的应用" → "创建应用"
3. 填写应用名称、类型
4. 创建成功后,拿到:
- AppKey
- AppSecret
|
AppKey 和 AppSecret 是开放平台的唯一身份标识——所有 API 调用都基于这两个参数。
3.2 应用类型选择
- Web 应用 —— 浏览器端 JS 调用
- 移动 App —— iOS / Android
- 服务端应用 —— 纯后端调用(最常用——服务器拉取设备列表 / 直播地址)
四、获取 accessToken
4.1 接口说明
1
2
3
4
5
6
7
| URL: https://open.ys7.com/api/lapp/token/get
Method: POST
Content-Type: application/x-www-form-urlencoded
请求参数:
appKey: <你的 AppKey>
appSecret: <你的 AppSecret>
|
4.2 返回数据
1
2
3
4
5
6
7
8
| {
"data": {
"accessToken": "at.7jrcjmna8qnqg8d3dgnzs87m4v2dme3l-32enpqgusd-1jvdfe4-uxo15ik0s",
"expireTime": 1470810222045
},
"code": "200",
"msg": "操作成功!"
}
|
accessToken 有效期 7 天——需要缓存(不要每次调用都请求一次)。
4.3 错误码
| 错误码 | 描述 |
|---|
| 200 | 请求成功 |
| 10001 | 参数错误(appKey/appSecret 为空或格式错) |
| 10005 | appKey 被冻结 |
| 10017 | appKey 不存在 |
| 10030 | appKey 和 appSecret 不匹配 |
| 49999 | 接口异常 |
4.4 curl 示例
1
2
3
| curl -X POST https://open.ys7.com/api/lapp/token/get \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "appKey=YOUR_APPKEY&appSecret=YOUR_APPSECRET"
|
五、添加设备
5.1 文档地址
https://open.ys7.com/doc/zh/book/index/device_option.html#device_option-api1
5.2 接口
1
2
3
4
5
6
7
8
| URL: https://open.ys7.com/api/lapp/device/add
Method: POST
Content-Type: application/x-www-form-urlencoded
请求参数:
accessToken: <上一步拿到的 accessToken>
deviceSerial: <设备序列号> # 字母必须大写
validateCode: <验证码> # 6 位大写字母
|
5.3 返回示例
1
2
3
4
| {
"code": "200",
"msg": "操作成功"
}
|
code: 200 表示添加成功。
5.4 注意事项
- 设备必须先在摄像头 Web 后台启用萤石云
- 一个 AppKey 可以添加多个设备——没有数量限制(但单 AppKey 调用频率有限制)
- 设备添加后立即生效——Web / App 端登录同一个萤石云账号就能看到
六、获取直播地址
6.1 接口
1
2
3
| URL: https://open.ys7.com/api/lapp/v2/live/address/get
Method: POST
Content-Type: application/x-www-form-urlencoded
|
6.2 请求参数
| 参数 | 类型 | 必选 | 描述 |
|---|
| accessToken | String | Y | 上一步拿到的 accessToken |
| deviceSerial | String | Y | 设备序列号,限制 50 字符 |
| channelNo | Integer | N | 通道号,默认 1 |
| code | String | N | ezopen 协议的视频加密密码 |
| expireTime | Integer | N | 过期时长(秒),30 - 720 天 |
| protocol | Integer | N | 协议:1-ezopen / 2-hls / 3-rtmp / 4-flv,默认 1 |
| quality | Integer | N | 清晰度:1-高清 / 2-流畅 |
| supportH265 | Integer | N | 是否 H.265:1-需要 / 0-不要求 |
6.3 返回示例
1
2
3
4
5
6
7
8
9
| {
"code": "200",
"msg": "操作成功,获取指定有效期的直播地址",
"data": {
"id": "...",
"url": "ezopen://open.ys7.com/BC9729213/1.hd.live",
"expireTime": 1694764800
}
}
|
返回的 url 字段就是直播地址——浏览器 / VLC / 萤石云 App 都能直接播。
6.4 协议对比
| 协议 | 浏览器支持 | 延迟 | 适用 |
|---|
| ezopen | ❌(萤石云私有协议) | 1-2s | 萤石云 App / SDK |
| hls | ✅ Safari 原生 / Chrome 需 video.js | 5-10s | Web 端最稳 |
| rtmp | ❌ Flash 88+ 已删 | 1-2s | 老项目 |
| flv | ✅ via video.js HTTP-FLV | 1-2s | Web 端低延迟 |
七、消息订阅(告警 / 事件推送)
7.1 订阅事件
1
2
3
4
5
| curl --location --request POST \
'https://open.ys7.com/api/open/cloud/ISAPI/Event/notification/subscribeEvent' \
--header 'User-Agent: apifox/1.0.0 (https://www.apifox.cn)' \
--data-urlencode 'accessToken=at.your_token_here' \
--data-urlencode 'deviceSerial=YOUR_DEVICE_SERIAL'
|
事件类型:
- 移动侦测(motion)
- 视频遮挡(videoBlind)
- 声音异常(audio异常)
- IO 报警(外接红外 / 门磁)
7.2 拉取告警列表
1
2
3
4
5
| curl --location --request POST \
'https://open.ys7.com/api/lapp/alarm/device/list' \
--header 'User-Agent: apifox/1.0.0 (https://www.apifox.cn)' \
--data-urlencode 'accessToken=at.your_token_here' \
--data-urlencode 'deviceSerial=YOUR_DEVICE_SERIAL'
|
返回形如:
1
2
3
4
5
6
7
8
9
10
11
12
| {
"code": "200",
"data": [
{
"alarmId": "...",
"alarmName": "移动侦测",
"alarmTime": "2024-12-15 10:30:00",
"channelNo": 1,
"deviceSerial": "..."
}
]
}
|
八、Java 后端封装
8.1 依赖
1
2
3
4
5
6
7
8
9
10
| <dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
|
8.2 TokenService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| @Service
public class Ys7TokenService {
private static final String APP_KEY = "your_app_key";
private static final String APP_SECRET = "your_app_secret";
private static final String TOKEN_URL = "https://open.ys7.com/api/lapp/token/get";
// 缓存 token
private String cachedToken;
private long expireTime = 0;
public synchronized String getAccessToken() {
if (cachedToken != null && System.currentTimeMillis() < expireTime) {
return cachedToken;
}
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("appKey", APP_KEY)
.add("appSecret", APP_SECRET)
.build();
Request request = new Request.Builder()
.url(TOKEN_URL)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String json = response.body().string();
JSONObject data = JSON.parseObject(json).getJSONObject("data");
cachedToken = data.getString("accessToken");
expireTime = data.getLong("expireTime") - 86400_000L; // 提前 1 天刷新
return cachedToken;
} catch (IOException e) {
throw new RuntimeException("获取萤石云 token 失败", e);
}
}
}
|
8.3 设备添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| @Service
public class Ys7DeviceService {
@Autowired
private Ys7TokenService tokenService;
public boolean addDevice(String deviceSerial, String validateCode) {
// 字母必须大写
deviceSerial = deviceSerial.toUpperCase();
validateCode = validateCode.toUpperCase();
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("accessToken", tokenService.getAccessToken())
.add("deviceSerial", deviceSerial)
.add("validateCode", validateCode)
.build();
Request request = new Request.Builder()
.url("https://open.ys7.com/api/lapp/device/add")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String json = response.body().string();
return "200".equals(JSON.parseObject(json).getString("code"));
} catch (IOException e) {
throw new RuntimeException("添加设备失败", e);
}
}
}
|
九、调用频率限制
萤石云开放平台有调用频率限制:
- token 接口:每个 AppKey 每分钟 60 次
- 设备 / 直播地址接口:每个 AppKey 每分钟 120 次
生产建议:
- accessToken 缓存 7 天(设过期前 1 天主动刷新)
- 设备列表本地缓存**(摄像头不轻易变化)**
- 直播地址按需获取,短期缓存 5-10 分钟
十、常见 5 个坑
- 设备序列号字母必须大写——
deviceSerial.toUpperCase() 是基本操作 - accessToken 每次调 token 接口——会触发频率限制——必须本地缓存
- 添加设备失败但摄像头 Web 后台能看到萤石云在线——AppKey 没绑定到具体设备——重新走 device/add
- 直播地址过期——默认 expireTime = 7 天——生产建议设 30 天 + 提前刷新
- 告警事件收不到——没订阅事件——先调 subscribeEvent 才会推送
十一、安全注意
- AppSecret 必须服务端存储——不能放到前端 / 客户端(会被反编译 / 抓包)
- 设备验证码只用于首次添加——建议添加成功后丢弃,不要长期存数据库
- accessToken 不要写日志——走日志脱敏(参考 .claude/docs/0.5-batch-guide.md)
十二、总结
- 萤石云开放平台 = 海康官方 IoT 云——AppKey/Secret 申请 → token 获取 → 设备添加 → 直播地址
- accessToken 7 天有效——必须服务端缓存(避免触发频率限制)
- 设备序列号 + 验证码——字母必须大写——6 位大写字母印在机身
- 直播地址协议 = ezopen(私有)/ hls(web 稳)/ flv(web 低延迟)
- 消息订阅 =
subscribeEvent + alarm/device/list 拉取 - 生产建议 = AppSecret 走服务端 + token 缓存 7 天 + 直播地址 5-10 分钟缓存
参考资料