Commit 2d8054be18bf6ad6591703c6ab957777d137ecfe
Committed by
GitHub
Merge pull request #32 from lawrencehj/master
优化录像列表获取算法等
Showing
8 changed files
with
80 additions
and
85 deletions
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
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
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; | ... | ... |