hik-iot-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +467 -0
- package/dist/core/__tests__/crypto.test.d.ts +1 -0
- package/dist/core/__tests__/crypto.test.js +68 -0
- package/dist/core/auth.d.ts +51 -0
- package/dist/core/auth.js +292 -0
- package/dist/core/config.d.ts +31 -0
- package/dist/core/config.js +11 -0
- package/dist/core/crypto.d.ts +12 -0
- package/dist/core/crypto.js +88 -0
- package/dist/core/errors.d.ts +45 -0
- package/dist/core/errors.js +61 -0
- package/dist/core/http.d.ts +29 -0
- package/dist/core/http.js +134 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/modules/access/access.schema.d.ts +2307 -0
- package/dist/modules/access/access.schema.js +353 -0
- package/dist/modules/access/access.service.d.ts +55 -0
- package/dist/modules/access/access.service.js +145 -0
- package/dist/modules/access/access.types.d.ts +307 -0
- package/dist/modules/access/access.types.js +3 -0
- package/dist/modules/access/index.d.ts +2 -0
- package/dist/modules/access/index.js +20 -0
- package/dist/modules/card/card.schema.d.ts +27 -0
- package/dist/modules/card/card.schema.js +12 -0
- package/dist/modules/card/card.service.d.ts +10 -0
- package/dist/modules/card/card.service.js +50 -0
- package/dist/modules/card/card.types.d.ts +18 -0
- package/dist/modules/card/card.types.js +2 -0
- package/dist/modules/device/device.schema.d.ts +258 -0
- package/dist/modules/device/device.schema.js +44 -0
- package/dist/modules/device/device.service.d.ts +14 -0
- package/dist/modules/device/device.service.js +69 -0
- package/dist/modules/device/device.types.d.ts +46 -0
- package/dist/modules/device/device.types.js +2 -0
- package/dist/modules/face/face.schema.d.ts +27 -0
- package/dist/modules/face/face.schema.js +12 -0
- package/dist/modules/face/face.service.d.ts +9 -0
- package/dist/modules/face/face.service.js +42 -0
- package/dist/modules/face/face.types.d.ts +17 -0
- package/dist/modules/face/face.types.js +2 -0
- package/dist/modules/person/person.schema.d.ts +270 -0
- package/dist/modules/person/person.schema.js +42 -0
- package/dist/modules/person/person.service.d.ts +16 -0
- package/dist/modules/person/person.service.js +88 -0
- package/dist/modules/person/person.types.d.ts +65 -0
- package/dist/modules/person/person.types.js +2 -0
- package/dist/sdk.d.ts +60 -0
- package/dist/sdk.js +106 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# 海康互联 IoT SDK
|
|
2
|
+
|
|
3
|
+
海康互联开放平台 Node.js TypeScript SDK,提供门禁设备的人员、人脸、卡片管理及远程控门等功能。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **强类型** - 完整的 TypeScript 类型定义
|
|
8
|
+
- **自动鉴权** - 自动管理 access_token,无需手动处理
|
|
9
|
+
- **自动重试** - Token 过期自动刷新并重试请求
|
|
10
|
+
- **数据校验** - 使用 zod 对所有 API 返回值进行校验
|
|
11
|
+
- **业务模型** - 面向业务模型设计,而非 HTTP 接口
|
|
12
|
+
- **日志支持** - 使用 pino 进行日志记录
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @qiang9996/hik-iot-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { HikSdk } from '@qiang9996/hik-iot-sdk';
|
|
24
|
+
|
|
25
|
+
const sdk = new HikSdk({
|
|
26
|
+
appKey: 'your-app-key',
|
|
27
|
+
appSecret: 'your-app-secret',
|
|
28
|
+
userName: 'your-phone-number', // 登录手机号
|
|
29
|
+
password: 'your-password', // 登录密码
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 一键创建人员并绑定人脸和卡片
|
|
33
|
+
const result = await sdk.createPersonWithFaceAndCard({
|
|
34
|
+
deviceSerial: 'your-device-serial',
|
|
35
|
+
employeeNo: 'EMP001',
|
|
36
|
+
name: '张三',
|
|
37
|
+
faceURL: 'https://example.com/face.jpg',
|
|
38
|
+
cardNo: '12345678',
|
|
39
|
+
permanentValid: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 远程开门
|
|
43
|
+
await sdk.device.openDoor('your-device-serial');
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API 文档
|
|
47
|
+
|
|
48
|
+
### 初始化
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const sdk = new HikSdk({
|
|
52
|
+
appKey: string; // 应用 AppKey
|
|
53
|
+
appSecret: string; // 应用 AppSecret
|
|
54
|
+
userName: string; // 登录手机号
|
|
55
|
+
password: string; // 登录密码
|
|
56
|
+
redirectUrl?: string; // 重定向回调地址(可选,需在应用安全设置中配置)
|
|
57
|
+
baseUrl?: string; // API 基础地址(默认:https://open-api.hikiot.com)
|
|
58
|
+
timeout?: number; // 请求超时时间(默认:30000ms)
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 获取当前登录用户信息
|
|
62
|
+
const userInfo = sdk.getUserInfo();
|
|
63
|
+
// { teamNo: '团队编号', personNo: '成员编号', accountNo: '账号' }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 人员管理 (sdk.person)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// 添加/更新人员
|
|
70
|
+
await sdk.person.create({
|
|
71
|
+
deviceSerial: 'device-serial',
|
|
72
|
+
employeeNo: 'EMP001',
|
|
73
|
+
name: '张三',
|
|
74
|
+
userType: 'normal', // normal | visitor | blackList
|
|
75
|
+
permanentValid: true, // 是否永久有效
|
|
76
|
+
enableBeginTime: '2024-01-01T00:00:00',
|
|
77
|
+
enableEndTime: '2037-12-31T23:59:59',
|
|
78
|
+
doorRight: [1], // 门权限
|
|
79
|
+
doorRightPlan: [{ doorNo: 1, planTemplateId: [1] }],
|
|
80
|
+
password: '123456', // 开门密码(可选)
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 删除人员
|
|
84
|
+
await sdk.person.delete({
|
|
85
|
+
deviceSerial: 'device-serial',
|
|
86
|
+
employeeNo: 'EMP001',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 批量删除人员
|
|
90
|
+
await sdk.person.batchDelete({
|
|
91
|
+
deviceSerial: 'device-serial',
|
|
92
|
+
employeeNos: ['EMP001', 'EMP002'],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 查询人员
|
|
96
|
+
const result = await sdk.person.search({
|
|
97
|
+
deviceSerial: 'device-serial',
|
|
98
|
+
keyword: 'EMP001',
|
|
99
|
+
page: 1,
|
|
100
|
+
size: 20,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// 获取单个人员
|
|
104
|
+
const person = await sdk.person.get('device-serial', 'EMP001');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 人脸管理 (sdk.face)
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// 添加/更新人脸
|
|
111
|
+
await sdk.face.add({
|
|
112
|
+
deviceSerial: 'device-serial',
|
|
113
|
+
employeeNo: 'EMP001',
|
|
114
|
+
faceURL: 'https://example.com/face.jpg', // 图片需小于200KB
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 删除人脸
|
|
118
|
+
await sdk.face.delete({
|
|
119
|
+
deviceSerial: 'device-serial',
|
|
120
|
+
employeeNo: 'EMP001',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 卡片管理 (sdk.card)
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// 添加/更新卡片
|
|
128
|
+
await sdk.card.add({
|
|
129
|
+
deviceSerial: 'device-serial',
|
|
130
|
+
employeeNo: 'EMP001',
|
|
131
|
+
cardNo: '12345678',
|
|
132
|
+
cardType: 'normalCard', // normalCard | hijackCard | superCard | ...
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 删除卡片
|
|
136
|
+
await sdk.card.delete({
|
|
137
|
+
deviceSerial: 'device-serial',
|
|
138
|
+
cardNo: '12345678',
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 批量删除卡片
|
|
142
|
+
await sdk.card.batchDelete({
|
|
143
|
+
deviceSerial: 'device-serial',
|
|
144
|
+
cardNos: ['12345678', '87654321'],
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 设备管理 (sdk.device)
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// 远程开门
|
|
152
|
+
await sdk.device.openDoor('device-serial', 1);
|
|
153
|
+
|
|
154
|
+
// 远程关门(受控)
|
|
155
|
+
await sdk.device.closeDoor('device-serial', 1);
|
|
156
|
+
|
|
157
|
+
// 设置常开
|
|
158
|
+
await sdk.device.setDoorAlwaysOpen('device-serial', 1);
|
|
159
|
+
|
|
160
|
+
// 设置常闭
|
|
161
|
+
await sdk.device.setDoorAlwaysClose('device-serial', 1);
|
|
162
|
+
|
|
163
|
+
// 查询门控状态
|
|
164
|
+
const status = await sdk.device.getDoorStatus({
|
|
165
|
+
deviceSerial: 'device-serial',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 查询设备容量
|
|
169
|
+
const storage = await sdk.device.getStorageCount({
|
|
170
|
+
deviceSerial: 'device-serial',
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 清空设备数据
|
|
174
|
+
await sdk.device.clearStorage({
|
|
175
|
+
deviceSerial: 'device-serial',
|
|
176
|
+
opts: ['supportUserInfo', 'supportCardInfo'],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 门禁应用 API (sdk.access)
|
|
181
|
+
|
|
182
|
+
门禁应用 API 提供基于 SaaS 应用层面的权限管理,与硬件设备 API 互补。
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// ============ 权限配置 ============
|
|
186
|
+
// 添加权限配置
|
|
187
|
+
const authorityConfigId = await sdk.access.addAuthorityConfig({
|
|
188
|
+
personNo: 'ZHAxxxx',
|
|
189
|
+
timePlanIds: ['1'],
|
|
190
|
+
deviceGroupNo: 'SBZ001',
|
|
191
|
+
password: '123456',
|
|
192
|
+
dynamicCode: false,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 查询权限配置列表
|
|
196
|
+
const configs = await sdk.access.getAuthorityConfigPage('__UNI__E32B021', 1, 20);
|
|
197
|
+
|
|
198
|
+
// 查询权限配置详情
|
|
199
|
+
const config = await sdk.access.getAuthorityConfigDetail(authorityConfigId);
|
|
200
|
+
|
|
201
|
+
// 删除权限配置
|
|
202
|
+
await sdk.access.deleteAuthorityConfig(authorityConfigId);
|
|
203
|
+
|
|
204
|
+
// ============ 设备分组 ============
|
|
205
|
+
// 查询设备分组
|
|
206
|
+
const groups = await sdk.access.getDeviceGroupPage(1, 20);
|
|
207
|
+
|
|
208
|
+
// 创建设备分组
|
|
209
|
+
await sdk.access.saveDeviceGroup({
|
|
210
|
+
deviceGroupName: '一楼门禁',
|
|
211
|
+
deviceGroupType: 1,
|
|
212
|
+
deviceSerials: ['L12345678'],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ============ 通行权限查询 ============
|
|
216
|
+
// 查询人员与设备权限关系
|
|
217
|
+
const personDevices = await sdk.access.getPersonDevicePage({
|
|
218
|
+
personName: '张三',
|
|
219
|
+
page: 1,
|
|
220
|
+
size: 20,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// 查询未下发信息
|
|
224
|
+
const waitIssue = await sdk.access.getWaitIssueInfo('L12345678');
|
|
225
|
+
|
|
226
|
+
// 查询临时密码列表
|
|
227
|
+
const passwords = await sdk.access.listPassword();
|
|
228
|
+
|
|
229
|
+
// ============ 下发操作 ============
|
|
230
|
+
// 手动下发
|
|
231
|
+
await sdk.access.manualIssue({ deviceSerials: ['L12345678'] });
|
|
232
|
+
|
|
233
|
+
// 选择下发
|
|
234
|
+
await sdk.access.selectIssue({ issueNos: ['issue-no-1'] });
|
|
235
|
+
|
|
236
|
+
// 覆盖下发
|
|
237
|
+
await sdk.access.coverIssue({});
|
|
238
|
+
|
|
239
|
+
// ============ 开门记录 ============
|
|
240
|
+
// 查询开门记录
|
|
241
|
+
const events = await sdk.access.getAccessEventPage({
|
|
242
|
+
startDate: '2024-01-01 00:00:00',
|
|
243
|
+
endDate: '2024-12-31 23:59:59',
|
|
244
|
+
deviceSerials: ['L12345678'],
|
|
245
|
+
page: 1,
|
|
246
|
+
size: 20,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// 查询开门记录详情
|
|
250
|
+
const eventDetail = await sdk.access.getAccessEventDetail('origin-id');
|
|
251
|
+
|
|
252
|
+
// 获取认证图片 URL
|
|
253
|
+
const picUrl = await sdk.access.getAuthPicUrl({ originId: 'origin-id', deviceSerial: 'L12345678' });
|
|
254
|
+
|
|
255
|
+
// 远程开门(应用层面)
|
|
256
|
+
await sdk.access.remoteOpenDoor('L12345678');
|
|
257
|
+
|
|
258
|
+
// ============ 时间计划 ============
|
|
259
|
+
// 查询时间计划列表
|
|
260
|
+
const timePlans = await sdk.access.getTimePlanList();
|
|
261
|
+
|
|
262
|
+
// 查询时间计划详情
|
|
263
|
+
const timePlan = await sdk.access.getTimePlanDetail(1);
|
|
264
|
+
|
|
265
|
+
// 添加时间计划
|
|
266
|
+
await sdk.access.addTimePlan({ timePlanName: '工作日', timePlanType: 1 });
|
|
267
|
+
|
|
268
|
+
// 删除时间计划
|
|
269
|
+
await sdk.access.deleteTimePlan(1);
|
|
270
|
+
|
|
271
|
+
// ============ 节假日组 ============
|
|
272
|
+
// 查询节假日组列表
|
|
273
|
+
const holidays = await sdk.access.getHolidayGroupList();
|
|
274
|
+
|
|
275
|
+
// 添加节假日组
|
|
276
|
+
await sdk.access.addHolidayGroup({ holidayGroupName: '春节', holidayGroupDetail: [...] });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 便捷方法
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// 一键创建人员并绑定人脸
|
|
283
|
+
const result = await sdk.createPersonWithFace({
|
|
284
|
+
deviceSerial: 'device-serial',
|
|
285
|
+
employeeNo: 'EMP001',
|
|
286
|
+
name: '张三',
|
|
287
|
+
faceURL: 'https://example.com/face.jpg',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// 一键创建人员并绑定人脸和卡片
|
|
291
|
+
const result = await sdk.createPersonWithFaceAndCard({
|
|
292
|
+
deviceSerial: 'device-serial',
|
|
293
|
+
employeeNo: 'EMP001',
|
|
294
|
+
name: '张三',
|
|
295
|
+
faceURL: 'https://example.com/face.jpg',
|
|
296
|
+
cardNo: '12345678',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// 完整删除人员(包括人脸)
|
|
300
|
+
await sdk.deletePersonComplete('device-serial', 'EMP001');
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## 远程核验事件服务
|
|
304
|
+
|
|
305
|
+
SDK 提供了远程核验事件处理示例,用于接收海康互联平台推送的 `DoorRemoteCheck` 事件,并根据白名单决定是否放行。
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# 运行远程核验事件服务
|
|
309
|
+
npx tsx examples/mock.ts
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
服务默认监听 `8080` 端口,接收加密事件回调并自动解密。需要在海康互联开放平台配置:
|
|
313
|
+
|
|
314
|
+
1. **事件订阅** → 添加 `门禁远程核验` 事件
|
|
315
|
+
2. **加密策略** → 配置 `Encrypt Key` 和 `Verification Token`
|
|
316
|
+
3. **回调地址** → 设置为服务的公网地址(如 `https://your-domain/callback`)
|
|
317
|
+
|
|
318
|
+
环境变量配置:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
export HIK_APP_KEY="your-app-key"
|
|
322
|
+
export HIK_APP_SECRET="your-app-secret"
|
|
323
|
+
export HIK_USER_NAME="your-phone-number"
|
|
324
|
+
export HIK_PASSWORD="your-password"
|
|
325
|
+
export HIK_ENCRYPT_KEY="your-encrypt-key" # 事件解密用
|
|
326
|
+
export HIK_VERIFICATION_TOKEN="your-token" # 事件验证用
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 错误处理
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { HikError, HikDeviceError, HikAuthError, ERROR_CODES } from '@qiang9996/hik-iot-sdk';
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await sdk.person.create({ ... });
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof HikDeviceError) {
|
|
338
|
+
// 设备相关错误 (160xxx)
|
|
339
|
+
console.error('设备错误:', error.code, error.message, error.detail);
|
|
340
|
+
} else if (error instanceof HikAuthError) {
|
|
341
|
+
// 认证错误
|
|
342
|
+
console.error('认证错误:', error.message);
|
|
343
|
+
} else if (error instanceof HikError) {
|
|
344
|
+
// 其他 API 错误
|
|
345
|
+
console.error('API错误:', error.code, error.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 常见错误码
|
|
351
|
+
|
|
352
|
+
| 错误码 | 说明 |
|
|
353
|
+
|--------|------|
|
|
354
|
+
| 0 | 操作成功 |
|
|
355
|
+
| 400015 | AppAccessToken 无效 |
|
|
356
|
+
| 400019 | UserAccessToken 无效 |
|
|
357
|
+
| 160001 | 设备不存在 |
|
|
358
|
+
| 160101 | 设备离线 |
|
|
359
|
+
| 160102 | 设备超时 |
|
|
360
|
+
| 160104 | 设备不支持该功能 |
|
|
361
|
+
| 160112 | 人员已被删除 |
|
|
362
|
+
| 160116 | 人员数量已达上限 |
|
|
363
|
+
| 160117 | 照片未检测到人脸 |
|
|
364
|
+
|
|
365
|
+
## 示例说明
|
|
366
|
+
|
|
367
|
+
`examples/` 目录提供了多种使用示例:
|
|
368
|
+
|
|
369
|
+
| 示例文件 | 说明 |
|
|
370
|
+
|---------|------|
|
|
371
|
+
| `demo.ts` | 综合交互式示例,支持菜单选择运行门禁应用 API、人员管理或设备控制示例 |
|
|
372
|
+
| `quick-start.ts` | 最简快速开始示例,展示一键创建人员并绑定人脸和卡片 |
|
|
373
|
+
| `face-access-demo.ts` | 人脸与门禁设备管理示例 |
|
|
374
|
+
| `access-demo.ts` | 门禁应用 API 示例 |
|
|
375
|
+
| `mock.ts` | 远程核验事件 Web 服务示例,接收海康事件回调并处理 |
|
|
376
|
+
|
|
377
|
+
运行示例:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# 设置环境变量
|
|
381
|
+
export HIK_APP_KEY="your-app-key"
|
|
382
|
+
export HIK_APP_SECRET="your-app-secret"
|
|
383
|
+
export HIK_USER_NAME="your-phone-number"
|
|
384
|
+
export HIK_PASSWORD="your-password"
|
|
385
|
+
export HIK_DEVICE_SERIAL="your-device-serial"
|
|
386
|
+
|
|
387
|
+
# 运行综合示例(交互式菜单)
|
|
388
|
+
npx tsx examples/demo.ts
|
|
389
|
+
|
|
390
|
+
# 直接运行指定示例
|
|
391
|
+
npx tsx examples/demo.ts 1 # 门禁应用 API
|
|
392
|
+
npx tsx examples/demo.ts 2 # 人员/人脸/卡片管理
|
|
393
|
+
npx tsx examples/demo.ts 3 # 设备控制
|
|
394
|
+
|
|
395
|
+
# 运行远程核验服务
|
|
396
|
+
npx tsx examples/mock.ts
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## 数据加密
|
|
400
|
+
|
|
401
|
+
海康互联开放平台默认开启数据加密,SDK 会自动处理请求加密和响应解密:
|
|
402
|
+
|
|
403
|
+
- **API 请求/响应加密**:使用 RSA 私钥(App Secret)加密,SDK 自动处理
|
|
404
|
+
- **事件订阅加密**:使用 AES-256-CBC 加密,需配置 `Encrypt Key`
|
|
405
|
+
|
|
406
|
+
如需关闭加密(不推荐),可在初始化时设置:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const sdk = new HikSdk({
|
|
410
|
+
appKey: 'your-app-key',
|
|
411
|
+
appSecret: 'your-app-secret',
|
|
412
|
+
userName: 'your-phone-number',
|
|
413
|
+
password: 'your-password',
|
|
414
|
+
enableEncrypt: false, // 关闭加密
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## 项目结构
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
src/
|
|
422
|
+
├── core/ # 核心模块
|
|
423
|
+
│ ├── auth.ts # 认证管理(App/User Token)
|
|
424
|
+
│ ├── config.ts # 配置校验
|
|
425
|
+
│ ├── crypto.ts # RSA 加密/解密
|
|
426
|
+
│ ├── http.ts # HTTP 客户端
|
|
427
|
+
│ └── logger.ts # 日志
|
|
428
|
+
├── modules/ # 业务模块
|
|
429
|
+
│ ├── access/ # 门禁应用 API
|
|
430
|
+
│ ├── card/ # 卡片管理
|
|
431
|
+
│ ├── device/ # 设备控制
|
|
432
|
+
│ ├── face/ # 人脸管理
|
|
433
|
+
│ └── person/ # 人员管理
|
|
434
|
+
├── sdk.ts # SDK 主类
|
|
435
|
+
└── index.ts # 入口导出
|
|
436
|
+
|
|
437
|
+
examples/ # 示例代码
|
|
438
|
+
├── demo.ts # 综合示例
|
|
439
|
+
├── quick-start.ts # 快速开始
|
|
440
|
+
├── mock.ts # 远程核验服务
|
|
441
|
+
├── access-demo.ts # 门禁应用示例
|
|
442
|
+
└── face-access-demo.ts # 人脸设备示例
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## 开发
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# 安装依赖
|
|
449
|
+
npm install
|
|
450
|
+
|
|
451
|
+
# 构建
|
|
452
|
+
npm run build
|
|
453
|
+
|
|
454
|
+
# 运行测试
|
|
455
|
+
npm test
|
|
456
|
+
|
|
457
|
+
# 接收远程核验事件
|
|
458
|
+
npx tsx examples/mock.ts
|
|
459
|
+
|
|
460
|
+
#运行demo
|
|
461
|
+
npx tsx examples/demo.ts 2
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## License
|
|
466
|
+
|
|
467
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const crypto_1 = require("../crypto");
|
|
5
|
+
(0, vitest_1.describe)('HikCrypto', () => {
|
|
6
|
+
let crypto;
|
|
7
|
+
// 使用文档中的真实 appSecret
|
|
8
|
+
const testAppSecret = `MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKhhvUQyZg5VcFSgle2NI7tJ3YLsYjY4fRbWMoHJKqrq692esYYdqqhERvYbbW/X0ns2QKr1vNHcEXM709VoUX+X6cnTu/5ANDSbRAjWo+L+f1KFu0z82bpX2hSwLEGyj6qJvXcFgYlKu3RxO7b4SKVhuxa06mkoY62AnbP7k8ajAgMBAAECgYBJnADLkWTAG2Wx+UnRwYO/Wnk1xPTiUyuMEjOePT0j3vMhvUBKKynxyXdkG3VqEWV4a601j5vt+lC+mYbvol0NvtsVUAnp+f9ca036pJHbds4MTciwjKOcwJW9aGNEUFZak+SmGS3BFnWo2prNIHHJ0v4UcS2LwlrnD+65rmMaoQJBANUHmXMkma99mGrSf381yIsf4DaAGmAKT+UC1Uxo9TEGZWqYbykeXfiwoSaUUxaTspV0BQhIC68c5D7CFsU1vasCQQDKWJ4cuYs9FWlJfrCd4/GFP4BbhHussiqqbsG9E0znFx7kyQyrcL+qHv1kmv19CDlmEzZ+tMyqxdpn45HxnHLpAkBAbbz2OxKUJessG5/d7HlLwodAVHwlodjKDZJPOss1WZbgp1uvVvTa2yjSDzgV8vOnCeDfwVJvUtChPng+iuNjAkAHCh42EZ5ueZg4Hlg2pf95C0SD6pAC6g/h7gh/c3RxdbR1cSknrbx9Rqa6IUpAV1Dn5DM5JUEZj9iRVLF3HmJBAkAEhbazySPeyVvFqXMm9ST1JiGaYozZzqQXCR15DKFB6olpDIGtSXxTvMkhuwgE61G8m4rnpkLi7elVZAbtsifU`;
|
|
9
|
+
(0, vitest_1.beforeEach)(() => {
|
|
10
|
+
crypto = new crypto_1.HikCrypto(testAppSecret);
|
|
11
|
+
});
|
|
12
|
+
(0, vitest_1.it)('should format PKCS#1 private key correctly', () => {
|
|
13
|
+
crypto = new crypto_1.HikCrypto(testAppSecret);
|
|
14
|
+
console.log('✓ HikCrypto 实例化成功');
|
|
15
|
+
(0, vitest_1.expect)(crypto).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)('should encrypt simple string', () => {
|
|
18
|
+
const plaintext = 'test data';
|
|
19
|
+
const encrypted = crypto.encrypt(plaintext);
|
|
20
|
+
console.log('明文:', plaintext);
|
|
21
|
+
console.log('密文:', encrypted.substring(0, 50) + '...');
|
|
22
|
+
(0, vitest_1.expect)(encrypted).toBeDefined();
|
|
23
|
+
(0, vitest_1.expect)(typeof encrypted).toBe('string');
|
|
24
|
+
(0, vitest_1.expect)(encrypted.length).toBeGreaterThan(0);
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)('should encrypt and decrypt correctly', () => {
|
|
27
|
+
const plaintext = 'test data';
|
|
28
|
+
const encrypted = crypto.encrypt(plaintext);
|
|
29
|
+
const decrypted = crypto.decrypt(encrypted);
|
|
30
|
+
console.log('原文:', plaintext);
|
|
31
|
+
console.log('解密后:', decrypted);
|
|
32
|
+
(0, vitest_1.expect)(decrypted).toBe(plaintext);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('should encrypt JSON body', () => {
|
|
35
|
+
const body = {
|
|
36
|
+
deviceSerial: 'L12345678',
|
|
37
|
+
employeeNo: 'EMP001',
|
|
38
|
+
name: '张三'
|
|
39
|
+
};
|
|
40
|
+
const result = crypto.encryptBody(body);
|
|
41
|
+
console.log('要加密的对象:', body);
|
|
42
|
+
console.log('加密结果:', result.bodySecret.substring(0, 50) + '...');
|
|
43
|
+
(0, vitest_1.expect)(result).toHaveProperty('bodySecret');
|
|
44
|
+
(0, vitest_1.expect)(result.bodySecret).toBeDefined();
|
|
45
|
+
(0, vitest_1.expect)(typeof result.bodySecret).toBe('string');
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)('should encrypt query params', () => {
|
|
48
|
+
const params = {
|
|
49
|
+
deviceSerial: 'L12345678',
|
|
50
|
+
page: 1,
|
|
51
|
+
size: 20
|
|
52
|
+
};
|
|
53
|
+
const result = crypto.encryptParams(params);
|
|
54
|
+
console.log('要加密的参数:', params);
|
|
55
|
+
console.log('加密结果:', result.substring(0, 50) + '...');
|
|
56
|
+
(0, vitest_1.expect)(result).toBeDefined();
|
|
57
|
+
(0, vitest_1.expect)(typeof result).toBe('string');
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)('should handle long data with chunking', () => {
|
|
60
|
+
// 测试分块加密:超过 117 字节的数据
|
|
61
|
+
const longText = 'a'.repeat(300);
|
|
62
|
+
const encrypted = crypto.encrypt(longText);
|
|
63
|
+
const decrypted = crypto.decrypt(encrypted);
|
|
64
|
+
console.log('长度:', longText.length);
|
|
65
|
+
console.log('分块加密成功');
|
|
66
|
+
(0, vitest_1.expect)(decrypted).toBe(longText);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HikConfig } from './config';
|
|
2
|
+
export interface AppTokenInfo {
|
|
3
|
+
appAccessToken: string;
|
|
4
|
+
refreshAppToken: string;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
}
|
|
7
|
+
export interface UserTokenInfo {
|
|
8
|
+
userAccessToken: string;
|
|
9
|
+
refreshUserToken: string;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
teamNo: string;
|
|
12
|
+
personNo: string;
|
|
13
|
+
accountNo: string;
|
|
14
|
+
}
|
|
15
|
+
export interface UserCredentials {
|
|
16
|
+
userName: string;
|
|
17
|
+
password: string;
|
|
18
|
+
redirectUrl: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class AuthManager {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private readonly httpClient;
|
|
23
|
+
private readonly userCredentials;
|
|
24
|
+
private readonly crypto;
|
|
25
|
+
private appTokenInfo;
|
|
26
|
+
private userTokenInfo;
|
|
27
|
+
private appTokenPromise;
|
|
28
|
+
private userTokenPromise;
|
|
29
|
+
constructor(config: HikConfig, userCredentials: UserCredentials);
|
|
30
|
+
private encryptBody;
|
|
31
|
+
private decryptResponse;
|
|
32
|
+
getUserAccessToken(): Promise<string>;
|
|
33
|
+
private isUserTokenValid;
|
|
34
|
+
getAppAccessToken(): Promise<string>;
|
|
35
|
+
private isAppTokenValid;
|
|
36
|
+
private shouldRefreshAppToken;
|
|
37
|
+
private fetchNewAppToken;
|
|
38
|
+
private doFetchAppToken;
|
|
39
|
+
private refreshAppToken;
|
|
40
|
+
private doRefreshAppToken;
|
|
41
|
+
private fetchUserToken;
|
|
42
|
+
private doFetchUserToken;
|
|
43
|
+
private applyAuthCode;
|
|
44
|
+
clearAppToken(): void;
|
|
45
|
+
clearUserToken(): void;
|
|
46
|
+
getUserInfo(): {
|
|
47
|
+
teamNo: string;
|
|
48
|
+
personNo: string;
|
|
49
|
+
accountNo: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|