视频平台图片鉴权接口开发踩坑记录

在水文监测后台管理系统中,视频设备接口需要返回缩略图鉴权信息,看似简单的需求却踩了几个坑:双重鉴权体系混淆、API 响应类型转换失败、缓存时间设计不当……本文记录完整的开发过程与问题解决。

问题背景

项目需要为 api/station/material/videoDevices 接口增加 thumbnailAuth 字段,用于返回访问缩略图图片所需的鉴权信息。

根据广东省水利视频共享接口文档:

  • 图片鉴权接口:/lisa/access/sign(POST)
  • 鉴权有效期:10 分钟
  • 返回字段:x-apig-noncex-apig-timestampx-apig-appidx-apig-signx-datax-code

看起来是个简单的需求,但实际开发中踩了不少坑。

双重鉴权体系

视频平台包含两套并行的鉴权逻辑,这是理解整个问题的关键:

鉴权类型 用途 接口 有效期
Token 鉴权 调用视频列表、播放、云台控制等接口 /resourceSharing/oauth/token 25 分钟
Sign 鉴权 访问缩略图、告警图片等资源 /lisa/access/sign 10 分钟

关键点:Sign 鉴权接口本身也需要 Token 鉴权才能调用!这个细节在文档中没有明确说明,导致后续踩坑。

踩坑过程

坑一:请求缺少 Token

现象

1
{"data":"请求缺少token","desc":"无权限","status":401}

原因:最初误认为图片鉴权接口不需要 Token,专门写了一个 sendPostWithoutToken() 方法来调用。

解决:Sign 鉴权接口本身也需要 Token,改用 sendPost() 方法,先获取 Token 再请求:

1
2
3
4
5
6
// 错误写法
String response = VideoApiClient.sendPostWithoutToken(videoApiProperties, "/lisa/access/sign", requestBody);

// 正确写法
String token = getToken();
String response = VideoApiClient.sendPost(videoApiProperties, token, "/lisa/access/sign", requestBody);

坑二:ClassCastException 类型转换失败

现象

1
java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Map

原因:API 返回的 data 字段在不同情况下类型不同:

  • 正常时是 Map(包含鉴权信息)
  • 错误时是 String(错误信息)

解决:添加 instanceof 类型检查:

1
2
3
4
5
6
if (dataObj instanceof Map) {
Map<String, String> authData = (Map<String, String>) dataObj;
// 正常处理
} else {
log.warn("data字段类型不是Map,实际类型: {}, 值: {}", dataObj.getClass().getSimpleName(), dataObj);
}

坑三:thumbnailAuth 始终为 null

现象:接口返回正常,但 thumbnailAuth 字段始终为 null

原因:原逻辑存在设计缺陷——只有当 thumbnail 不为空时才去获取鉴权信息。但有些设备没有缩略图,却仍然需要鉴权信息来访问告警图片。

解决:将获取鉴权信息移到循环外部,预先获取一次,统一设置给所有设备:

1
2
3
4
5
6
7
8
9
10
11
12
// 1.5 预先获取图片鉴权信息(对所有设备通用,只需获取一次)
Map<String, String> imageAuth = getImageAccessSign();

// 2. 为每个设备获取详细信息
for (String cameraId : cameraIds) {
// ... 获取播放地址、缩略图等 ...

// 统一设置 thumbnailAuth(无论缩略图是否为空)
if (imageAuth != null && !imageAuth.isEmpty()) {
deviceInfo.setThumbnailAuth(imageAuth);
}
}

坑四:账户权限不足

现象

1
{"data":"","desc":"共享用户无权限访问该接口","status":401}

原因:这不是代码问题,是视频平台账户权限问题。当前的共享用户账户没有 /lisa/access/sign 接口的访问权限。

解决:需要联系视频平台提供方开通接口权限。

核心代码实现

获取图片鉴权信息(带缓存)

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
39
40
41
42
43
44
45
46
/**
* 获取图片访问鉴权信息(带缓存)
* 根据文档,鉴权信息10分钟有效
*/
public Map<String, String> getImageAccessSign() throws ServiceException {
try {
// 1. 优先从Redis缓存获取
if (redisCache != null) {
Map<String, String> cachedAuth = redisCache.getCacheObject(IMAGE_AUTH_CACHE_KEY);
if (cachedAuth != null && !cachedAuth.isEmpty()) {
return cachedAuth;
}
}

// 2. 缓存未命中,调用API获取
String appId = videoApiProperties.getImageAppId();
if (StringUtils.isEmpty(appId)) {
log.warn("图片访问鉴权的appId未配置");
return null;
}

// 3. 调用 /lisa/access/sign 接口(需要Token鉴权)
String token = getToken();
Map<String, String> requestBody = new HashMap<>();
requestBody.put("x-apig-appid", appId);

String response = VideoApiClient.sendPost(videoApiProperties, token, "/lisa/access/sign", requestBody);

// 4. 解析响应(带类型检查)
Map<String, Object> responseMap = objectMapper.readValue(response, Map.class);
if (responseMap != null && responseMap.get("data") instanceof Map) {
Map<String, String> authData = (Map<String, String>) responseMap.get("data");

// 5. 存入缓存(9分钟,留1分钟缓冲)
if (redisCache != null && authData != null && !authData.isEmpty()) {
redisCache.setCacheObject(IMAGE_AUTH_CACHE_KEY, authData, IMAGE_AUTH_EXPIRE_TIME, TimeUnit.SECONDS);
}
return authData;
}

return null;
} catch (Exception e) {
log.error("获取图片鉴权信息失败", e);
return null;
}
}

缓存时间设计

当存在多层缓存时,内层缓存的过期时间应小于外层缓存

缓存层 有效期 说明
图片鉴权缓存 9 分钟 留 1 分钟缓冲
接口整体缓存 9 分钟 原来是 10 分钟,已调整

如果不这样设计,可能会出现:外层缓存还活着,但内层的鉴权信息已经过期,导致返回无效的鉴权参数。

经验总结

1. 第三方接口鉴权要确认清楚

文档可能不完整或有误,实际联调前应该:

  1. 确认接口是否需要鉴权
  2. 确认鉴权方式(Token、Sign、API Key 等)
  3. 确认账户是否有对应权限

2. 外部 API 响应要做类型检查

外部 API 的响应结构可能因为各种原因(错误、权限、参数)发生变化,使用 instanceof 进行类型检查可以避免运行时异常。

3. 缓存时间要层层递减

多层缓存场景下,内层缓存有效期必须小于外层,避免返回过期数据。

4. 日志是调试的好帮手

在排查问题时,添加原始响应日志非常关键:

1
log.info("图片鉴权API原始响应: {}", response);

这帮助快速定位是代码问题还是平台权限问题。

当前状态

项目 状态
代码实现 ✅ 完成
单元测试 ✅ 完成
缓存逻辑 ✅ 完成
平台权限 ❌ 待开通

下一步:联系视频平台方开通 /lisa/access/sign 接口权限,然后验证完整链路。