Commit 2d8054be18bf6ad6591703c6ab957777d137ecfe

Authored by 648540858
Committed by GitHub
2 parents 021b8207 5de358d3

Merge pull request #32 from lawrencehj/master

优化录像列表获取算法等
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
1 1 /*
2   -* Conditions Of Use
3   -*
4   -* This software was developed by employees of the National Institute of
5   -* Standards and Technology (NIST), an agency of the Federal Government.
6   -* Pursuant to title 15 Untied States Code Section 105, works of NIST
7   -* employees are not subject to copyright protection in the United States
8   -* and are considered to be in the public domain. As a result, a formal
9   -* license is not needed to use the software.
10   -*
11   -* This software is provided by NIST as a service and is expressly
12   -* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13   -* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14   -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15   -* AND DATA ACCURACY. NIST does not warrant or make any representations
16   -* regarding the use of the software or the results thereof, including but
17   -* not limited to the correctness, accuracy, reliability or usefulness of
18   -* the software.
19   -*
20   -* Permission to use this software is contingent upon your acceptance
21   -* of the terms of this agreement
22   -*
23   -* .
24   -*
25   -*/
  2 + * Conditions Of Use
  3 + *
  4 + * This software was developed by employees of the National Institute of
  5 + * Standards and Technology (NIST), an agency of the Federal Government.
  6 + * Pursuant to title 15 Untied States Code Section 105, works of NIST
  7 + * employees are not subject to copyright protection in the United States
  8 + * and are considered to be in the public domain. As a result, a formal
  9 + * license is not needed to use the software.
  10 + *
  11 + * This software is provided by NIST as a service and is expressly
  12 + * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
  13 + * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
  14 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
  15 + * AND DATA ACCURACY. NIST does not warrant or make any representations
  16 + * regarding the use of the software or the results thereof, including but
  17 + * not limited to the correctness, accuracy, reliability or usefulness of
  18 + * the software.
  19 + *
  20 + * Permission to use this software is contingent upon your acceptance
  21 + * of the terms of this agreement
  22 + *
  23 + * .
  24 + *
  25 + */
26 26 package com.genersoft.iot.vmp.gb28181.auth;
27 27  
28 28 import java.security.MessageDigest;
... ... @@ -42,18 +42,18 @@ import gov.nist.core.InternalErrorHandler;
42 42  
43 43 /**
44 44 * Implements the HTTP digest authentication method server side functionality.
45   - *
  45 + *
46 46 * @author M. Ranganathan
47 47 * @author Marc Bednarek
48 48 */
49 49  
50 50 public class DigestServerAuthenticationHelper {
51   -
  51 +
52 52 private MessageDigest messageDigest;
53   -
  53 +
54 54 public static final String DEFAULT_ALGORITHM = "MD5";
55 55 public static final String DEFAULT_SCHEME = "Digest";
56   -
  56 +
57 57  
58 58  
59 59  
... ... @@ -63,11 +63,11 @@ public class DigestServerAuthenticationHelper {
63 63  
64 64 /**
65 65 * Default constructor.
66   - * @throws NoSuchAlgorithmException
  66 + * @throws NoSuchAlgorithmException
67 67 */
68   - public DigestServerAuthenticationHelper()
69   - throws NoSuchAlgorithmException {
70   - messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
  68 + public DigestServerAuthenticationHelper()
  69 + throws NoSuchAlgorithmException {
  70 + messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
71 71 }
72 72  
73 73 public static String toHexString(byte b[]) {
... ... @@ -79,7 +79,7 @@ public class DigestServerAuthenticationHelper {
79 79 }
80 80 return new String(c);
81 81 }
82   -
  82 +
83 83 /**
84 84 * Generate the challenge string.
85 85 *
... ... @@ -121,34 +121,34 @@ public class DigestServerAuthenticationHelper {
121 121 *
122 122 * @param request - the request to authenticate.
123 123 * @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password.
124   - *
  124 + *
125 125 * @return true if authentication succeded and false otherwise.
126 126 */
127 127 public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) {
128   - AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
  128 + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
129 129 if ( authHeader == null ) return false;
130 130 String realm = authHeader.getRealm();
131 131 String username = authHeader.getUsername();
132   -
  132 +
133 133 if ( username == null || realm == null ) {
134 134 return false;
135 135 }
136   -
  136 +
137 137 String nonce = authHeader.getNonce();
138 138 URI uri = authHeader.getURI();
139 139 if (uri == null) {
140 140 return false;
141 141 }
142   -
143 142  
144   -
  143 +
  144 +
145 145 String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
146 146 String HA1 = hashedPassword;
147 147  
148   -
  148 +
149 149 byte[] mdbytes = messageDigest.digest(A2.getBytes());
150 150 String HA2 = toHexString(mdbytes);
151   -
  151 +
152 152 String cnonce = authHeader.getCNonce();
153 153 String KD = HA1 + ":" + nonce;
154 154 if (cnonce != null) {
... ... @@ -158,7 +158,7 @@ public class DigestServerAuthenticationHelper {
158 158 mdbytes = messageDigest.digest(KD.getBytes());
159 159 String mdString = toHexString(mdbytes);
160 160 String response = authHeader.getResponse();
161   -
  161 +
162 162  
163 163 return mdString.equals(response);
164 164 }
... ... @@ -168,11 +168,11 @@ public class DigestServerAuthenticationHelper {
168 168 *
169 169 * @param request - the request to authenticate.
170 170 * @param pass -- the plain text password.
171   - *
  171 + *
172 172 * @return true if authentication succeded and false otherwise.
173 173 */
174 174 public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
175   - AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
  175 + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
176 176 if ( authHeader == null ) return false;
177 177 String realm = authHeader.getRealm().trim();
178 178 String username = authHeader.getUsername().trim();
... ... @@ -184,7 +184,7 @@ public class DigestServerAuthenticationHelper {
184 184 String nonce = authHeader.getNonce();
185 185 URI uri = authHeader.getURI();
186 186 if (uri == null) {
187   - return false;
  187 + return false;
188 188 }
189 189 // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
190 190 String qop = authHeader.getQop();
... ... @@ -233,6 +233,6 @@ public class DigestServerAuthenticationHelper {
233 233 String response = authHeader.getResponse();
234 234 System.out.println("response: " + response);
235 235 return mdString.equals(response);
236   -
  236 +
237 237 }
238 238 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -409,7 +409,12 @@ public class SIPCommander implements ISIPCommander {
409 409 try {
410 410 MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
411 411 String ssrc = streamSession.createPlayBackSsrc();
412   - String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
  412 + String streamId = null;
  413 + if (rtpEnable) {
  414 + streamId = String.format("gb_playback_%s_%s", device.getDeviceId(), channelId);
  415 + }else {
  416 + streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
  417 + }
413 418 // 添加订阅
414 419 JSONObject subscribeKey = new JSONObject();
415 420 subscribeKey.put("app", "rtp");
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
... ... @@ -342,6 +342,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
342 342 try {
343 343 // 回复200 OK
344 344 responseAck(evt);
  345 + String seqNo = String.valueOf(System.currentTimeMillis());
345 346 RecordInfo recordInfo = new RecordInfo();
346 347 Element rootElement = getRootElement(evt);
347 348 Element deviceIdElement = rootElement.element("DeviceID");
... ... @@ -396,32 +397,22 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
396 397 if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
397 398 // 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
398 399 String cacheKey = CACHE_RECORDINFO_KEY + deviceId + sn;
399   - // TODO 暂时直接操作redis存储,后续封装专用缓存接口,改为本地内存缓存
400   - if (redis.hasKey(cacheKey)) {
401   - List<RecordItem> previousList = (List<RecordItem>) redis.get(cacheKey);
402   - if (previousList != null && previousList.size() > 0) {
403   - recordList.addAll(previousList);
404   - }
405   - // 本分支表示录像列表被拆包,且加上之前的数据还是不够,保存缓存返回,等待下个包再处理
406   - if (recordList.size() < recordInfo.getSumNum()) {
407   - logger.info("已获取" + recordList.size() + "项录像数据,共" + recordInfo.getSumNum() + "项");
408   - redis.set(cacheKey, recordList, 90);
409   - return;
410   - } else {
411   - // 本分支表示录像被拆包,但加上之前的数据够足够,返回响应
412   - // 因设备心跳有监听redis过期机制,为提高性能,此处手动删除
413   - logger.info("录像数据已全部获取");
414   - redis.del(cacheKey);
415   - }
416   - } else {
417   - // 本分支有两种可能:1、录像列表被拆包,且是第一个包,直接保存缓存返回,等待下个包再处理
418   - // 2、之前有包,但超时清空了,那么这次sn批次的响应数据已经不完整,等待过期时间后redis自动清空数据
419   - logger.info("已获取" + recordList.size() + "项录像数据,共" + recordInfo.getSumNum() + "项");
420   - logger.info("等待后续的包...");
421 400  
422   - redis.set(cacheKey, recordList, 90);
  401 + redis.set(cacheKey + "_" + seqNo, recordList, 90);
  402 + List<Object> cacheKeys = redis.scan(cacheKey + "_*");
  403 + List<RecordItem> totalRecordList = new ArrayList<RecordItem>();
  404 + for (int i = 0; i < cacheKeys.size(); i++) {
  405 + totalRecordList.addAll((List<RecordItem>) redis.get(cacheKeys.get(i).toString()));
  406 + }
  407 + if (totalRecordList.size() < recordInfo.getSumNum()) {
  408 + logger.info("已获取" + totalRecordList.size() + "项录像数据,共" + recordInfo.getSumNum() + "项");
423 409 return;
424 410 }
  411 + logger.info("录像数据已全部获取,共" + recordInfo.getSumNum() + "项");
  412 + recordInfo.setRecordList(totalRecordList);
  413 + for (int i = 0; i < cacheKeys.size(); i++) {
  414 + redis.del(cacheKeys.get(i).toString());
  415 + }
425 416 }
426 417 // 自然顺序排序, 元素进行升序排列
427 418 recordInfo.getRecordList().sort(Comparator.naturalOrder());
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
... ... @@ -144,10 +144,10 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor {
144 144 storager.updateDevice(device);
145 145 publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
146 146  
147   - // 只有第一次注册才更新通道
148   - if (!exists) {
  147 + // 重新注册更新设备和通道,以免设备替换或更新后信息无法更新
  148 + //if (!exists) {
149 149 handler.onRegister(device);
150   - }
  150 + //}
151 151 } else if (registerFlag == 2) {
152 152 logger.info("注销成功! deviceId:" + device.getDeviceId());
153 153 publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
... ...
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
... ... @@ -43,7 +43,6 @@ public interface DeviceMapper {
43 43 ")")
44 44 int add(Device device);
45 45  
46   -
47 46 @Update(value = {" <script>" +
48 47 "UPDATE device " +
49 48 "SET deviceId='${deviceId}'" +
... ...
src/main/resources/application-dev.yml
... ... @@ -13,12 +13,12 @@ spring:
13 13 timeout: 10000
14 14 # [不可用] jdbc数据库配置, 暂不支持
15 15 datasource:
16   - # name: eiot
17   - # url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
18   - # username:
19   - # password:
20   - # type: com.alibaba.druid.pool.DruidDataSource
21   - # driver-class-name: com.mysql.jdbc.Driver
  16 + #name: eiot
  17 + #url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
  18 + #username:
  19 + #password:
  20 + #type: com.alibaba.druid.pool.DruidDataSource
  21 + #driver-class-name: com.mysql.jdbc.Driver
22 22 name: eiot
23 23 url: jdbc:sqlite::resource:wvp.sqlite
24 24 username:
... ...
src/main/resources/application.yml
1 1 spring:
2 2 profiles:
3   - active: local
4 3 \ No newline at end of file
  4 + active: dev
5 5 \ No newline at end of file
... ...
web_src/src/components/gb28181/devicePlayer.vue
1 1 <template>
2 2 <div id="devicePlayer" v-loading="isLoging">
3   -
  3 +
4 4 <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">
5 5 <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> -->
6 6 <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></player>
... ... @@ -125,7 +125,7 @@
125 125 <p>采样率: {{item.sample_rate}}</p>
126 126 </div>
127 127 </div>
128   -
  128 +
129 129 </div>
130 130  
131 131 </el-tab-pane>
... ... @@ -237,7 +237,7 @@ export default {
237 237 console.log(val)
238 238 },
239 239 play: function (streamInfo, hasAudio) {
240   -
  240 +
241 241 this.hasaudio = hasAudio;
242 242 this.isLoging = false;
243 243 this.videoUrl = streamInfo.ws_flv;
... ... @@ -319,7 +319,7 @@ export default {
319 319 }
320 320 this.convertKey = ''
321 321 },
322   -
  322 +
323 323 copySharedInfo: function (data) {
324 324 console.log('复制内容:' + data);
325 325 this.coverPlaying = false;
... ...