Commit 847557bf authored by 高东东's avatar 高东东

新增项目

parents
# Node rules:
## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
## Dependency directory
## Commenting this out is preferred by some people, see
## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
node_modules
# Book build output
_book
# git push scripts
zxPush
zxPushDev
zxPull
# eBook build output
*.epub
*.mobi
*.pdf
.vsnode_modules/
//伪造测试数据
const devices=[
{
"deviceId": "37010200491320000001",
"name": "浪潮仓外",
"manufacturer": "Hikvision",
"model": "iDS-2DE7223IX-A/S1",
"firmware": "V5.5.23",
"transport": "TCP",
"host": {
"ip": "10.200.64.194",
"port": 58206,
"address": "10.200.64.194:58206"
},
"online": 1,
"channelMap": {
"37010200491320000001": {
"channelId": "37010200491320000001",
"name": "济南第一粮库大门",
"manufacture": "Hikvision",
"model": "IP Camera",
"owner": "Owner",
"civilCode": "CivilCode",
"block": null,
"address": "Address",
"parental": 0,
"parentId": null,
"safetyWay": 0,
"registerWay": 1,
"certNum": null,
"certifiable": 0,
"errCode": 0,
"endTime": null,
"secrecy": "0",
"ipAddress": null,
"port": 0,
"password": null,
"status": 1,
"longitude": 0.0,
"latitude": 0.0
}
}
},
{
"deviceId": "34020000001320000004",
"name": null,
"manufacturer": "Dahua",
"model": "DH-SD-59D120T-HN",
"firmware": "V2.422.3.R.2016-11-30",
"transport": "UDP",
"host": {
"ip": "10.200.64.195",
"port": 5060,
"address": "10.200.64.195:5060"
},
"online": 0,
"channelMap": {
"34020000001320000004": {
"channelId": "34020000001320000004",
"name": "Camera 01",
"manufacture": "Dahua",
"model": "DH-SD-59D120T-HN",
"owner": "0",
"civilCode": "6532",
"block": null,
"address": "axy",
"parental": 0,
"parentId": null,
"safetyWay": 0,
"registerWay": 1,
"certNum": null,
"certifiable": 0,
"errCode": 0,
"endTime": null,
"secrecy": "0",
"ipAddress": null,
"port": 0,
"password": null,
"status": 1,
"longitude": 0.0,
"latitude": 0.0
},
"34020000001320000005": {
"channelId": "34020000001320000005",
"name": "Camera 011",
"manufacture": "Dahua1",
"model": "DH-SD-59D120T-HN",
"owner": "0",
"civilCode": "6532",
"block": null,
"address": "axy1",
"parental": 0,
"parentId": null,
"safetyWay": 0,
"registerWay": 1,
"certNum": null,
"certifiable": 0,
"errCode": 0,
"endTime": null,
"secrecy": "0",
"ipAddress": null,
"port": 0,
"password": null,
"status": 1,
"longitude": 0.0,
"latitude": 0.0
}
}
}
]
const recordData={
"deviceId": "34020000001320000001",
"name": "假数据中化梁抵库",
"sumNum": 2,
"recordList": [
{
"deviceId": "34020000001320000001",
"name": "假数据中化梁抵库",
"filePath": "1589112259_1589112788",
"address": "Address 1",
"startTime": "2020-05-10 20:04:19",
"endTime": "2020-05-10 20:13:08",
"secrecy": 0,
"type": "time",
"recorderId": null
},
{
"deviceId": "34020000001320000001",
"name": "假数据中化梁抵库",
"filePath": "1589112788_1589113318",
"address": "Address 1",
"startTime": "2020-05-10 20:13:08",
"endTime": "2020-05-10 20:21:58",
"secrecy": 0,
"type": "time",
"recorderId": null
}
]
}
export default{
devices,//设备列表
recordData,
}
\ No newline at end of file
MIT License
Copyright (c) 2020 KKKKK5G
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# default
### Where to change
```
./global.js 中更改相关配置,需要配合wvp gb28181工程使用
```
## 2020-05-14
1.对接云台控制
2.对接录像查询
## 效果展示
![Image text](screenshot/deviceList.png)
![Image text](screenshot/deviceChannel.png)
![Image text](screenshot/mediaPlayer.png)
![Image text](screenshot/record.png)
![Image text](screenshot/ptz.png)
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/app'
]
}
/**---------------GB28181服务器信息配置-------------------**/
const serverip="172.16.10.98:8082"
const host = 'http://' + serverip;
/**------------------------------------------------------**/
/**---------------ZLMediaKit流媒体服务器配置---------------**/
const ZLServerIp="172.16.10.98:8080";
const ZLSecret="035c73f7-bb6b-4889-a715-d9eb2d1925cc";
const ZLHost='http://' + ZLServerIp+"/index/api";
const baseMediaUrl='ws://' + ZLServerIp + '/';
/**------------------------------------------------------**/
//生成GB28181平台接口url
function genGb28181Url(method){
return host+method;
}
//生成ZLMediaKit平台接口url
function genApiUrl(method){
return ZLHost+method+"?secret="+ZLSecret;
}
export default{
genApiUrl,
genGb28181Url,
baseMediaUrl,
ZLServerIp
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "default",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@liveqing/liveplayer": "^1.9.6",
"axios": "^0.19.2",
"core-js": "^2.6.5",
"echarts": "^4.7.0",
"element-ui": "^2.10.1",
"vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
"vue-router": "^3.1.6"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.8.0",
"@vue/cli-service": "^3.8.0",
"vue-template-compiler": "^2.6.11"
}
}
module.exports = {
plugins: {
autoprefixer: {}
}
}
<?xml version="1.0" encoding="utf-8"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>default</title>
<script type="text/javascript" src="/dist/liveplayer-lib.min.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but default doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<?xml version="1.0" encoding="utf-8"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE HTML>
<html>
<head>
<title>liveplayer</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
<script type="text/javascript" src="liveplayer-element.min.js"></script>
<script type="text/javascript">
window.onload = function() {
}
window.addEventListener("message", function(event) {
var data = event.data;
switch (data.cmd) {
case 'switchUrl':
// 处理业务逻辑
console.log("收到消息:"+JSON.stringify(data.params));
var player = document.getElementById('player01');
player.setAttribute("video-url",data.params["path"]);
break;
}
});
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
}
function onError(event){
console.log("播放器错误:"+JSON.stringify(event));
}
function sendMsgToParent(cmd,data){
window.parent.postMessage({
cmd: cmd,
params: {
success: true,
data: data
}
}, '*');
}
</script>
</head>
<body>
<live-player id="player01" live="true" stretch="true" show-custom-button="false" autoplay error="onError">
</live-player>
<script>
var videoPath = getQueryVariable("url");
console.log('播放地址:' + videoPath);
var player = document.getElementById('player01');
player.setAttribute("video-url", videoPath);
</script>
</body>
</html>
<template>
<div id="app">
<el-container>
<el-header>
<el-menu router :default-active="this.$route.path" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
<el-menu-item index="/">控制台</el-menu-item>
<el-menu-item index="/videoList">视频广场</el-menu-item>
<!-- <el-menu-item index="/videoReplay">录像回看</el-menu-item> -->
<el-menu-item index="4">通道配置</el-menu-item>
</el-menu>
</el-header>
<el-main><router-view></router-view></el-main>
<!-- <el-footer style="position: absolute; bottom: 0; width: 100%;">ZLMediaKit-VUE_UI v1</el-footer> -->
</el-container>
</div>
</template>
<script>
export default {
name: 'app',
components: {}
};
</script>
<style>
html,
body,
#app {
margin: 0 0;
background-color: #e9eef3;
height: 100%;
}
.el-header,
.el-footer {
/* background-color: #b3c0d1; */
color: #333;
text-align: center;
line-height: 60px;
}
.el-main {
background-color: #e9eef3;
color: #333;
text-align: center;
padding-top: 0px !important;
}
</style>
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
<template>
<div id="app">
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
<span style="font-size: 1rem; font-weight: bold;">控制台</span>
<div style="position: absolute; right: 1rem; top: 0.3rem;">
<el-popover placement="bottom" width="750" height="300" trigger="click">
<div style="height: 600px;overflow:auto;">
<table class="table-c" cellspacing="0">
<tr v-for="(value, key, index) in serverConfig">
<td style="width: 18rem; text-align: right;">{{ key }}</td>
<td style="width: 33rem; text-align:left">{{ value }}</td>
</tr>
</table>
</div>
<el-button type="primary" slot="reference" size="mini" @click="getServerConfig()">查看服务器配置</el-button>
</el-popover>
<el-button style="margin-left: 1rem;" type="danger" size="mini" @click="reStartServer()">重启服务器</el-button>
</div>
</div>
<el-row :gutter="30">
<el-col :span="12"><div class="control-table" id="ThreadsLoad">table1</div></el-col>
<el-col :span="12"><div class="control-table" id="WorkThreadsLoad">table2</div></el-col>
</el-row>
<el-table :data="allSessionData" style="margin-top: 1rem;">
<el-table-column prop="peer_ip" label="远端"></el-table-column>
<el-table-column prop="local_ip" label="本地"></el-table-column>
<el-table-column prop="typeid" label="类型"></el-table-column>
<el-table-column align="right">
<template slot="header" slot-scope="scope">
<el-button icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button>
</template>
<template slot-scope="scope">
<el-button @click.native.prevent="deleteRow(scope.$index, allSessionData)" type="text" size="small">移除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import echarts from 'echarts';
export default {
name: 'app',
components: {
echarts
},
data() {
return {
tableOption: {
// legend: {},
xAxis: {},
yAxis: {},
label: {},
tooltip: {},
dataZoom: [],
series: []
},
table1Option: {
// legend: {},
xAxis: {},
yAxis: {},
label: {},
tooltip: {},
series: []
},
mChart: null,
mChart1: null,
charZoomStart: 0,
charZoomEnd: 100,
chartInterval: 0, //更新图表统计图定时任务标识
allSessionData: [],
visible: false,
serverConfig: {}
};
},
mounted() {
this.getAllSession();
this.initTable();
this.updateData();
this.chartInterval = setInterval(this.updateData, 3000);
},
destroyed() {
clearInterval(this.chartInterval); //释放定时任务
},
methods: {
updateData: function() {
this.getThreadsLoad();
},
/**
* 获取线程状态
*/
getThreadsLoad: function() {
let that = this;
this.$axios({
method: 'get',
url: this.$global.genApiUrl('/getThreadsLoad')
}).then(function(res) {
if (res.data.code == 0) {
that.tableOption.xAxis.data.push(new Date().toLocaleTimeString());
that.table1Option.xAxis.data.push(new Date().toLocaleTimeString());
for (var i = 0; i < res.data.data.length; i++) {
if (that.tableOption.series[i] === undefined) {
let data = {
data: [],
type: 'line'
};
let data1 = {
data: [],
type: 'line'
};
data.data.push(res.data.data[i].delay);
data1.data.push(res.data.data[i].load);
that.tableOption.series.push(data);
that.table1Option.series.push(data1);
} else {
that.tableOption.series[i].data.push(res.data.data[i].delay);
that.table1Option.series[i].data.push(res.data.data[i].load);
}
}
that.tableOption.dataZoom[0].start = that.charZoomStart;
that.tableOption.dataZoom[0].end = that.charZoomEnd;
that.table1Option.dataZoom[0].start = that.charZoomStart;
that.table1Option.dataZoom[0].end = that.charZoomEnd;
//that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
that.myChart.setOption(that.tableOption, true);
// that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
that.myChart1.setOption(that.table1Option, true);
}
});
},
initTable: function() {
let that = this;
this.tableOption.xAxis = {
type: 'category',
data: [], // x轴数据
name: '时间', // x轴名称
// x轴名称样式
nameTextStyle: {
fontWeight: 300,
fontSize: 15
}
};
this.tableOption.yAxis = {
type: 'value',
name: '延迟率', // y轴名称
boundaryGap: [0, '100%'],
max: 100,
axisLabel: {
show: true,
interval: 'auto',
formatter: '{value} %'
},
// y轴名称样式
nameTextStyle: {
fontWeight: 300,
fontSize: 15
}
};
this.tableOption.dataZoom = [
{
show: true,
start: this.charZoomStart,
end: this.charZoomEnd
}
];
this.myChart = echarts.init(document.getElementById('ThreadsLoad'));
this.myChart.setOption(this.tableOption);
this.myChart.on('dataZoom', function(event) {
if (event.batch) {
that.charZoomStart = event.batch[0].start;
that.charZoomEnd = event.batch[0].end;
} else {
that.charZoomStart = event.start;
that.charZoomEnd = event.end;
}
});
this.table1Option.xAxis = {
type: 'category',
data: [], // x轴数据
name: '时间', // x轴名称
// x轴名称样式
nameTextStyle: {
fontWeight: 300,
fontSize: 15
}
};
this.table1Option.yAxis = {
type: 'value',
name: '负载率', // y轴名称
boundaryGap: [0, '100%'],
max: 100,
axisLabel: {
show: true,
interval: 'auto',
formatter: '{value} %'
},
// y轴名称样式
nameTextStyle: {
fontWeight: 300,
fontSize: 15
}
};
this.table1Option.dataZoom = [
{
show: true,
start: this.charZoomStart,
end: this.charZoomEnd
}
];
this.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
this.myChart1.setOption(this.table1Option);
this.myChart1.on('dataZoom', function(event) {
if (event.batch) {
that.charZoomStart = event.batch[0].start;
that.charZoomEnd = event.batch[0].end;
} else {
that.charZoomStart = event.start;
that.charZoomEnd = event.end;
}
});
},
getAllSession: function() {
let that = this;
that.allSessionData = [];
console.log("地址:"+this.$global.genApiUrl('/getAllSession'));
this.$axios({
method: 'get',
url: this.$global.genApiUrl('/getAllSession')
}).then(function(res) {
res.data.data.forEach(item => {
let data = {
peer_ip: item.peer_ip,
local_ip: item.local_ip,
typeid: item.typeid,
id: item.id
};
that.allSessionData.push(data);
});
});
},
getServerConfig: function() {
let that = this;
this.$axios({
method: 'get',
url: this.$global.genApiUrl('/getServerConfig')
}).then(function(res) {
that.serverConfig = res.data.data[0];
that.visible = true;
});
},
reStartServer: function() {
let that = this;
this.$confirm('此操作将重启媒体服务器, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let that = this;
this.$axios({
method: 'get',
url: this.$global.genApiUrl('/restartServer')
}).then(function(res) {
that.getAllSession();
if (res.data.code == 0) {
that.$message({
type: 'success',
message: '操作完成'
});
}
});
});
},
deleteRow: function(index, tabledata) {
let that = this;
this.$confirm('此操作将断开该通信链路, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
that.deleteSession(tabledata[index].id);
})
.catch(() => {
console.log('id:' + JSON.stringify(tabledata[index]));
this.$message({
type: 'info',
message: '已取消删除'
});
});
console.log(JSON.stringify(tabledata[index]));
},
deleteSession: function(id) {
let that = this;
this.$axios({
method: 'get',
url: this.$global.genApiUrl('/kick_session') + '&id=' + id
}).then(function(res) {
that.getAllSession();
that.$message({
type: 'success',
message: '删除成功!'
});
});
}
}
};
</script>
<style>
#app {
height: 100%;
}
.control-table {
background-color: #ffffff;
height: 25rem;
}
.table-c {
border-right: 1px solid #dcdcdc;
border-bottom: 1px solid #dcdcdc;
}
.table-c td {
border-left: 1px solid #dcdcdc;
border-top: 1px solid #dcdcdc;
padding: 0.2rem;
}
.el-table {
width: 99.9% !important;
}
</style>
<template>
<div id="devicePlayer">
<el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="stop()">
<LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" fluent autoplay live stretch></LivePlayer>
<div id="shared" style="text-align: right; margin-top: 1rem;">
<el-tabs v-model="tabActiveName">
<el-tab-pane label="媒体流信息" name="media">
<div style="margin-bottom: 0.5rem;">
<el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>
<!-- <el-button type="primary" size="small" @click="startRecord()">录制</el-button> -->
<!-- <el-button type="primary" size="small" @click="stopRecord()">停止录制</el-button> -->
</div>
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedUrl)"></el-input>
</div>
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedIframe)"></el-input>
</div>
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedRtmp)"></el-input>
</div>
</el-tab-pane>
<!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
<el-tab-pane label="录像查询" name="second">
<el-date-picker v-model="videoHistory.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间"
@change="recordList()"></el-date-picker>
<el-date-picker v-model="videoHistory.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间"
@change="recordList()"></el-date-picker>
<el-table :data="videoHistory.searchHistoryResult" style="width: 100%">
<el-table-column label="名称" prop="name" width="150"></el-table-column>
<el-table-column label="文件" prop="filePath" width="300"></el-table-column>
<el-table-column label="开始时间" prop="startTime" width="160"></el-table-column>
<el-table-column label="结束时间" prop="endTime" width="160"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="playRecord(false, scope.row)">播放</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!--遥控界面-->
<el-tab-pane label="云台控制" name="third">
<div style="display: flex; justify-content: center;">
<div class="control-wrapper">
<div class="control-btn control-top" @mousedown="ptzCamera(0, 1, 0)" @mouseup="ptzCamera(0, 0, 0)">
<i class="el-icon-caret-top"></i>
<div class="control-inner-btn control-inner"></div>
</div>
<div class="control-btn control-left" @mousedown="ptzCamera(1, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
<i class="el-icon-caret-left"></i>
<div class="control-inner-btn control-inner"></div>
</div>
<div class="control-btn control-bottom" @mousedown="ptzCamera(0, 2, 0)" @mouseup="ptzCamera(0, 0, 0)">
<i class="el-icon-caret-bottom"></i>
<div class="control-inner-btn control-inner"></div>
</div>
<div class="control-btn control-right" @mousedown="ptzCamera(2, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
<i class="el-icon-caret-right"></i>
<div class="control-inner-btn control-inner"></div>
</div>
<div class="control-round">
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
</div>
<div style="position: absolute; left: 7.25rem; top: 2.25rem" @mousedown="ptzCamera(0, 0, 2)" @mouseup="ptzCamera(0, 0, 0)"><i
class="el-icon-zoom-in" style="font-size: 1.875rem;"></i></div>
<div style="position: absolute; left: 7.25rem; top: 4.25rem; font-size: 1.875rem;" @mousedown="ptzCamera(0, 0, 1)"
@mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-out"></i></div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-dialog>
</div>
</template>
<script>
import LivePlayer from '@liveqing/liveplayer'
import falsificationData from '../../../FalsificationData.js'
export default {
name: 'devicePlayer',
props: {},
components: {
LivePlayer
},
computed: {
getPlayerShared: function() {
let info = {
sharedUrl: window.location.host + '/' + this.videoUrl,
sharedIframe: '<iframe src="' + window.location.host + '/' + this.videoUrl + '"></iframe>',
sharedRtmp: this.videoUrl
};
return info;
}
},
created() {
this.videoHistory.searchHistoryResult = falsificationData.recordData.recordList;
},
data() {
return {
video:'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
videoUrl: '',
videoHistory: {
startTime: '',
endTime: '',
searchHistoryResult: [] //媒体流历史记录搜索结果
},
showVideoDialog: false,
normalssrc: '',
ssrc: '',
deviceId: '',
channelId: '',
tabActiveName: 'media'
};
},
methods: {
getVideoUrlBySsrc: function(ssrc) {
let hex_ssrc = parseInt(ssrc).toString(16);
let hex_ssrc_size = 8 - hex_ssrc.length;
for (var i = 0; i < hex_ssrc_size; i++) {
hex_ssrc = '0' + hex_ssrc;
}
console.log('hex_ssrc:' + hex_ssrc);
this.ssrc = hex_ssrc.toLocaleUpperCase();
let videoUrl = this.$global.baseMediaUrl + 'rtp/' + this.ssrc + '.flv?st=' + new Date()
.getTime();
return videoUrl;
},
play: function(normalssrc, deviceId, channelId) {
this.normalssrc = normalssrc;
this.deviceId = deviceId;
this.channelId = channelId;
this.videoUrl = this.getVideoUrlBySsrc(normalssrc);
this.showVideoDialog = true;
console.log(this.ssrc);
},
stop: function() {
console.log('关闭视频');
this.videoUrl = '';
this.$refs.videoPlayer.pause();
this.showVideoDialog = false;
let that = this;
this.$axios({
method: 'post',
url: this.$global.genGb28181Url('/api/play/' + this.ssrc + '/stop')
}).then(function(res) {
console.log(JSON.stringify(res));
});
this.$axios({
method: 'post',
url: this.$global.genGb28181Url('/api/playback/' + this.ssrc + '/stop')
}).then(function(res) {
console.log(JSON.stringify(res));
});
},
copySharedInfo: function(data) {
console.log('复制内容:' + data);
let _this = this;
this.$copyText(data).then(
function(e) {
_this.$message({
showClose: true,
message: '复制成功',
type: 'success'
});
},
function(e) {
_this.$message({
showClose: true,
message: '复制失败,请手动复制',
type: 'error'
});
}
);
},
recordList: function() {
if (!this.videoHistory.startTime || !this.videoHistory.endTime) {
return;
}
let that = this;
this.$axios({
method: 'get',
url: this.$global.genGb28181Url('/api/record/' + this.deviceId + '/' + this.channelId) + '?startTime=' + this.videoHistory
.startTime + '&endTime=' + this.videoHistory.endTime
}).then(function(res) {
console.log(JSON.stringify(res));
}).catch(function(e) {
that.videoHistory.searchHistoryResult = falsificationData.recordData;
});
},
playRecord: function(isBackLive, rowData) {
let that = this;
if(isBackLive){
this.videoUrl=this.getVideoUrlBySsrc(this.normalssrc);
return;
}
this.$axios({
method: 'get',
url: this.$global.genGb28181Url(
'/api/playback/' + this.deviceId + '/' + this.channelId + '?startTime=' + rowData.startTime + '&endTime=' +
rowData.endTime
)
}).then(function(res) {
let ssrc = res.data.ssrc;
that.videoUrl = that.getVideoUrlBySsrc(ssrc);
//that.videoUrl='http://hls.cntv.kcdnvip.com/asp/hls/main/0303000a/3/default/f466089412c04a759c5515dbfcc3ac3d/main.m3u8?maxbr=2048';
});
},
ptzCamera: function(leftRight, upDown, zoom) {
console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom);
let that = this;
this.$axios({
method: 'post',
url: this.$global.genGb28181Url(
'/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown +
'&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50'
)
}).then(function(res) {});
},
//////////////////////播放器事件处理//////////////////////////
videoError:function(e){
console.log("播放器错误:"+JSON.stringify(e));
}
}
};
</script>
<style>
.control-wrapper {
position: relative;
width: 6.25rem;
height: 6.25rem;
max-width: 6.25rem;
max-height: 6.25rem;
margin: 0 auto;
border-radius: 100%;
float: left;
}
.control-btn {
display: flex;
justify-content: center;
position: absolute;
width: 44%;
height: 44%;
border-radius: 5px;
border: 1px solid #78aee4;
box-sizing: border-box;
transition: all 0.3s linear;
}
.control-btn i {
font-size: 20px;
color: #78aee4;
display: flex;
justify-content: center;
align-items: center;
}
.control-round {
position: absolute;
top: 21%;
left: 21%;
width: 58%;
height: 58%;
background: #fff;
border-radius: 100%;
}
.control-round-inner {
position: absolute;
left: 15%;
top: 15%;
display: flex;
justify-content: center;
align-items: center;
width: 70%;
height: 70%;
font-size: 40px;
color: #78aee4;
border: 1px solid #78aee4;
border-radius: 100%;
transition: all 0.3s linear;
}
.control-inner-btn {
position: absolute;
width: 60%;
height: 60%;
background: #fafafa;
}
.control-top {
top: -8%;
left: 27%;
transform: rotate(-45deg);
border-radius: 5px 100% 5px 0;
}
.control-top i {
transform: rotate(45deg);
border-radius: 5px 100% 5px 0;
}
.control-top .control-inner {
left: -1px;
bottom: 0;
border-top: 1px solid #78aee4;
border-right: 1px solid #78aee4;
border-radius: 0 100% 0 0;
}
.control-top .fa {
transform: rotate(45deg) translateY(-7px);
}
.control-left {
top: 27%;
left: -8%;
transform: rotate(45deg);
border-radius: 5px 0 5px 100%;
}
.control-left i {
transform: rotate(-45deg);
}
.control-left .control-inner {
right: -1px;
top: -1px;
border-bottom: 1px solid #78aee4;
border-left: 1px solid #78aee4;
border-radius: 0 0 0 100%;
}
.control-left .fa {
transform: rotate(-45deg) translateX(-7px);
}
.control-right {
top: 27%;
right: -8%;
transform: rotate(45deg);
border-radius: 5px 100% 5px 0;
}
.control-right i {
transform: rotate(-45deg);
}
.control-right .control-inner {
left: -1px;
bottom: -1px;
border-top: 1px solid #78aee4;
border-right: 1px solid #78aee4;
border-radius: 0 100% 0 0;
}
.control-right .fa {
transform: rotate(-45deg) translateX(7px);
}
.control-bottom {
left: 27%;
bottom: -8%;
transform: rotate(45deg);
border-radius: 0 5px 100% 5px;
}
.control-bottom i {
transform: rotate(-45deg);
}
.control-bottom .control-inner {
top: -1px;
left: -1px;
border-bottom: 1px solid #78aee4;
border-right: 1px solid #78aee4;
border-radius: 0 0 100% 0;
}
.control-bottom .fa {
transform: rotate(-45deg) translateY(7px);
}
</style>
<template>
<div id="app">
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
<span style="font-size: 1rem; font-weight: bold;">设备列表</span>
<div style="position: absolute; right: 1rem; top: 0.3rem;">
<el-button icon="el-icon-refresh-right" circle size="mini" @click="getDeviceList()"></el-button>
</div>
</div>
<devicePlayer ref="devicePlayer"></devicePlayer>
<!--设备列表-->
<el-table :data="deviceList" border style="width: 100%">
<el-table-column prop="name" label="名称" width="180" align="center">
</el-table-column>
<el-table-column prop="deviceId" label="设备编号" width="240" align="center">
</el-table-column>
<el-table-column label="地址" width="180" align="center">
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.host.address }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="manufacturer" label="厂家" align="center">
</el-table-column>
<el-table-column prop="model" label="固件版本" align="center">
</el-table-column>
<el-table-column prop="transport" label="通讯方式" align="center">
</el-table-column>
<el-table-column label="状态" width="180" align="center">
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.online==1?'在线' :'离线'}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="240" align="center">
<template slot-scope="scope">
<el-button size="mini" @click="refDevice(scope.row)">刷新</el-button>
<el-button size="mini" @click="currentDevice=scope.row;showDevicePushConfirm=true">查看通道</el-button>
</template>
</el-table-column>
</el-table>
<!--推流信息确认框-->
<el-dialog title="通道列表" :visible.sync="showDevicePushConfirm" width="40%">
<el-table :data="getcurrentDeviceChannels" style="width: 100%">
<el-table-column prop="channelId" label="通道编号" width="210">
</el-table-column>
<el-table-column prop="name" label="通道名称" width="180">
</el-table-column>
<el-table-column prop="address" label="地址">
</el-table-column>
<el-table-column label="操作" width="240" align="center">
<template slot-scope="scope">
<el-button size="mini" @click="sendDevicePush(scope.row)">预览视频</el-button>
<!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import devicePlayer from './gb28181/devicePlayer.vue'
export default {
name: 'app',
components: {
devicePlayer,
},
data() {
return {
deviceList: [], //设备列表
currentDevice: {}, //当前操作设备对象
showDevicePushConfirm: false, //是否显示推流确认框
videoComponentList: [],
currentPlayerInfo: {}, //当前播放对象
updateLooper: 0, //数据刷新轮训标志
};
},
computed: {
getcurrentDeviceChannels: function() {
let data = this.currentDevice['channelMap'];
let channels = null;
if (data) {
channels = Object.keys(data).map(key => {
return data[key];
});
}
console.log("数据:" + JSON.stringify(channels));
return channels;
}
},
mounted() {
this.initData();
this.updateLooper = setInterval(this.initData, 10000);
},
destroyed() {
this.$destroy('videojs');
clearTimeout(this.updateLooper);
},
methods: {
initData: function() {
this.getDeviceList();
},
getDeviceList: function() {
let that = this;
this.$axios({
method: 'get',
url: this.$global.genGb28181Url('/api/devices')
}).then(function(res) {
that.deviceList = res.data;
}).catch(function(error) {
console.log("请求失败,使用假数据");
});
},
//gb28181平台对接
//刷新设备信息
refDevice: function(itemData) {
///api/devices/{deviceId}/sync
console.log("刷新对应设备:" + itemData.deviceId);
this.$axios({
method: 'post',
url: this.$global.genGb28181Url('/api/devices/' + itemData.deviceId + '/sync')
}).then(function(res) {
// console.log("刷新设备结果:"+JSON.stringify(res));
}).catch(function(e) {
that.$message({
showClose: true,
message: '请求成功',
type: 'success'
});
});;
},
//通知设备上传媒体流
sendDevicePush: function(itemData) {
let deviceId = this.currentDevice.deviceId;
let channelId = itemData.channelId;
console.log("通知设备推流1:" + deviceId + " : " + channelId);
let that = this;
this.$axios({
method: 'get',
url: this.$global.genGb28181Url('/api/play/' + deviceId + '/' + channelId)
}).then(function(res) {
let ssrc = res.data.ssrc;
that.$refs.devicePlayer.play(ssrc,deviceId,channelId);
}).catch(function(e) {
});
}
}
};
</script>
<style>
.videoList {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
.video-item {
position: relative;
width: 15rem;
height: 10rem;
margin-right: 1rem;
background-color: #000000;
}
.video-item-img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 100%;
height: 100%;
}
.video-item-img:after {
content: "";
display: inline-block;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 3rem;
height: 3rem;
background-image: url("../assets/loading.png");
background-size: cover;
background-color: #000000;
}
.video-item-title {
position: absolute;
bottom: 0;
color: #000000;
background-color: #ffffff;
line-height: 1.5rem;
padding: 0.3rem;
width: 14.4rem;
}
</style>
import Vue from 'vue';
import App from './App.vue';
import global from '../global.js';
Vue.config.productionTip = false;
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router/index.js';
import axios from 'axios';
import echarts from 'echarts';
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)
Vue.use(ElementUI);
Vue.prototype.$global = global;
Vue.prototype.$axios = axios;
new Vue({
router: router,
render: h => h(App),
}).$mount('#app')
import Vue from 'vue'
import VueRouter from 'vue-router'
import control from '../components/control.vue'
import videoList from '../components/videoList.vue'
import HelloWorld from '../components/HelloWorld.vue'
Vue.use(VueRouter)
export default new VueRouter({
mode:'hash',
routes: [{
path: '/',
component: control,
},
{
path: '/videoList',
component: videoList,
},
]
})
module.exports = {
devServer: {
open: true, //浏览器自动打开页面
proxy: {
//配置跨域
'/gb28181': {
target: "http://172.16.10.98:8080/api",
changOrigin: true,
}
}
}
}
\ No newline at end of file
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
wvp
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="wvp" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="wvp" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: ch.qos.logback:logback-classic:1.1.11">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-classic/1.1.11/logback-classic-1.1.11.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-classic/1.1.11/logback-classic-1.1.11-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-classic/1.1.11/logback-classic-1.1.11-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: ch.qos.logback:logback-core:1.1.11">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-core/1.1.11/logback-core-1.1.11.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-core/1.1.11/logback-core-1.1.11-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/ch/qos/logback/logback-core/1.1.11/logback-core-1.1.11-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.alibaba:druid:1.0.11">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/druid/1.0.11/druid-1.0.11.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/druid/1.0.11/druid-1.0.11-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/druid/1.0.11/druid-1.0.11-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.alibaba:fastjson:1.2.33">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/fastjson/1.2.33/fastjson-1.2.33.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/fastjson/1.2.33/fastjson-1.2.33-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/alibaba/fastjson/1.2.33/fastjson-1.2.33-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.fasterxml:classmate:1.3.4">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/classmate/1.3.4/classmate-1.3.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/classmate/1.3.4/classmate-1.3.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/classmate/1.3.4/classmate-1.3.4-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.8.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.fasterxml.jackson.core:jackson-core:2.8.10">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-core/2.8.10/jackson-core-2.8.10.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-core/2.8.10/jackson-core-2.8.10-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-core/2.8.10/jackson-core-2.8.10-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.fasterxml.jackson.core:jackson-databind:2.8.10">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-databind/2.8.10/jackson-databind-2.8.10.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-databind/2.8.10/jackson-databind-2.8.10-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/fasterxml/jackson/core/jackson-databind/2.8.10/jackson-databind-2.8.10-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.github.jsqlparser:jsqlparser:0.9.4">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/jsqlparser/jsqlparser/0.9.4/jsqlparser-0.9.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/jsqlparser/jsqlparser/0.9.4/jsqlparser-0.9.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/jsqlparser/jsqlparser/0.9.4/jsqlparser-0.9.4-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.github.pagehelper:pagehelper:4.1.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/pagehelper/pagehelper/4.1.1/pagehelper-4.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/pagehelper/pagehelper/4.1.1/pagehelper-4.1.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/github/pagehelper/pagehelper/4.1.1/pagehelper-4.1.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.google.code.gson:gson:2.8.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/code/gson/gson/2.8.0/gson-2.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/code/gson/gson/2.8.0/gson-2.8.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: com.google.guava:guava:18.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/guava/guava/18.0/guava-18.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/guava/guava/18.0/guava-18.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/com/google/guava/guava/18.0/guava-18.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-core:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-core/2.6.1/springfox-core-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-core/2.6.1/springfox-core-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-core/2.6.1/springfox-core-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-schema:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-schema/2.6.1/springfox-schema-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-schema/2.6.1/springfox-schema-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-schema/2.6.1/springfox-schema-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-spi:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spi/2.6.1/springfox-spi-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spi/2.6.1/springfox-spi-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spi/2.6.1/springfox-spi-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-spring-web:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spring-web/2.6.1/springfox-spring-web-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spring-web/2.6.1/springfox-spring-web-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-spring-web/2.6.1/springfox-spring-web-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-swagger2:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger2/2.6.1/springfox-swagger2-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger2/2.6.1/springfox-swagger2-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger2/2.6.1/springfox-swagger2-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-swagger-common:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-common/2.6.1/springfox-swagger-common-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-common/2.6.1/springfox-swagger-common-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-common/2.6.1/springfox-swagger-common-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.springfox:springfox-swagger-ui:2.6.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-ui/2.6.1/springfox-swagger-ui-2.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-ui/2.6.1/springfox-swagger-ui-2.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/springfox/springfox-swagger-ui/2.6.1/springfox-swagger-ui-2.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.swagger:swagger-annotations:1.5.10">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-annotations/1.5.10/swagger-annotations-1.5.10.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-annotations/1.5.10/swagger-annotations-1.5.10-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-annotations/1.5.10/swagger-annotations-1.5.10-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: io.swagger:swagger-models:1.5.10">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-models/1.5.10/swagger-models-1.5.10.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-models/1.5.10/swagger-models-1.5.10-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/io/swagger/swagger-models/1.5.10/swagger-models-1.5.10-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: javax.persistence:persistence-api:1.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/persistence/persistence-api/1.0/persistence-api-1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/persistence/persistence-api/1.0/persistence-api-1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/persistence/persistence-api/1.0/persistence-api-1.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: javax.sip:jain-sip-ri:1.3.0-91">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/sip/jain-sip-ri/1.3.0-91/jain-sip-ri-1.3.0-91.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/sip/jain-sip-ri/1.3.0-91/jain-sip-ri-1.3.0-91-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/sip/jain-sip-ri/1.3.0-91/jain-sip-ri-1.3.0-91-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: javax.validation:validation-api:1.1.0.Final">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: mysql:mysql-connector-java:5.1.30">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/mysql/mysql-connector-java/5.1.30/mysql-connector-java-5.1.30.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/mysql/mysql-connector-java/5.1.30/mysql-connector-java-5.1.30-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/mysql/mysql-connector-java/5.1.30/mysql-connector-java-5.1.30-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.commons:commons-lang3:3.4">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-lang3/3.4/commons-lang3-3.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-lang3/3.4/commons-lang3-3.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-lang3/3.4/commons-lang3-3.4-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.commons:commons-pool2:2.4.3">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-pool2/2.4.3/commons-pool2-2.4.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-pool2/2.4.3/commons-pool2-2.4.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/commons/commons-pool2/2.4.3/commons-pool2-2.4.3-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-core/8.5.27/tomcat-embed-core-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-core/8.5.27/tomcat-embed-core-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-core/8.5.27/tomcat-embed-core-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-el/8.5.27/tomcat-embed-el-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-el/8.5.27/tomcat-embed-el-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-el/8.5.27/tomcat-embed-el-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.27/tomcat-embed-websocket-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.27/tomcat-embed-websocket-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.27/tomcat-embed-websocket-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat:tomcat-annotations-api:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-annotations-api/8.5.27/tomcat-annotations-api-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-annotations-api/8.5.27/tomcat-annotations-api-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-annotations-api/8.5.27/tomcat-annotations-api-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat:tomcat-jdbc:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-jdbc/8.5.27/tomcat-jdbc-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-jdbc/8.5.27/tomcat-jdbc-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-jdbc/8.5.27/tomcat-jdbc-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.apache.tomcat:tomcat-juli:8.5.27">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-juli/8.5.27/tomcat-juli-8.5.27.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-juli/8.5.27/tomcat-juli-8.5.27-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/apache/tomcat/tomcat-juli/8.5.27/tomcat-juli-8.5.27-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.aspectj:aspectjweaver:1.8.13">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.dom4j:dom4j:2.1.3">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/dom4j/dom4j/2.1.3/dom4j-2.1.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/dom4j/dom4j/2.1.3/dom4j-2.1.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/dom4j/dom4j/2.1.3/dom4j-2.1.3-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.hibernate:hibernate-validator:5.3.6.Final">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/hibernate/hibernate-validator/5.3.6.Final/hibernate-validator-5.3.6.Final.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/hibernate/hibernate-validator/5.3.6.Final/hibernate-validator-5.3.6.Final-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/hibernate/hibernate-validator/5.3.6.Final/hibernate-validator-5.3.6.Final-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.jboss.logging:jboss-logging:3.3.1.Final">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.mapstruct:mapstruct:1.0.0.Final">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mapstruct/mapstruct/1.0.0.Final/mapstruct-1.0.0.Final.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mapstruct/mapstruct/1.0.0.Final/mapstruct-1.0.0.Final-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mapstruct/mapstruct/1.0.0.Final/mapstruct-1.0.0.Final-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.mybatis:mybatis:3.3.1">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis/3.3.1/mybatis-3.3.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis/3.3.1/mybatis-3.3.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis/3.3.1/mybatis-3.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.mybatis:mybatis-spring:1.2.4">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis-spring/1.2.4/mybatis-spring-1.2.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis-spring/1.2.4/mybatis-spring-1.2.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/mybatis/mybatis-spring/1.2.4/mybatis-spring-1.2.4-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.slf4j:jcl-over-slf4j:1.7.25">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.slf4j:jul-to-slf4j:1.7.25">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.slf4j:log4j-over-slf4j:1.7.25">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.slf4j:slf4j-api:1.7.25">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot/1.5.10.RELEASE/spring-boot-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot/1.5.10.RELEASE/spring-boot-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot/1.5.10.RELEASE/spring-boot-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-autoconfigure:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-autoconfigure/1.5.10.RELEASE/spring-boot-autoconfigure-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-autoconfigure/1.5.10.RELEASE/spring-boot-autoconfigure-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-autoconfigure/1.5.10.RELEASE/spring-boot-autoconfigure-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter/1.5.10.RELEASE/spring-boot-starter-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter/1.5.10.RELEASE/spring-boot-starter-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter/1.5.10.RELEASE/spring-boot-starter-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter-aop:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-aop/1.5.10.RELEASE/spring-boot-starter-aop-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-aop/1.5.10.RELEASE/spring-boot-starter-aop-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-aop/1.5.10.RELEASE/spring-boot-starter-aop-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter-jdbc:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-jdbc/1.5.10.RELEASE/spring-boot-starter-jdbc-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-jdbc/1.5.10.RELEASE/spring-boot-starter-jdbc-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-jdbc/1.5.10.RELEASE/spring-boot-starter-jdbc-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter-logging:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-logging/1.5.10.RELEASE/spring-boot-starter-logging-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-logging/1.5.10.RELEASE/spring-boot-starter-logging-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-logging/1.5.10.RELEASE/spring-boot-starter-logging-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter-tomcat:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-tomcat/1.5.10.RELEASE/spring-boot-starter-tomcat-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-tomcat/1.5.10.RELEASE/spring-boot-starter-tomcat-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-tomcat/1.5.10.RELEASE/spring-boot-starter-tomcat-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-web/1.5.10.RELEASE/spring-boot-starter-web-1.5.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-web/1.5.10.RELEASE/spring-boot-starter-web-1.5.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/boot/spring-boot-starter-web/1.5.10.RELEASE/spring-boot-starter-web-1.5.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.data:spring-data-commons:1.13.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-commons/1.13.10.RELEASE/spring-data-commons-1.13.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-commons/1.13.10.RELEASE/spring-data-commons-1.13.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-commons/1.13.10.RELEASE/spring-data-commons-1.13.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.data:spring-data-keyvalue:1.2.10.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-keyvalue/1.2.10.RELEASE/spring-data-keyvalue-1.2.10.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-keyvalue/1.2.10.RELEASE/spring-data-keyvalue-1.2.10.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-keyvalue/1.2.10.RELEASE/spring-data-keyvalue-1.2.10.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.data:spring-data-redis:1.8.4.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-redis/1.8.4.RELEASE/spring-data-redis-1.8.4.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-redis/1.8.4.RELEASE/spring-data-redis-1.8.4.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/data/spring-data-redis/1.8.4.RELEASE/spring-data-redis-1.8.4.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-core/1.2.0.RELEASE/spring-plugin-core-1.2.0.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-core/1.2.0.RELEASE/spring-plugin-core-1.2.0.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-core/1.2.0.RELEASE/spring-plugin-core-1.2.0.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework.plugin:spring-plugin-metadata:1.2.0.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-metadata/1.2.0.RELEASE/spring-plugin-metadata-1.2.0.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-metadata/1.2.0.RELEASE/spring-plugin-metadata-1.2.0.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/plugin/spring-plugin-metadata/1.2.0.RELEASE/spring-plugin-metadata-1.2.0.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-aop:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-aop/4.3.14.RELEASE/spring-aop-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-aop/4.3.14.RELEASE/spring-aop-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-aop/4.3.14.RELEASE/spring-aop-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-beans:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-beans/4.3.14.RELEASE/spring-beans-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-beans/4.3.14.RELEASE/spring-beans-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-beans/4.3.14.RELEASE/spring-beans-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-context:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context/4.3.14.RELEASE/spring-context-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context/4.3.14.RELEASE/spring-context-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context/4.3.14.RELEASE/spring-context-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-context-support:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context-support/4.3.14.RELEASE/spring-context-support-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context-support/4.3.14.RELEASE/spring-context-support-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-context-support/4.3.14.RELEASE/spring-context-support-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-core:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-core/4.3.14.RELEASE/spring-core-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-core/4.3.14.RELEASE/spring-core-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-core/4.3.14.RELEASE/spring-core-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-expression:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-expression/4.3.14.RELEASE/spring-expression-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-expression/4.3.14.RELEASE/spring-expression-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-expression/4.3.14.RELEASE/spring-expression-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-jdbc:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-jdbc/4.3.14.RELEASE/spring-jdbc-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-jdbc/4.3.14.RELEASE/spring-jdbc-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-jdbc/4.3.14.RELEASE/spring-jdbc-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-oxm:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-oxm/4.3.14.RELEASE/spring-oxm-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-oxm/4.3.14.RELEASE/spring-oxm-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-oxm/4.3.14.RELEASE/spring-oxm-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-tx:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-tx/4.3.14.RELEASE/spring-tx-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-tx/4.3.14.RELEASE/spring-tx-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-tx/4.3.14.RELEASE/spring-tx-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-web:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-web/4.3.14.RELEASE/spring-web-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-web/4.3.14.RELEASE/spring-web-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-web/4.3.14.RELEASE/spring-web-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.springframework:spring-webmvc:4.3.14.RELEASE">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-webmvc/4.3.14.RELEASE/spring-webmvc-4.3.14.RELEASE.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-webmvc/4.3.14.RELEASE/spring-webmvc-4.3.14.RELEASE-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/springframework/spring-webmvc/4.3.14.RELEASE/spring-webmvc-4.3.14.RELEASE-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: org.yaml:snakeyaml:1.17">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/yaml/snakeyaml/1.17/snakeyaml-1.17-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/org/yaml/snakeyaml/1.17/snakeyaml-1.17-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: redis.clients:jedis:2.9.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/redis/clients/jedis/2.9.0/jedis-2.9.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/redis/clients/jedis/2.9.0/jedis-2.9.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/redis/clients/jedis/2.9.0/jedis-2.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Maven: tk.mybatis:mapper:3.4.0">
<CLASSES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/tk/mybatis/mapper/3.4.0/mapper-3.4.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/tk/mybatis/mapper/3.4.0/mapper-3.4.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3/repo/tk/mybatis/mapper/3.4.0/mapper-3.4.0-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/wvp.iml" filepath="$PROJECT_DIR$/wvp.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="b130843b-8e63-45e1-af42-87d12a7882a7" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="localRepository" value="G:\download\apache-maven-3.5.3-bin\apache-maven-3.5.3\repo" />
<option name="mavenHome" value="G:/download/apache-maven-3.5.3-bin/apache-maven-3.5.3" />
<option name="userSettingsFile" value="G:\download\apache-maven-3.5.3-bin\apache-maven-3.5.3\conf\settings.xml" />
</MavenGeneralSettings>
</option>
<option name="importingSettings">
<MavenImportingSettings>
<option name="importAutomatically" value="true" />
</MavenImportingSettings>
</option>
</component>
<component name="ProjectId" id="1ec5sqPrOU7ZvNaTTquR4OGlWFs" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="settings.editor.selected.configurable" value="MavenSettings" />
</component>
<component name="RunManager">
<configuration name="VManageBootstrap" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="com.genersoft.iot.vmp.VManageBootstrap" />
<module name="wvp" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.genersoft.iot.vmp.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Application.VManageBootstrap" />
</list>
</recent_temporary>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="b130843b-8e63-45e1-af42-87d12a7882a7" name="Default Changelist" comment="" />
<created>1594608249738</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1594608249738</updated>
</task>
<servers />
</component>
<component name="WindowStateProjectService">
<state width="754" height="466" key="DebuggerActiveHint" timestamp="1594802726549">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="754" height="466" key="DebuggerActiveHint/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594802726549" />
<state x="740" y="278" key="FileChooserDialogImpl" timestamp="1594608303456">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="740" y="278" key="FileChooserDialogImpl/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594608303456" />
<state width="1877" height="348" key="GridCell.Tab.0.bottom" timestamp="1594805096470">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.0.bottom/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096470" />
<state width="1877" height="348" key="GridCell.Tab.0.center" timestamp="1594805096470">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.0.center/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096470" />
<state width="1877" height="348" key="GridCell.Tab.0.left" timestamp="1594805096470">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.0.left/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096470" />
<state width="1877" height="348" key="GridCell.Tab.0.right" timestamp="1594805096470">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.0.right/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096470" />
<state width="1877" height="348" key="GridCell.Tab.1.bottom" timestamp="1594805096358">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.1.bottom/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096358" />
<state width="1877" height="348" key="GridCell.Tab.1.center" timestamp="1594805096358">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.1.center/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096358" />
<state width="1877" height="348" key="GridCell.Tab.1.left" timestamp="1594805096358">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.1.left/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096358" />
<state width="1877" height="348" key="GridCell.Tab.1.right" timestamp="1594805096358">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1877" height="348" key="GridCell.Tab.1.right/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594805096358" />
<state x="463" y="165" key="SettingsEditor" timestamp="1594608310297">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="463" y="165" key="SettingsEditor/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594608310297" />
<state x="652" y="348" key="com.intellij.ide.util.TipDialog" timestamp="1594633922729">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="652" y="348" key="com.intellij.ide.util.TipDialog/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594633922729" />
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup" timestamp="1594694978311">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup/0.0.1920.1040/1920.0.1440.860@0.0.1920.1040" timestamp="1594694978311" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/SubscribeRequestProcessor.java</url>
<line>39</line>
<option name="timeStamp" value="3" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
<watches-manager>
<configuration name="Application">
<watch expression="VideoManagerConstants.CACHEKEY_PREFIX+deviceId" language="JAVA" />
<watch expression="response.getReasonPhrase()" language="JAVA" />
</configuration>
</watches-manager>
</component>
</project>
\ No newline at end of file
MIT License
Copyright (c) 2020 swwhaha
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# wvp
WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR、DVR接入。
流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit
前端展示基于MediaServerUI-https://gitee.com/kkkkk5G/MediaServerUI/tree/gb28181/
# 应用场景:
主要应用在IPC等设备没有固定IP地址,但需要在互联网中观看的场景。
要求IPC设备可以访问互联网,有云服务器用于部署本服务。
预计7月可以达到商用级别的文档性
# 支持特性:
1、视频预览
2、云台控制(方向、缩放控制)
3、视频设备信息同步
4、离在线监控
5、录像查询与回放(基于NVR\DVR,暂不支持快进、seek操作)
6、无人观看自动断流
# 2020路线图:
5月中旬-录像回放(基于NVR\DVR)、设备认证(基于密码)
5月下旬-设备报警
6月上旬-流媒体认证(ZLM推流、取流)
6月下旬-语音对讲、Android Deme\iOS Demo
7月下旬-设备认证(基于数字证书)、集群部署
8月下旬-云端录像与回放
9月下旬-Onvif协议支持
10月下旬-GB28181-2011版设备适配
12月底-上级级联、时间同步、其他国标能力
# 项目部署
参考wiki说明
# 使用帮助
参考wiki说明
# 致谢
感谢作者[夏楚](https://github.com/xiongziliang) 提供这么棒的开源流媒体服务框架
感谢作者[kkkkk5G](https://gitee.com/kkkkk5G) 提供这么棒的前端UI
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
<groupId>com.genersoft</groupId>
<artifactId>wvp</artifactId>
<name>web video platform</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hystrix.version>1.5.12</hystrix.version>
<!-- 依赖版本 -->
<mapper.version>3.4.0</mapper.version>
<mybatis.version>3.3.1</mybatis.version>
<mybatis.spring.version>1.2.4</mybatis.spring.version>
<pagehelper.version>4.1.1</pagehelper.version>
<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
<asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
<generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
<gson.version>2.8.0</gson.version>
<jedis.version>3.0.1</jedis.version>
<jsonlib.version>2.4</jsonlib.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
<!--Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!--通用Mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!--Swagger2 -->
<!--在线文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
<version>1.3.0-91</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.4.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
This source diff could not be displayed because it is too large. You can view the blob instead.
package com.genersoft.iot.vmp;
import java.util.logging.LogManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//@EnableEurekaClient
//@EnableTransactionManagement
//@EnableFeignClients(basePackages = { "com.genersoft.iot.vmp", "org.integrain" })
//@ServletComponentScan("com.genersoft.iot.vmp")
@EnableAutoConfiguration
public class VManageBootstrap extends LogManager {
public static void main(String[] args) {
SpringApplication.run(VManageBootstrap.class, args);
}
}
package com.genersoft.iot.vmp.common;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2019年5月30日 下午3:04:04
*
*/
public class VideoManagerConstants {
public static final String CACHEKEY_PREFIX = "VMP_deviceId_";
public static final String KEEPLIVEKEY_PREFIX = "VMP_keeplive_";
public static final String EVENT_ONLINE_REGISTER = "1";
public static final String EVENT_ONLINE_KEEPLIVE = "2";
public static final String EVENT_OUTLINE_UNREGISTER = "1";
public static final String EVENT_OUTLINE_TIMEOUT = "2";
}
package com.genersoft.iot.vmp.conf;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.alibaba.fastjson.parser.ParserConfig;
import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
/**
* @Description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置
* @author: songww
* @date: 2019年5月30日 上午10:58:25
*
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean("redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用fastjson进行序列化处理,提高解析效率
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
// 使用fastjson时需设置此项,否则会报异常not support type
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
return template;
}
/**
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
*
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
package com.genersoft.iot.vmp.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration("sipConfig")
public class SipConfig {
@Value("${sip.ip}")
String sipIp;
@Value("${sip.port}")
Integer sipPort;
@Value("${sip.domain}")
String sipDomain;
@Value("${sip.id}")
String sipId;
@Value("${sip.password}")
String sipPassword;
@Value("${media.ip}")
String mediaIp;
@Value("${media.port}")
Integer mediaPort;
@Value("${sip.ptz.speed:50}")
Integer speed;
public String getSipIp() {
return sipIp;
}
public void setSipIp(String sipIp) {
this.sipIp = sipIp;
}
public Integer getSipPort() {
return sipPort;
}
public void setSipPort(Integer sipPort) {
this.sipPort = sipPort;
}
public String getSipDomain() {
return sipDomain;
}
public void setSipDomain(String sipDomain) {
this.sipDomain = sipDomain;
}
public String getSipPassword() {
return sipPassword;
}
public void setSipPassword(String sipPassword) {
this.sipPassword = sipPassword;
}
public String getMediaIp() {
return mediaIp;
}
public void setMediaIp(String mediaIp) {
this.mediaIp = mediaIp;
}
public Integer getMediaPort() {
return mediaPort;
}
public void setMediaPort(Integer mediaPort) {
this.mediaPort = mediaPort;
}
public Integer getSpeed() {
return speed;
}
public void setSpeed(Integer speed) {
this.speed = speed;
}
public String getSipId() {
return sipId;
}
public void setSipId(String sipId) {
this.sipId = sipId;
}
}
package com.genersoft.iot.vmp.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月6日 下午2:46:00
*/
@Configuration("vmConfig")
public class VManagerConfig {
@Value("${spring.application.database:redis}")
private String database;
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
}
package com.genersoft.iot.vmp.gb28181;
import java.text.ParseException;
import java.util.Properties;
import javax.annotation.PostConstruct;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.ListeningPoint;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionAlreadyExistsException;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.AddressFactory;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
import gov.nist.javax.sip.SipStackImpl;
@Component
public class SipLayer implements SipListener, Runnable {
private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
@Autowired
private SipConfig sipConfig;
private SipProvider tcpSipProvider;
private SipProvider udpSipProvider;
@Autowired
private SIPProcessorFactory processorFactory;
private SipStack sipStack;
private AddressFactory addressFactory;
private HeaderFactory headerFactory;
private MessageFactory messageFactory;
@PostConstruct
private void initSipServer() {
Thread thread = new Thread(this);
thread.setDaemon(true);
thread.setName("sip server thread start");
thread.start();
}
@Override
public void run() {
SipFactory sipFactory = SipFactory.getInstance();
sipFactory.setPathName("gov.nist");
try {
headerFactory = sipFactory.createHeaderFactory();
addressFactory = sipFactory.createAddressFactory();
messageFactory = sipFactory.createMessageFactory();
Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp());
properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
/**
* sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
* 0; public static final int TRACE_MESSAGES = 16; public static final int
* TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32;
*/
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");
sipStack = (SipStackImpl) sipFactory.createSipStack(properties);
startTcpListener();
startUdpListener();
} catch (Exception e) {
logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}");
e.printStackTrace();
}
logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}");
}
private void startTcpListener() throws Exception {
ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(),
"TCP");
tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
tcpSipProvider.addSipListener(this);
}
private void startUdpListener() throws Exception {
ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(),
"UDP");
udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
udpSipProvider.addSipListener(this);
}
/**
* SIP服务端接收消息的方法 Content 里面是GBK编码 This method is called by the SIP stack when a
* new request arrives.
*/
@Override
public void processRequest(RequestEvent evt) {
ISIPRequestProcessor processor = processorFactory.createRequestProcessor(evt);
processor.process(evt, this);
}
@Override
public void processResponse(ResponseEvent evt) {
Response response = evt.getResponse();
int status = response.getStatusCode();
if ((status >= 200) && (status < 300)) { // Success!
ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
try {
processor.process(evt, this, sipConfig);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// } else if (status == Response.TRYING) {
// trying不会回复
} else if ((status >= 100) && (status < 200)) {
// 增加其它无需回复的响应,如101、180等
} else {
logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()/* .getContent().toString()*/);
}
// trying不会回复
// if (status == Response.TRYING) {
// }
}
/**
* <p>
* Title: processTimeout
* </p>
* <p>
* Description:
* </p>
*
* @param timeoutEvent
*/
@Override
public void processTimeout(TimeoutEvent timeoutEvent) {
// TODO Auto-generated method stub
}
/**
* <p>
* Title: processIOException
* </p>
* <p>
* Description:
* </p>
*
* @param exceptionEvent
*/
@Override
public void processIOException(IOExceptionEvent exceptionEvent) {
// TODO Auto-generated method stub
}
/**
* <p>
* Title: processTransactionTerminated
* </p>
* <p>
* Description:
* </p>
*
* @param transactionTerminatedEvent
*/
@Override
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
// TODO Auto-generated method stub
}
/**
* <p>
* Title: processDialogTerminated
* </p>
* <p>
* Description:
* </p>
*
* @param dialogTerminatedEvent
*/
@Override
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
// TODO Auto-generated method stub
}
public ServerTransaction getServerTransaction(RequestEvent evt) {
Request request = evt.getRequest();
ServerTransaction serverTransaction = evt.getServerTransaction();
// 判断TCP还是UDP
boolean isTcp = false;
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
if (transport.equals("TCP")) {
isTcp = true;
}
if (serverTransaction == null) {
try {
if (isTcp) {
serverTransaction = tcpSipProvider.getNewServerTransaction(request);
} else {
serverTransaction = udpSipProvider.getNewServerTransaction(request);
}
} catch (TransactionAlreadyExistsException e) {
e.printStackTrace();
} catch (TransactionUnavailableException e) {
e.printStackTrace();
}
}
return serverTransaction;
}
public AddressFactory getAddressFactory() {
return addressFactory;
}
public HeaderFactory getHeaderFactory() {
return headerFactory;
}
public MessageFactory getMessageFactory() {
return messageFactory;
}
public SipProvider getTcpSipProvider() {
return tcpSipProvider;
}
public SipProvider getUdpSipProvider() {
return udpSipProvider;
}
}
/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain. As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY. NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
package com.genersoft.iot.vmp.gb28181.auth;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Random;
import javax.sip.address.URI;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import gov.nist.core.InternalErrorHandler;
/**
* Implements the HTTP digest authentication method server side functionality.
*
* @author M. Ranganathan
* @author Marc Bednarek
*/
public class DigestServerAuthenticationHelper {
private MessageDigest messageDigest;
public static final String DEFAULT_ALGORITHM = "MD5";
public static final String DEFAULT_SCHEME = "Digest";
/** to hex converter */
private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Default constructor.
* @throws NoSuchAlgorithmException
*/
public DigestServerAuthenticationHelper()
throws NoSuchAlgorithmException {
messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
}
public static String toHexString(byte b[]) {
int pos = 0;
char[] c = new char[b.length * 2];
for (int i = 0; i < b.length; i++) {
c[pos++] = toHex[(b[i] >> 4) & 0x0F];
c[pos++] = toHex[b[i] & 0x0f];
}
return new String(c);
}
/**
* Generate the challenge string.
*
* @return a generated nonce.
*/
private String generateNonce() {
// Get the time of day and run MD5 over it.
Date date = new Date();
long time = date.getTime();
Random rand = new Random();
long pad = rand.nextLong();
String nonceString = (new Long(time)).toString()
+ (new Long(pad)).toString();
byte mdbytes[] = messageDigest.digest(nonceString.getBytes());
// Convert the mdbytes array into a hex string.
return toHexString(mdbytes);
}
public Response generateChallenge(HeaderFactory headerFactory, Response response, String realm) {
try {
WWWAuthenticateHeader proxyAuthenticate = headerFactory
.createWWWAuthenticateHeader(DEFAULT_SCHEME);
proxyAuthenticate.setParameter("realm", realm);
proxyAuthenticate.setParameter("nonce", generateNonce());
proxyAuthenticate.setParameter("opaque", "");
proxyAuthenticate.setParameter("stale", "FALSE");
proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM);
response.setHeader(proxyAuthenticate);
} catch (Exception ex) {
InternalErrorHandler.handleException(ex);
}
return response;
}
/**
* Authenticate the inbound request.
*
* @param request - the request to authenticate.
* @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password.
*
* @return true if authentication succeded and false otherwise.
*/
public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) {
AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if ( authHeader == null ) return false;
String realm = authHeader.getRealm();
String username = authHeader.getUsername();
if ( username == null || realm == null ) {
return false;
}
String nonce = authHeader.getNonce();
URI uri = authHeader.getURI();
if (uri == null) {
return false;
}
String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
String HA1 = hashedPassword;
byte[] mdbytes = messageDigest.digest(A2.getBytes());
String HA2 = toHexString(mdbytes);
String cnonce = authHeader.getCNonce();
String KD = HA1 + ":" + nonce;
if (cnonce != null) {
KD += ":" + cnonce;
}
KD += ":" + HA2;
mdbytes = messageDigest.digest(KD.getBytes());
String mdString = toHexString(mdbytes);
String response = authHeader.getResponse();
return mdString.equals(response);
}
/**
* Authenticate the inbound request given plain text password.
*
* @param request - the request to authenticate.
* @param pass -- the plain text password.
*
* @return true if authentication succeded and false otherwise.
*/
public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if ( authHeader == null ) return false;
String realm = authHeader.getRealm();
String username = authHeader.getUsername();
if ( username == null || realm == null ) {
return false;
}
String nonce = authHeader.getNonce();
URI uri = authHeader.getURI();
if (uri == null) {
return false;
}
String A1 = username + ":" + realm + ":" + pass;
String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
byte mdbytes[] = messageDigest.digest(A1.getBytes());
String HA1 = toHexString(mdbytes);
mdbytes = messageDigest.digest(A2.getBytes());
String HA2 = toHexString(mdbytes);
String cnonce = authHeader.getCNonce();
String KD = HA1 + ":" + nonce;
if (cnonce != null) {
KD += ":" + cnonce;
}
KD += ":" + HA2;
mdbytes = messageDigest.digest(KD.getBytes());
String mdString = toHexString(mdbytes);
String response = authHeader.getResponse();
return mdString.equals(response);
}
}
package com.genersoft.iot.vmp.gb28181.auth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
/**
* @Description:注册逻辑处理,当设备注册后触发逻辑。
* @author: songww
* @date: 2020年5月8日 下午9:41:46
*/
@Component
public class RegisterLogicHandler {
@Autowired
private SIPCommander cmder;
public void onRegister(Device device) {
// TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口
cmder.deviceInfoQuery(device);
cmder.catalogQuery(device);
}
}
package com.genersoft.iot.vmp.gb28181.bean;
import java.util.Map;
public class Device {
/**
* 设备Id
*/
private String deviceId;
/**
* 设备名
*/
private String name;
/**
* 生产厂商
*/
private String manufacturer;
/**
* 型号
*/
private String model;
/**
* 固件版本
*/
private String firmware;
/**
* 传输协议
* UDP/TCP
*/
private String transport;
/**
* wan地址
*/
private Host host;
/**
* 在线
*/
private int online;
/**
* 通道列表
*/
private Map<String,DeviceChannel> channelMap;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTransport() {
return transport;
}
public void setTransport(String transport) {
this.transport = transport;
}
public Host getHost() {
return host;
}
public void setHost(Host host) {
this.host = host;
}
public Map<String, DeviceChannel> getChannelMap() {
return channelMap;
}
public void setChannelMap(Map<String, DeviceChannel> channelMap) {
this.channelMap = channelMap;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getFirmware() {
return firmware;
}
public void setFirmware(String firmware) {
this.firmware = firmware;
}
public int getOnline() {
return online;
}
public void setOnline(int online) {
this.online = online;
}
}
package com.genersoft.iot.vmp.gb28181.bean;
public class DeviceAlarm {
/**
* 设备Id
*/
private String deviceId;
/**
* 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情-
*/
private String alarmPriorit;
/**
* 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
* 7其他报警;可以为直接组合如12为电话报警或 设备报警-
*/
private String alarmMethod;
/**
* 报警时间
*/
private String alarmTime;
/**
* 报警内容描述
*/
private String alarmDescription;
/**
* 经度
*/
private double longitude;
/**
* 纬度
*/
private double latitude;
/**
* 报警类型
*/
private String alarmType;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getAlarmPriorit() {
return alarmPriorit;
}
public void setAlarmPriorit(String alarmPriorit) {
this.alarmPriorit = alarmPriorit;
}
public String getAlarmMethod() {
return alarmMethod;
}
public void setAlarmMethod(String alarmMethod) {
this.alarmMethod = alarmMethod;
}
public String getAlarmTime() {
return alarmTime;
}
public void setAlarmTime(String alarmTime) {
this.alarmTime = alarmTime;
}
public String getAlarmDescription() {
return alarmDescription;
}
public void setAlarmDescription(String alarmDescription) {
this.alarmDescription = alarmDescription;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public String getAlarmType() {
return alarmType;
}
public void setAlarmType(String alarmType) {
this.alarmType = alarmType;
}
}
package com.genersoft.iot.vmp.gb28181.bean;
public class DeviceChannel {
/**
* 通道id
*/
private String channelId;
/**
* 通道名
*/
private String name;
/**
* 生产厂商
*/
private String manufacture;
/**
* 型号
*/
private String model;
/**
* 设备归属
*/
private String owner;
/**
* 行政区域
*/
private String civilCode;
/**
* 警区
*/
private String block;
/**
* 安装地址
*/
private String address;
/**
* 是否有子设备 1有, 0没有
*/
private int parental;
/**
* 父级id
*/
private String parentId;
/**
* 信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式
*/
private int safetyWay;
/**
* 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式
*/
private int registerWay;
/**
* 证书序列号
*/
private String certNum;
/**
* 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效
*/
private int certifiable;
/**
* 证书无效原因码
*/
private int errCode;
/**
* 证书终止有效期
*/
private String endTime;
/**
* 保密属性 缺省为0; 0:不涉密, 1:涉密
*/
private String secrecy;
/**
* IP地址
*/
private String ipAddress;
/**
* 端口号
*/
private int port;
/**
* 密码
*/
private String password;
/**
* 在线/离线
* 1在线,0离线
* 默认在线
* 信令:
* <Status>ON</Status>
* <Status>OFF</Status>
* 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF
*/
private int status;
/**
* 经度
*/
private double longitude;
/**
* 纬度
*/
private double latitude;
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getManufacture() {
return manufacture;
}
public void setManufacture(String manufacture) {
this.manufacture = manufacture;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getCivilCode() {
return civilCode;
}
public void setCivilCode(String civilCode) {
this.civilCode = civilCode;
}
public String getBlock() {
return block;
}
public void setBlock(String block) {
this.block = block;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getParental() {
return parental;
}
public void setParental(int parental) {
this.parental = parental;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public int getSafetyWay() {
return safetyWay;
}
public void setSafetyWay(int safetyWay) {
this.safetyWay = safetyWay;
}
public int getRegisterWay() {
return registerWay;
}
public void setRegisterWay(int registerWay) {
this.registerWay = registerWay;
}
public String getCertNum() {
return certNum;
}
public void setCertNum(String certNum) {
this.certNum = certNum;
}
public int getCertifiable() {
return certifiable;
}
public void setCertifiable(int certifiable) {
this.certifiable = certifiable;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public String getSecrecy() {
return secrecy;
}
public void setSecrecy(String secrecy) {
this.secrecy = secrecy;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
}
package com.genersoft.iot.vmp.gb28181.bean;
public class Host {
private String ip;
private int port;
private String address;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.genersoft.iot.vmp.gb28181.bean;
import java.util.List;
/**
* @Description:设备录像信息bean
* @author: songww
* @date: 2020年5月8日 下午2:05:56
*/
public class RecordInfo {
private String deviceId;
private String name;
private int sumNum;
private List<RecordItem> recordList;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSumNum() {
return sumNum;
}
public void setSumNum(int sumNum) {
this.sumNum = sumNum;
}
public List<RecordItem> getRecordList() {
return recordList;
}
public void setRecordList(List<RecordItem> recordList) {
this.recordList = recordList;
}
}
package com.genersoft.iot.vmp.gb28181.bean;
/**
* @Description:设备录像bean
* @author: songww
* @date: 2020年5月8日 下午2:06:54
*/
public class RecordItem {
private String deviceId;
private String name;
private String filePath;
private String address;
private String startTime;
private String endTime;
private int secrecy;
private String type;
private String recorderId;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public int getSecrecy() {
return secrecy;
}
public void setSecrecy(int secrecy) {
this.secrecy = secrecy;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRecorderId() {
return recorderId;
}
public void setRecordId(String recorderId) {
this.recorderId = recorderId;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}
package com.genersoft.iot.vmp.gb28181.event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description:设备离在线状态检测器,用于检测设备状态
* @author: songww
* @date: 2020年5月13日 下午2:40:29
*/
@Component
public class DeviceOffLineDetector {
@Autowired
private RedisUtil redis;
public boolean isOnline(String deviceId) {
String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + deviceId;
return redis.hasKey(key);
}
}
package com.genersoft.iot.vmp.gb28181.event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.event.offline.OfflineEvent;
import com.genersoft.iot.vmp.gb28181.event.online.OnlineEvent;
/**
* @Description:Event事件通知推送器,支持推送在线事件、离线事件
* @author: songww
* @date: 2020年5月6日 上午11:30:50
*/
@Component
public class EventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void onlineEventPublish(String deviceId, String from) {
OnlineEvent onEvent = new OnlineEvent(this);
onEvent.setDeviceId(deviceId);
onEvent.setFrom(from);
applicationEventPublisher.publishEvent(onEvent);
}
public void outlineEventPublish(String deviceId, String from){
OfflineEvent outEvent = new OfflineEvent(this);
outEvent.setDeviceId(deviceId);
outEvent.setFrom(from);
applicationEventPublisher.publishEvent(outEvent);
}
}
package com.genersoft.iot.vmp.gb28181.event.offline;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
/**
* @Description:设备心跳超时监听,借助redis过期特性,进行监听,监听到说明设备心跳超时,发送离线事件
* @author: songww
* @date: 2020年5月6日 上午11:35:46
*/
@Component
public class KeepliveTimeoutListener extends KeyExpirationEventMessageListener {
@Autowired
private EventPublisher publisher;
public KeepliveTimeoutListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 监听失效的key,key格式为keeplive_deviceId
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取失效的key
String expiredKey = message.toString();
if(!expiredKey.startsWith(VideoManagerConstants.KEEPLIVEKEY_PREFIX)){
System.out.println("收到redis过期监听,但开头不是"+VideoManagerConstants.KEEPLIVEKEY_PREFIX+",忽略");
return;
}
String deviceId = expiredKey.substring(VideoManagerConstants.KEEPLIVEKEY_PREFIX.length(),expiredKey.length());
publisher.outlineEventPublish(deviceId, VideoManagerConstants.EVENT_OUTLINE_TIMEOUT);
}
}
package com.genersoft.iot.vmp.gb28181.event.offline;
import org.springframework.context.ApplicationEvent;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月6日 上午11:33:13
*/
public class OfflineEvent extends ApplicationEvent {
/**
* @Title: OutlineEvent
* @Description: TODO(这里用一句话描述这个方法的作用)
* @param: @param source
* @throws
*/
public OfflineEvent(Object source) {
super(source);
}
private String deviceId;
private String from;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
}
package com.genersoft.iot.vmp.gb28181.event.offline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description: 离线事件监听器,监听到离线后,修改设备离在线状态。 设备离线有两个来源:
* 1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor}
* 2、设备未知原因离线,心跳超时,{@link com.genersoft.iot.vmp.gb28181.event.offline.OfflineEventListener}
* @author: songww
* @date: 2020年5月6日 下午1:51:23
*/
@Component
public class OfflineEventListener implements ApplicationListener<OfflineEvent> {
private final static Logger logger = LoggerFactory.getLogger(OfflineEventListener.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private RedisUtil redis;
@Override
public void onApplicationEvent(OfflineEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
}
String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId();
switch (event.getFrom()) {
// 心跳超时触发的离线事件,说明redis中已删除,无需处理
case VideoManagerConstants.EVENT_OUTLINE_TIMEOUT:
break;
// 设备主动注销触发的离线事件,需要删除redis中的超时监听
case VideoManagerConstants.EVENT_OUTLINE_UNREGISTER:
redis.del(key);
break;
default:
boolean exist = redis.hasKey(key);
if (exist) {
redis.del(key);
}
}
// 处理离线监听
storager.outline(event.getDeviceId());
}
}
package com.genersoft.iot.vmp.gb28181.event.online;
import org.springframework.context.ApplicationEvent;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月6日 上午11:32:56
*/
public class OnlineEvent extends ApplicationEvent {
/**
* @Title: OnlineEvent
* @Description: TODO(这里用一句话描述这个方法的作用)
* @param: @param source
* @throws
*/
public OnlineEvent(Object source) {
super(source);
}
private String deviceId;
private String from;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
}
package com.genersoft.iot.vmp.gb28181.event.online;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源:
* 1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor}
* 2、设备未知原因离线,心跳超时,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor}
* @author: songww
* @date: 2020年5月6日 下午1:51:23
*/
@Component
public class OnlineEventListener implements ApplicationListener<OnlineEvent> {
private final static Logger logger = LoggerFactory.getLogger(OnlineEventListener.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private RedisUtil redis;
@Override
public void onApplicationEvent(OnlineEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("设备离线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
}
String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId();
boolean needUpdateStorager = false;
switch (event.getFrom()) {
// 注册时触发的在线事件,先在redis中增加超时超时监听
case VideoManagerConstants.EVENT_ONLINE_REGISTER:
// TODO 超时时间暂时写死为180秒
redis.set(key, event.getDeviceId(), 180);
needUpdateStorager = true;
break;
// 设备主动发送心跳触发的离线事件
case VideoManagerConstants.EVENT_ONLINE_KEEPLIVE:
boolean exist = redis.hasKey(key);
// 先判断是否还存在,当设备先心跳超时后又发送心跳时,redis没有监听,需要增加
if (!exist) {
needUpdateStorager = true;
redis.set(key, event.getDeviceId(), 180);
} else {
redis.expire(key, 180);
}
break;
}
if (needUpdateStorager) {
// 处理离线监听
storager.online(event.getDeviceId());
}
}
}
package com.genersoft.iot.vmp.gb28181.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
/**
* @Description:SIP信令中的SSRC工具类。SSRC值由10位十进制整数组成的字符串,第一位为0代表实况,为1则代表回放;第二位至第六位由监控域ID的第4位到第8位组成;最后4位为不重复的4个整数
* @author: songww
* @date: 2020年5月10日 上午11:57:57
*/
public class SsrcUtil {
private static String ssrcPrefix;
private static List<String> isUsed;
private static List<String> notUsed;
private static void init() {
SipConfig sipConfig = (SipConfig) SpringBeanFactory.getBean("sipConfig");
ssrcPrefix = sipConfig.getSipDomain().substring(3, 8);
isUsed = new ArrayList<String>();
notUsed = new ArrayList<String>();
for (int i = 1; i < 10000; i++) {
if (i < 10) {
notUsed.add("000" + i);
} else if (i < 100) {
notUsed.add("00" + i);
} else if (i < 1000) {
notUsed.add("0" + i);
} else {
notUsed.add(String.valueOf(i));
}
}
}
/**
* 获取视频预览的SSRC值,第一位固定为0
*
*/
public static String getPlaySsrc() {
return "0" + getSsrcPrefix() + getSN();
}
/**
* 获取录像回放的SSRC值,第一位固定为1
*
*/
public static String getPlayBackSsrc() {
return "1" + getSsrcPrefix() + getSN();
}
/**
* 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
*
*/
public static void releaseSsrc(String ssrc) {
String sn = ssrc.substring(6);
isUsed.remove(sn);
notUsed.add(sn);
}
/**
* 获取后四位数SN,随机数
*
*/
private static String getSN() {
String sn = null;
if (notUsed.size() == 0) {
throw new RuntimeException("ssrc已经用完");
} else if (notUsed.size() == 1) {
sn = notUsed.get(0);
} else {
sn = notUsed.get(new Random().nextInt(notUsed.size() - 1));
}
notUsed.remove(0);
isUsed.add(sn);
return sn;
}
private static String getSsrcPrefix() {
if (ssrcPrefix == null) {
init();
}
return ssrcPrefix;
}
}
package com.genersoft.iot.vmp.gb28181.session;
import java.util.concurrent.ConcurrentHashMap;
import javax.sip.ClientTransaction;
import org.springframework.stereotype.Component;
/**
* @Description:视频流session管理器,管理视频预览、预览回放的通信句柄
* @author: songww
* @date: 2020年5月13日 下午4:03:02
*/
@Component
public class VideoStreamSessionManager {
private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
public String createPlaySsrc(){
String ssrc = SsrcUtil.getPlaySsrc();
return ssrc;
}
public String createPlayBackSsrc(){
String ssrc = SsrcUtil.getPlayBackSsrc();
return ssrc;
}
public void put(String ssrc,ClientTransaction transaction){
sessionMap.put(ssrc, transaction);
}
public ClientTransaction get(String ssrc){
return sessionMap.get(ssrc);
}
public void remove(String ssrc) {
sessionMap.remove(ssrc);
SsrcUtil.releaseSsrc(ssrc);
}
}
package com.genersoft.iot.vmp.gb28181.transmit;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.header.CSeqHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.AckRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.ByeRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.CancelRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.InviteRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.OtherRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.SubscribeRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.ByeResponseProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.CancelResponseProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.InviteResponseProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.OtherResponseProcessor;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月3日 下午4:24:37
*/
@Component
public class SIPProcessorFactory {
@Autowired
private InviteRequestProcessor inviteRequestProcessor;
@Autowired
private RegisterRequestProcessor registerRequestProcessor;
@Autowired
private SubscribeRequestProcessor subscribeRequestProcessor;
@Autowired
private AckRequestProcessor ackRequestProcessor;
@Autowired
private ByeRequestProcessor byeRequestProcessor;
@Autowired
private CancelRequestProcessor cancelRequestProcessor;
@Autowired
private MessageRequestProcessor messageRequestProcessor;
@Autowired
private OtherRequestProcessor otherRequestProcessor;
@Autowired
private InviteResponseProcessor inviteResponseProcessor;
@Autowired
private ByeResponseProcessor byeResponseProcessor;
@Autowired
private CancelResponseProcessor cancelResponseProcessor;
@Autowired
private OtherResponseProcessor otherResponseProcessor;
public ISIPRequestProcessor createRequestProcessor(RequestEvent evt) {
Request request = evt.getRequest();
String method = request.getMethod();
if (Request.INVITE.equals(method)) {
return inviteRequestProcessor;
} else if (Request.REGISTER.equals(method)) {
return registerRequestProcessor;
} else if (Request.SUBSCRIBE.equals(method)) {
return subscribeRequestProcessor;
} else if (Request.ACK.equals(method)) {
return ackRequestProcessor;
} else if (Request.BYE.equals(method)) {
return byeRequestProcessor;
} else if (Request.CANCEL.equals(method)) {
return cancelRequestProcessor;
} else if (Request.MESSAGE.equals(method)) {
return messageRequestProcessor;
} else {
return otherRequestProcessor;
}
}
public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) {
Response response = evt.getResponse();
CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
String method = cseqHeader.getMethod();
if(Request.INVITE.equals(method)){
return inviteResponseProcessor;
} else if (Request.BYE.equals(method)) {
return byeResponseProcessor;
} else if (Request.CANCEL.equals(method)) {
return cancelResponseProcessor;
} else {
return otherResponseProcessor;
}
}
}
package com.genersoft.iot.vmp.gb28181.transmit.callback;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月8日 下午7:59:05
*/
@Component
public class DeferredResultHolder {
public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO";
public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG";
public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO";
private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
public void put(String key, DeferredResult result) {
map.put(key, result);
}
public DeferredResult get(String key) {
return map.get(key);
}
public void invokeResult(RequestMessage msg) {
DeferredResult result = map.get(msg.getId());
if (result == null) {
return;
}
result.setResult(new ResponseEntity<>(msg.getData(),HttpStatus.OK));
}
}
package com.genersoft.iot.vmp.gb28181.transmit.callback;
/**
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: songww
* @date: 2020年5月8日 下午1:09:18
*/
public class RequestMessage {
private String id;
private String deviceId;
private String type;
private Object data;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
this.id = type + deviceId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
this.id = type + deviceId;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.gb28181.bean.Device;
/**
* @Description:设备能力接口,用于定义设备的控制、查询能力
* @author: songww
* @date: 2020年5月3日 下午9:16:34
*/
public interface ISIPCommander {
/**
* 云台方向放控制,使用配置文件中的默认镜头移动速度
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param moveSpeed 镜头移动速度
*/
public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
/**
* 云台方向放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param moveSpeed 镜头移动速度
*/
public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
/**
* 云台缩放控制,使用配置文件中的默认镜头缩放速度
*
* @param device 控制设备
* @param channelId 预览通道
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
*/
public boolean ptzZoomCmd(Device device,String channelId,int inOut);
/**
* 云台缩放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param zoomSpeed 镜头缩放速度
*/
public boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
/**
* 云台控制,支持方向与缩放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param moveSpeed 镜头移动速度
* @param zoomSpeed 镜头缩放速度
*/
public boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
/**
* 请求预览视频流
*
* @param device 视频设备
* @param channelId 预览通道
*/
public String playStreamCmd(Device device,String channelId);
/**
* 请求回放视频流
*
* @param device 视频设备
* @param channelId 预览通道
* @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
*/
public String playbackStreamCmd(Device device,String channelId, String startTime, String endTime);
/**
* 视频流停止
*
* @param ssrc ssrc
*/
public void streamByeCmd(String ssrc);
/**
* 语音广播
*
* @param device 视频设备
* @param channelId 预览通道
*/
public boolean audioBroadcastCmd(Device device,String channelId);
/**
* 音视频录像控制
*
* @param device 视频设备
* @param channelId 预览通道
*/
public boolean recordCmd(Device device,String channelId);
/**
* 报警布防/撤防命令
*
* @param device 视频设备
*/
public boolean guardCmd(Device device);
/**
* 报警复位命令
*
* @param device 视频设备
*/
public boolean alarmCmd(Device device);
/**
* 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
*
* @param device 视频设备
* @param channelId 预览通道
*/
public boolean iFameCmd(Device device,String channelId);
/**
* 看守位控制命令
*
* @param device 视频设备
*/
public boolean homePositionCmd(Device device);
/**
* 设备配置命令
*
* @param device 视频设备
*/
public boolean deviceConfigCmd(Device device);
/**
* 查询设备状态
*
* @param device 视频设备
*/
public boolean deviceStatusQuery(Device device);
/**
* 查询设备信息
*
* @param device 视频设备
* @return
*/
public boolean deviceInfoQuery(Device device);
/**
* 查询目录列表
*
* @param device 视频设备
*/
public boolean catalogQuery(Device device);
/**
* 查询录像信息
*
* @param device 视频设备
* @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
*/
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
/**
* 查询报警信息
*
* @param device 视频设备
*/
public boolean alarmInfoQuery(Device device);
/**
* 查询设备配置
*
* @param device 视频设备
*/
public boolean configQuery(Device device);
/**
* 查询设备预置位置
*
* @param device 视频设备
*/
public boolean presetQuery(Device device);
/**
* 查询移动设备位置数据
*
* @param device 视频设备
*/
public boolean mobilePostitionQuery(Device device);
}
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import java.text.ParseException;
import java.util.ArrayList;
import javax.sip.InvalidArgumentException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.Host;
/**
* @Description:摄像头命令request创造器 TODO 冗余代码太多待优化
* @author: songww
* @date: 2020年5月6日 上午9:29:02
*/
@Component
public class SIPRequestHeaderProvider {
@Autowired
private SipLayer layer;
@Autowired
private SipConfig sipConfig;
public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
Request request = null;
Host host = device.getHost();
// sipuri
SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
// via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
device.getTransport(), viaTag);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
// from
SipURI fromSipURI = layer.getAddressFactory().createSipURI(sipConfig.getSipId(),
sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
// to
SipURI toSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), sipConfig.getSipDomain());
Address toAddress = layer.getAddressFactory().createAddress(toSipURI);
ToHeader toHeader = layer.getHeaderFactory().createToHeader(toAddress, toTag);
// callid
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? layer.getTcpSipProvider().getNewCallId()
: layer.getUdpSipProvider().getNewCallId();
// Forwards
MaxForwardsHeader maxForwards = layer.getHeaderFactory().createMaxForwardsHeader(70);
// ceq
CSeqHeader cSeqHeader = layer.getHeaderFactory().createCSeqHeader(1L, Request.MESSAGE);
request = layer.getMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards);
ContentTypeHeader contentTypeHeader = layer.getHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
request.setContent(content, contentTypeHeader);
return request;
}
public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
Request request = null;
Host host = device.getHost();
//请求行
SipURI requestLine = layer.getAddressFactory().createSipURI(channelId, host.getAddress());
//via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
// ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
//from
SipURI fromSipURI = layer.getAddressFactory().createSipURI(sipConfig.getSipId(),sipConfig.getSipDomain());
Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
//to
SipURI toSipURI = layer.getAddressFactory().createSipURI(channelId,sipConfig.getSipDomain());
Address toAddress = layer.getAddressFactory().createAddress(toSipURI);
ToHeader toHeader = layer.getHeaderFactory().createToHeader(toAddress,null);
//callid
CallIdHeader callIdHeader = null;
if(device.getTransport().equals("TCP")) {
callIdHeader = layer.getTcpSipProvider().getNewCallId();
}
if(device.getTransport().equals("UDP")) {
callIdHeader = layer.getUdpSipProvider().getNewCallId();
}
//Forwards
MaxForwardsHeader maxForwards = layer.getHeaderFactory().createMaxForwardsHeader(70);
//ceq
CSeqHeader cSeqHeader = layer.getHeaderFactory().createCSeqHeader(1L, Request.INVITE);
request = layer.getMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
Address concatAddress = layer.getAddressFactory().createAddress(layer.getAddressFactory().createSipURI(sipConfig.getSipId(), sipConfig.getSipIp()+":"+sipConfig.getSipPort()));
// Address concatAddress = layer.getAddressFactory().createAddress(layer.getAddressFactory().createSipURI(sipConfig.getSipId(), device.getHost().getIp()+":"+device.getHost().getPort()));
request.addHeader(layer.getHeaderFactory().createContactHeader(concatAddress));
ContentTypeHeader contentTypeHeader = layer.getHeaderFactory().createContentTypeHeader("Application", "SDP");
request.setContent(content, contentTypeHeader);
return request;
}
public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
Request request = null;
Host host = device.getHost();
//请求行
SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
//via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
// ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
//from
SipURI fromSipURI = layer.getAddressFactory().createSipURI(sipConfig.getSipId(),sipConfig.getSipDomain());
Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
//to
SipURI toSipURI = layer.getAddressFactory().createSipURI(channelId,sipConfig.getSipDomain());
Address toAddress = layer.getAddressFactory().createAddress(toSipURI);
ToHeader toHeader = layer.getHeaderFactory().createToHeader(toAddress,null);
//callid
CallIdHeader callIdHeader = null;
if(device.getTransport().equals("TCP")) {
callIdHeader = layer.getTcpSipProvider().getNewCallId();
}
if(device.getTransport().equals("UDP")) {
callIdHeader = layer.getUdpSipProvider().getNewCallId();
}
//Forwards
MaxForwardsHeader maxForwards = layer.getHeaderFactory().createMaxForwardsHeader(70);
//ceq
CSeqHeader cSeqHeader = layer.getHeaderFactory().createCSeqHeader(1L, Request.INVITE);
request = layer.getMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
Address concatAddress = layer.getAddressFactory().createAddress(layer.getAddressFactory().createSipURI(sipConfig.getSipId(), sipConfig.getSipIp()+":"+sipConfig.getSipPort()));
// Address concatAddress = layer.getAddressFactory().createAddress(layer.getAddressFactory().createSipURI(sipConfig.getSipId(), device.getHost().getIp()+":"+device.getHost().getPort()));
request.addHeader(layer.getHeaderFactory().createContactHeader(concatAddress));
ContentTypeHeader contentTypeHeader = layer.getHeaderFactory().createContentTypeHeader("Application", "SDP");
request.setContent(content, contentTypeHeader);
return request;
}
}
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import java.text.ParseException;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import javax.sip.TransactionDoesNotExistException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* @Description:设备能力接口,用于定义设备的控制、查询能力
* @author: songww
* @date: 2020年5月3日 下午9:22:48
*/
@Component
public class SIPCommander implements ISIPCommander {
@Autowired
private SipConfig sipConfig;
@Autowired
private SIPRequestHeaderProvider headerProvider;
@Autowired
private SipLayer sipLayer;
@Autowired
private VideoStreamSessionManager streamSession;
/**
* 云台方向放控制,使用配置文件中的默认镜头移动速度
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param moveSpeed 镜头移动速度
*/
@Override
public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getSpeed(), 0);
}
/**
* 云台方向放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param moveSpeed 镜头移动速度
*/
@Override
public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) {
return ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
}
/**
* 云台缩放控制,使用配置文件中的默认镜头缩放速度
*
* @param device 控制设备
* @param channelId 预览通道
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
*/
@Override
public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getSpeed());
}
/**
* 云台缩放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param zoomSpeed 镜头缩放速度
*/
@Override
public boolean ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) {
return ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
}
/**
* 云台指令码计算
*
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param moveSpeed 镜头移动速度 默认 0XFF (0-255)
* @param zoomSpeed 镜头缩放速度 默认 0X1 (0-255)
*/
public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
int cmdCode = 0;
if (leftRight == 2) {
cmdCode|=0x01; // 右移
} else if(leftRight == 1) {
cmdCode|=0x02; // 左移
}
if (upDown == 2) {
cmdCode|=0x04; // 下移
} else if(upDown == 1) {
cmdCode|=0x08; // 上移
}
if (inOut == 2) {
cmdCode |= 0x10; // 放大
} else if(inOut == 1) {
cmdCode |= 0x20; // 缩小
}
StringBuilder builder = new StringBuilder("A50F01");
String strTmp;
strTmp = String.format("%02X", cmdCode);
builder.append(strTmp, 0, 2);
strTmp = String.format("%02X", moveSpeed);
builder.append(strTmp, 0, 2);
builder.append(strTmp, 0, 2);
strTmp = String.format("%X", zoomSpeed);
builder.append(strTmp, 0, 1).append("0");
//计算校验码
int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed /*<< 4*/ & 0XF0)) % 0X100;
strTmp = String.format("%02X", checkCode);
builder.append(strTmp, 0, 2);
return builder.toString();
}
/**
* 云台控制,支持方向与缩放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param moveSpeed 镜头移动速度
* @param zoomSpeed 镜头缩放速度
*/
@Override
public boolean ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
int zoomSpeed) {
try {
String cmdStr= cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
StringBuffer ptzXml = new StringBuffer(200);
ptzXml.append("<?xml version=\"1.0\" ?>");
ptzXml.append("<Control>");
ptzXml.append("<CmdType>DeviceControl</CmdType>");
ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
ptzXml.append("<DeviceID>" + channelId + "</DeviceID>");
ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>");
ptzXml.append("<Info>");
ptzXml.append("</Info>");
ptzXml.append("</Control>");
Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
transmitRequest(device, request);
return true;
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
return false;
}
/**
* 请求预览视频流
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override
public String playStreamCmd(Device device, String channelId) {
try {
String ssrc = streamSession.createPlaySsrc();
String transport = device.getTransport();
//
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
content.append("s=Play\r\n");
content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
content.append("t=0 0\r\n");
if("TCP".equals(transport)) {
content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
}
if("UDP".equals(transport)) {
content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
}
content.append("a=recvonly\r\n");
content.append("a=rtpmap:96 PS/90000\r\n");
content.append("a=rtpmap:98 H264/90000\r\n");
content.append("a=rtpmap:97 MPEG4/90000\r\n");
if("TCP".equals(transport)){
content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n");
}
content.append("y="+ssrc+"\r\n");//ssrc
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null);
ClientTransaction transaction = transmitRequest(device, request);
streamSession.put(ssrc, transaction);
return ssrc;
} catch ( SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return null;
}
}
/**
* 请求回放视频流
*
* @param device 视频设备
* @param channelId 预览通道
* @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
*/
@Override
public String playbackStreamCmd(Device device, String channelId, String startTime, String endTime) {
try {
String ssrc = streamSession.createPlayBackSsrc();
//
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o="+sipConfig.getSipId()+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
content.append("s=Playback\r\n");
content.append("u="+channelId+":0\r\n");
content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
if(device.getTransport().equals("TCP")) {
content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
}
if(device.getTransport().equals("UDP")) {
content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
}
content.append("a=recvonly\r\n");
content.append("a=rtpmap:96 PS/90000\r\n");
content.append("a=rtpmap:98 H264/90000\r\n");
content.append("a=rtpmap:97 MPEG4/90000\r\n");
if(device.getTransport().equals("TCP")){
content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n");
}
content.append("y="+ssrc+"\r\n");//ssrc
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "playback", null);
ClientTransaction transaction = transmitRequest(device, request);
streamSession.put(ssrc, transaction);
return ssrc;
} catch ( SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return null;
}
}
/**
* 视频流停止
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override
public void streamByeCmd(String ssrc) {
try {
ClientTransaction transaction = streamSession.get(ssrc);
if (transaction == null) {
return;
}
Dialog dialog = transaction.getDialog();
if (dialog == null) {
return;
}
Request byeRequest = dialog.createRequest(Request.BYE);
SipURI byeURI = (SipURI) byeRequest.getRequestURI();
String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString();
Pattern p = Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)\\:(\\d+)");
Matcher matcher = p.matcher(vh);
if (matcher.find()) {
byeURI.setHost(matcher.group(1));
}
ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
String protocol = viaHeader.getTransport().toUpperCase();
ClientTransaction clientTransaction = null;
if("TCP".equals(protocol)) {
clientTransaction = sipLayer.getTcpSipProvider().getNewClientTransaction(byeRequest);
} else if("UDP".equals(protocol)) {
clientTransaction = sipLayer.getUdpSipProvider().getNewClientTransaction(byeRequest);
}
dialog.sendRequest(clientTransaction);
} catch (TransactionDoesNotExistException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
/**
* 语音广播
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override
public boolean audioBroadcastCmd(Device device, String channelId) {
// TODO Auto-generated method stub
return false;
}
/**
* 音视频录像控制
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override
public boolean recordCmd(Device device, String channelId) {
// TODO Auto-generated method stub
return false;
}
/**
* 报警布防/撤防命令
*
* @param device 视频设备
*/
@Override
public boolean guardCmd(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 报警复位命令
*
* @param device 视频设备
*/
@Override
public boolean alarmCmd(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override
public boolean iFameCmd(Device device, String channelId) {
// TODO Auto-generated method stub
return false;
}
/**
* 看守位控制命令
*
* @param device 视频设备
*/
@Override
public boolean homePositionCmd(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 设备配置命令
*
* @param device 视频设备
*/
@Override
public boolean deviceConfigCmd(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 查询设备状态
*
* @param device 视频设备
*/
@Override
public boolean deviceStatusQuery(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 查询设备信息
*
* @param device 视频设备
*/
@Override
public boolean deviceInfoQuery(Device device) {
try {
StringBuffer catalogXml = new StringBuffer(200);
catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
catalogXml.append("<Query>");
catalogXml.append("<CmdType>DeviceInfo</CmdType>");
catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
catalogXml.append("</Query>");
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
transmitRequest(device, request);
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 查询目录列表
*
* @param device 视频设备
*/
@Override
public boolean catalogQuery(Device device) {
try {
StringBuffer catalogXml = new StringBuffer(200);
catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
catalogXml.append("<Query>");
catalogXml.append("<CmdType>Catalog</CmdType>");
catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
catalogXml.append("</Query>");
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
transmitRequest(device, request);
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 查询录像信息
*
* @param device 视频设备
* @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
*/
@Override
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
try {
StringBuffer recordInfoXml = new StringBuffer(200);
recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
recordInfoXml.append("<Query>");
recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
recordInfoXml.append("<Secrecy>0</Secrecy>");
// 大华NVR要求必须增加一个值为all的文本元素节点Type
recordInfoXml.append("<Type>all</Type>");
recordInfoXml.append("</Query>");
Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
transmitRequest(device, request);
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 查询报警信息
*
* @param device 视频设备
*/
@Override
public boolean alarmInfoQuery(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 查询设备配置
*
* @param device 视频设备
*/
@Override
public boolean configQuery(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 查询设备预置位置
*
* @param device 视频设备
*/
@Override
public boolean presetQuery(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 查询移动设备位置数据
*
* @param device 视频设备
*/
@Override
public boolean mobilePostitionQuery(Device device) {
// TODO Auto-generated method stub
return false;
}
private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
ClientTransaction clientTransaction = null;
if("TCP".equals(device.getTransport())) {
clientTransaction = sipLayer.getTcpSipProvider().getNewClientTransaction(request);
} else if("UDP".equals(device.getTransport())) {
clientTransaction = sipLayer.getUdpSipProvider().getNewClientTransaction(request);
}
clientTransaction.sendRequest();
return clientTransaction;
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request;
import javax.sip.RequestEvent;
import com.genersoft.iot.vmp.gb28181.SipLayer;
/**
* @Description:处理接收IPCamera发来的SIP协议请求消息
* @author: songww
* @date: 2020年5月3日 下午4:42:22
*/
public interface ISIPRequestProcessor {
public void process(RequestEvent evt, SipLayer layer);
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.message.Request;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import gov.nist.javax.sip.header.CSeq;
/**
* @Description:ACK请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:31:45
*/
@Component
public class AckRequestProcessor implements ISIPRequestProcessor {
/**
* 处理 ACK请求
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
Request request = evt.getRequest();
Dialog dialog = evt.getDialog();
try {
Request ackRequest = null;
CSeq csReq = (CSeq) request.getHeader(CSeq.NAME);
ackRequest = dialog.createAck(csReq.getSeqNumber());
dialog.sendAck(ackRequest);
System.out.println("send ack to callee:" + ackRequest.toString());
} catch (SipException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
}
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
/**
* @Description: BYE请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:05
*/
@Component
public class ByeRequestProcessor implements ISIPRequestProcessor {
/**
* 处理BYE请求
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
// TODO Auto-generated method stub
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
/**
* @Description:CANCEL请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:23
*/
@Component
public class CancelRequestProcessor implements ISIPRequestProcessor {
/**
* 处理CANCEL请求
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
// TODO Auto-generated method stub
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
/**
* @Description:处理INVITE请求
* @author: songww
* @date: 2020年5月3日 下午4:43:52
*/
@Component
public class InviteRequestProcessor implements ISIPRequestProcessor {
/**
* 处理invite请求
*
* @param request
* 请求消息
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
// TODO Auto-generated method stub
// Request request = requestEvent.getRequest();
//
// try {
// // 发送100 Trying
// ServerTransaction serverTransaction = getServerTransaction(requestEvent);
// // 查询目标地址
// URI reqUri = request.getRequestURI();
// URI contactURI = currUser.get(reqUri);
//
// System.out.println("processInvite rqStr=" + reqUri + " contact=" + contactURI);
//
// // 根据Request uri来路由,后续的响应消息通过VIA来路由
// Request cliReq = messageFactory.createRequest(request.toString());
// cliReq.setRequestURI(contactURI);
//
// HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
// Via callerVia = (Via) request.getHeader(Via.NAME);
// Via via = (Via) headerFactory.createViaHeader(SIPMain.ip, SIPMain.port, "UDP",
// callerVia.getBranch() + "sipphone");
//
// // FIXME 需要测试是否能够通过设置VIA头域来修改VIA头域值
// cliReq.removeHeader(Via.NAME);
// cliReq.addHeader(via);
//
// // 更新contact的地址
// ContactHeader contactHeader = headerFactory.createContactHeader();
// Address address = SipFactory.getInstance().createAddressFactory()
// .createAddress("sip:sipsoft@" + SIPMain.ip + ":" + SIPMain.port);
// contactHeader.setAddress(address);
// contactHeader.setExpires(3600);
// cliReq.setHeader(contactHeader);
//
// clientTransactionId = sipProvider.getNewClientTransaction(cliReq);
// clientTransactionId.sendRequest();
//
// System.out.println("processInvite clientTransactionId=" + clientTransactionId.toString());
//
// System.out.println("send invite to callee: " + cliReq);
// } catch (TransactionUnavailableException e1) {
// e1.printStackTrace();
// } catch (SipException e) {
// e.printStackTrace();
// } catch (ParseException e) {
// e.printStackTrace();
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description:MESSAGE请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:41
*/
@Component
public class MessageRequestProcessor implements ISIPRequestProcessor {
private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
private ServerTransaction transaction;
private SipLayer layer;
@Autowired
private SIPCommander cmder;
@Autowired
private IVideoManagerStorager storager;
@Autowired
private EventPublisher publisher;
@Autowired
private RedisUtil redis;
@Autowired
private DeferredResultHolder deferredResultHolder;
@Autowired
private DeviceOffLineDetector offLineDetector;
private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
private static final String MESSAGE_CATALOG = "Catalog";
private static final String MESSAGE_DEVICE_INFO = "DeviceInfo";
private static final String MESSAGE_KEEP_ALIVE = "Keepalive";
private static final String MESSAGE_ALARM = "Alarm";
private static final String MESSAGE_RECORD_INFO = "RecordInfo";
// private static final String MESSAGE_BROADCAST = "Broadcast";
// private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
// private static final String MESSAGE_MOBILE_POSITION = "MobilePosition";
// private static final String MESSAGE_MOBILE_POSITION_INTERVAL = "Interval";
/**
* 处理MESSAGE请求
*
* @param evt
* @param layer
* @param transaction
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
this.layer = layer;
this.transaction = layer.getServerTransaction(evt);
Request request = evt.getRequest();
SAXReader reader = new SAXReader();
reader.setEncoding("gbk");
Document xml;
try {
xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
Element rootElement = xml.getRootElement();
String cmd = rootElement.element("CmdType").getStringValue();
if (MESSAGE_KEEP_ALIVE.equals(cmd)) {
logger.info("接收到KeepAlive消息");
processMessageKeepAlive(evt);
} else if (MESSAGE_CATALOG.equals(cmd)) {
logger.info("接收到Catalog消息");
processMessageCatalogList(evt);
} else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
logger.info("接收到DeviceInfo消息");
processMessageDeviceInfo(evt);
} else if (MESSAGE_ALARM.equals(cmd)) {
logger.info("接收到Alarm消息");
processMessageAlarm(evt);
} else if (MESSAGE_RECORD_INFO.equals(cmd)) {
logger.info("接收到RecordInfo消息");
processMessageRecordInfo(evt);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 收到deviceInfo设备信息请求 处理
* @param evt
*/
private void processMessageDeviceInfo(RequestEvent evt) {
try {
Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID");
String deviceId = deviceIdElement.getText().toString();
Device device = storager.queryVideoDevice(deviceId);
if (device == null) {
return;
}
device.setName(XmlUtil.getText(rootElement,"DeviceName"));
device.setManufacturer(XmlUtil.getText(rootElement,"Manufacturer"));
device.setModel(XmlUtil.getText(rootElement,"Model"));
device.setFirmware(XmlUtil.getText(rootElement,"Firmware"));
storager.update(device);
RequestMessage msg = new RequestMessage();
msg.setDeviceId(deviceId);
msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICEINFO);
msg.setData(device);
deferredResultHolder.invokeResult(msg);
} catch (DocumentException e) {
e.printStackTrace();
}
}
/***
* 收到catalog设备目录列表请求 处理
* @param evt
*/
private void processMessageCatalogList(RequestEvent evt) {
try {
Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID");
String deviceId = deviceIdElement.getText().toString();
Element deviceListElement = rootElement.element("DeviceList");
if (deviceListElement == null) {
return;
}
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
Device device = storager.queryVideoDevice(deviceId);
if (device == null) {
return;
}
Map<String, DeviceChannel> channelMap = device.getChannelMap();
if (channelMap == null) {
channelMap = new HashMap<String, DeviceChannel>(5);
device.setChannelMap(channelMap);
}
// 遍历DeviceList
while (deviceListIterator.hasNext()) {
Element itemDevice = deviceListIterator.next();
Element channelDeviceElement = itemDevice.element("DeviceID");
if (channelDeviceElement == null) {
continue;
}
String channelDeviceId = channelDeviceElement.getText().toString();
Element channdelNameElement = itemDevice.element("Name");
String channelName = channdelNameElement != null ? channdelNameElement.getText().toString() : "";
Element statusElement = itemDevice.element("Status");
String status = statusElement != null ? statusElement.getText().toString() : "ON";
DeviceChannel deviceChannel = channelMap.containsKey(channelDeviceId) ? channelMap.get(channelDeviceId) : new DeviceChannel();
deviceChannel.setName(channelName);
deviceChannel.setChannelId(channelDeviceId);
if(status.equals("ON")) {
deviceChannel.setStatus(1);
}
if(status.equals("OFF")) {
deviceChannel.setStatus(0);
}
deviceChannel.setManufacture(XmlUtil.getText(itemDevice,"Manufacturer"));
deviceChannel.setModel(XmlUtil.getText(itemDevice,"Model"));
deviceChannel.setOwner(XmlUtil.getText(itemDevice,"Owner"));
deviceChannel.setCivilCode(XmlUtil.getText(itemDevice,"CivilCode"));
deviceChannel.setBlock(XmlUtil.getText(itemDevice,"Block"));
deviceChannel.setAddress(XmlUtil.getText(itemDevice,"Address"));
deviceChannel.setParental(itemDevice.element("Parental") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Parental")));
deviceChannel.setParentId(XmlUtil.getText(itemDevice,"ParentId"));
deviceChannel.setSafetyWay(itemDevice.element("SafetyWay") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"SafetyWay")));
deviceChannel.setRegisterWay(itemDevice.element("RegisterWay") == null? 1:Integer.parseInt(XmlUtil.getText(itemDevice,"RegisterWay")));
deviceChannel.setCertNum(XmlUtil.getText(itemDevice,"CertNum"));
deviceChannel.setCertifiable(itemDevice.element("Certifiable") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Certifiable")));
deviceChannel.setErrCode(itemDevice.element("ErrCode") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"ErrCode")));
deviceChannel.setEndTime(XmlUtil.getText(itemDevice,"EndTime"));
deviceChannel.setSecrecy(XmlUtil.getText(itemDevice,"Secrecy"));
deviceChannel.setIpAddress(XmlUtil.getText(itemDevice,"IPAddress"));
deviceChannel.setPort(itemDevice.element("Port") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"Port")));
deviceChannel.setPassword(XmlUtil.getText(itemDevice,"Password"));
deviceChannel.setLongitude(itemDevice.element("Longitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Longitude")));
deviceChannel.setLatitude(itemDevice.element("Latitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Latitude")));
channelMap.put(channelDeviceId, deviceChannel);
}
// 更新
storager.update(device);
RequestMessage msg = new RequestMessage();
msg.setDeviceId(deviceId);
msg.setType(DeferredResultHolder.CALLBACK_CMD_CATALOG);
msg.setData(device);
deferredResultHolder.invokeResult(msg);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
/***
* 收到alarm设备报警信息 处理
* @param evt
*/
private void processMessageAlarm(RequestEvent evt) {
try {
Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID");
String deviceId = deviceIdElement.getText().toString();
Device device = storager.queryVideoDevice(deviceId);
if (device == null) {
return;
}
device.setName(XmlUtil.getText(rootElement,"DeviceName"));
device.setManufacturer(XmlUtil.getText(rootElement,"Manufacturer"));
device.setModel(XmlUtil.getText(rootElement,"Model"));
device.setFirmware(XmlUtil.getText(rootElement,"Firmware"));
storager.update(device);
cmder.catalogQuery(device);
} catch (DocumentException e) {
e.printStackTrace();
}
}
/***
* 收到keepalive请求 处理
* @param evt
*/
private void processMessageKeepAlive(RequestEvent evt){
try {
Element rootElement = getRootElement(evt);
String deviceId = XmlUtil.getText(rootElement,"DeviceID");
Request request = evt.getRequest();
Response response = null;
if (offLineDetector.isOnline(deviceId)) {
response = layer.getMessageFactory().createResponse(Response.OK,request);
publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
} else {
response = layer.getMessageFactory().createResponse(Response.BAD_REQUEST,request);
}
transaction.sendResponse(response);
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
e.printStackTrace();
}
}
/***
* 收到catalog设备目录列表请求 处理
* TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
* @param evt
*/
private void processMessageRecordInfo(RequestEvent evt) {
try {
RecordInfo recordInfo = new RecordInfo();
Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID");
String deviceId = deviceIdElement.getText().toString();
recordInfo.setDeviceId(deviceId);
recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
String sn = XmlUtil.getText(rootElement,"SN");
Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null) {
return;
}
Iterator<Element> recordListIterator = recordListElement.elementIterator();
List<RecordItem> recordList = new ArrayList<RecordItem>();
if (recordListIterator != null) {
RecordItem record = new RecordItem();
// 遍历DeviceList
while (recordListIterator.hasNext()) {
Element itemRecord = recordListIterator.next();
Element recordElement = itemRecord.element("DeviceID");
if (recordElement == null) {
continue;
}
record = new RecordItem();
record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
record.setName(XmlUtil.getText(itemRecord,"Name"));
record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
record.setAddress(XmlUtil.getText(itemRecord,"Address"));
record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"StartTime")));
record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime")));
record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
record.setType(XmlUtil.getText(itemRecord,"Type"));
record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID"));
recordList.add(record);
}
recordInfo.setRecordList(recordList);
}
// 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
// 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
String cacheKey = CACHE_RECORDINFO_KEY+deviceId+sn;
// TODO 暂时直接操作redis存储,后续封装专用缓存接口,改为本地内存缓存
if (redis.hasKey(cacheKey)) {
List<RecordItem> previousList = (List<RecordItem>) redis.get(cacheKey);
if (previousList != null && previousList.size() > 0) {
recordList.addAll(previousList);
}
// 本分支表示录像列表被拆包,且加上之前的数据还是不够,保存缓存返回,等待下个包再处理
if (recordList.size() < recordInfo.getSumNum()) {
redis.set(cacheKey, recordList, 180);
return;
} else {
// 本分支表示录像被拆包,但加上之前的数据够足够,返回响应
// 因设备心跳有监听redis过期机制,为提高性能,此处手动删除
redis.del(cacheKey);
}
} else {
// 本分支有两种可能:1、录像列表被拆包,且是第一个包,直接保存缓存返回,等待下个包再处理
// 2、之前有包,但超时清空了,那么这次sn批次的响应数据已经不完整,等待过期时间后redis自动清空数据
redis.set(cacheKey, recordList, 180);
return;
}
}
// 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
// 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
// 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
RequestMessage msg = new RequestMessage();
msg.setDeviceId(deviceId);
msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
msg.setData(recordInfo);
deferredResultHolder.invokeResult(msg);
} catch (DocumentException e) {
e.printStackTrace();
}
}
private Element getRootElement(RequestEvent evt) throws DocumentException {
Request request = evt.getRequest();
SAXReader reader = new SAXReader();
reader.setEncoding("gbk");
Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
return xml.getRootElement();
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
/**
* @Description:暂不支持的消息请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:59
*/
@Component
public class OtherRequestProcessor implements ISIPRequestProcessor {
/**
* <p>Title: process</p>
* <p>Description: </p>
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
System.out.println("no support the method! Method:" + evt.getRequest().getMethod());
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Locale;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
import com.genersoft.iot.vmp.gb28181.auth.RegisterLogicHandler;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.Host;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.header.Expires;
/**
* @Description:收到注册请求 处理
* @author: songww
* @date: 2020年5月3日 下午4:47:25
*/
@Component
public class RegisterRequestProcessor implements ISIPRequestProcessor {
@Autowired
private SipConfig sipConfig;
@Autowired
private RegisterLogicHandler handler;
@Autowired
private IVideoManagerStorager storager;
@Autowired
private EventPublisher publisher;
/***
* 收到注册请求 处理
*
* @param request
* 请求消息
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
try {
System.out.println("收到注册请求,开始处理");
Request request = evt.getRequest();
Response response = null;
boolean passwordCorrect = false;
// 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功
int registerFlag = 0;
Device device = null;
AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
// 校验密码是否正确
if (authorhead != null) {
passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
sipConfig.getSipPassword());
}
// 未携带授权头或者密码错误 回复401
if (authorhead == null || !passwordCorrect) {
if (authorhead == null) {
System.out.println("未携带授权头 回复401");
} else if (!passwordCorrect) {
System.out.println("密码错误 回复401");
}
response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, sipConfig.getSipDomain());
}
// 携带授权头并且密码正确
else if (passwordCorrect) {
response = layer.getMessageFactory().createResponse(Response.OK, request);
// 添加date头
response.addHeader(layer.getHeaderFactory().createDateHeader(Calendar.getInstance(Locale.ENGLISH)));
ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
// 添加Contact头
response.addHeader(request.getHeader(ContactHeader.NAME));
// 添加Expires头
response.addHeader(request.getExpires());
// 1.获取到通信地址等信息,保存到Redis
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String received = viaHeader.getReceived();
int rPort = viaHeader.getRPort();
// 本地模拟设备 received 为空 rPort 为 -1
// 解析本地地址替代
if (StringUtils.isEmpty(received) || rPort == -1) {
received = viaHeader.getHost();
rPort = viaHeader.getPort();
}
//
Host host = new Host();
host.setIp(received);
host.setPort(rPort);
host.setAddress(received.concat(":").concat(String.valueOf(rPort)));
AddressImpl address = (AddressImpl) fromHeader.getAddress();
SipUri uri = (SipUri) address.getURI();
String deviceId = uri.getUser();
device = new Device();
device.setDeviceId(deviceId);
device.setHost(host);
// 注销成功
if (expiresHeader != null && expiresHeader.getExpires() == 0) {
registerFlag = 2;
}
// 注册成功
else {
registerFlag = 1;
// 判断TCP还是UDP
boolean isTcp = false;
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
if (transport.equals("TCP")) {
isTcp = true;
}
device.setTransport(isTcp ? "TCP" : "UDP");
}
}
layer.getServerTransaction(evt).sendResponse(response);
// 注册成功
// 保存到redis
// 下发catelog查询目录
if (registerFlag == 1 && device != null) {
System.out.println("注册成功! deviceId:" + device.getDeviceId());
storager.update(device);
publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
handler.onRegister(device);
} else if (registerFlag == 2) {
System.out.println("注销成功! deviceId:" + device.getDeviceId());
publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
}
} catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
e.printStackTrace();
}
}
}
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import java.text.ParseException;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.header.ExpiresHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
/**
* @Description:SUBSCRIBE请求处理器
* @author: songww
* @date: 2020年5月3日 下午5:31:20
*/
@Component
public class SubscribeRequestProcessor implements ISIPRequestProcessor {
/**
* 处理SUBSCRIBE请求
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(RequestEvent evt, SipLayer layer) {
Request request = evt.getRequest();
try {
Response response = null;
response = layer.getMessageFactory().createResponse(200, request);
if (response != null) {
ExpiresHeader expireHeader = layer.getHeaderFactory().createExpiresHeader(30);
response.setExpires(expireHeader);
}
System.out.println("response : " + response.toString());
ServerTransaction transaction = layer.getServerTransaction(evt);
if (transaction != null) {
transaction.sendResponse(response);
transaction.terminate();
} else {
System.out.println("processRequest serverTransactionId is null.");
}
} catch (ParseException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
}
}
}
package com.genersoft.iot.vmp.gb28181.transmit.response;
import java.text.ParseException;
import javax.sip.ResponseEvent;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
/**
* @Description:处理接收IPCamera发来的SIP协议响应消息
* @author: songww
* @date: 2020年5月3日 下午4:42:22
*/
public interface ISIPResponseProcessor {
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) throws ParseException;
}
package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
import javax.sip.ResponseEvent;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
/**
* @Description: BYE请求响应器
* @author: songww
* @date: 2020年5月3日 下午5:32:05
*/
@Component
public class ByeResponseProcessor implements ISIPResponseProcessor {
/**
* 处理BYE响应
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
// TODO Auto-generated method stub
}
}
package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
import javax.sip.ResponseEvent;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
/**
* @Description:CANCEL响应处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:23
*/
@Component
public class CancelResponseProcessor implements ISIPResponseProcessor {
/**
* 处理CANCEL响应
*
* @param evt
* @param layer
* @param transaction
* @param config
*/
@Override
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
// TODO Auto-generated method stub
}
}
package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
import java.text.ParseException;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
// import java.util.regex.Pattern;
// import java.util.regex.Matcher;
/**
* @Description:处理INVITE响应
* @author: songww
* @date: 2020年5月3日 下午4:43:52
*/
@Component
public class InviteResponseProcessor implements ISIPResponseProcessor {
private final static Logger logger = LoggerFactory.getLogger(SIPProcessorFactory.class);
/**
* 处理invite响应
*
* @param evt 响应消息
* @throws ParseException
*/
@Override
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) throws ParseException {
try {
Response response = evt.getResponse();
int statusCode = response.getStatusCode();
// trying不会回复
if (statusCode == Response.TRYING) {
}
// 成功响应
// 下发ack
if (statusCode == Response.OK) {
// ClientTransaction clientTransaction = evt.getClientTransaction();
// if(clientTransaction == null){
// logger.error("回复ACK时,clientTransaction为null >>> {}",response);
// return;
// }
// Dialog clientDialog = clientTransaction.getDialog();
// CSeqHeader clientCSeqHeader = (CSeqHeader)
// response.getHeader(CSeqHeader.NAME);
// long cseqId = clientCSeqHeader.getSeqNumber();
// /*
// createAck函数,创建的ackRequest,会采用Invite响应的200OK,中的contact字段中的地址,作为目标地址。
// 有的终端传上来的可能还是内网地址,会造成ack发送不出去。接受不到音视频流
// 所以在此处统一替换地址。和响应消息的Via头中的地址保持一致。
// */
// Request ackRequest = clientDialog.createAck(cseqId);
// SipURI requestURI = (SipURI) ackRequest.getRequestURI();
// ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
// try {
// requestURI.setHost(viaHeader.getHost());
// } catch (Exception e) {
// e.printStackTrace();
// }
// requestURI.setPort(viaHeader.getPort());
// clientDialog.sendAck(ackRequest);
Dialog dialog = evt.getDialog();
CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
Request reqAck = dialog.createAck(cseq.getSeqNumber());
SipURI requestURI = (SipURI) reqAck.getRequestURI();
ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
// String viaHost =viaHeader.getHost();
//getHost()函数取回的IP地址是“[xxx.xxx.xxx.xxx:yyyy]”的格式,需用正则表达式截取为“xxx.xxx.xxx.xxx"格式
// Pattern p = Pattern.compile("(?<=//|)((\\w)+\\.)+\\w+");
// Matcher matcher = p.matcher(viaHeader.getHost());
// if (matcher.find()) {
// requestURI.setHost(matcher.group());
// }
requestURI.setHost(viaHeader.getHost());
requestURI.setPort(viaHeader.getPort());
reqAck.setRequestURI(requestURI);
dialog.sendAck(reqAck);
}
} catch (InvalidArgumentException | SipException e) {
e.printStackTrace();
}
}
}
package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
import javax.sip.ResponseEvent;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
/**
* @Description:暂不支持的消息响应处理器
* @author: songww
* @date: 2020年5月3日 下午5:32:59
*/
@Component
public class OtherResponseProcessor implements ISIPResponseProcessor {
/**
* <p>Title: process</p>
* <p>Description: </p>
* @param evt
* @param layer
* @param config
*/
@Override
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
// TODO Auto-generated method stub
}
}
package com.genersoft.iot.vmp.gb28181.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* @Description:时间工具类,主要处理ISO 8601格式转换
* @author: songww
* @date: 2020年5月8日 下午3:24:42
*/
public class DateUtil {
//private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
SimpleDateFormat oldsdf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
SimpleDateFormat newsdf = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
try {
return newsdf.format(oldsdf.parse(formatTime));
} catch (ParseException e) {
e.printStackTrace();
}
return "";
}
public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
SimpleDateFormat oldsdf = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
SimpleDateFormat newsdf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
try {
return newsdf.format(oldsdf.parse(formatTime));
} catch (ParseException e) {
e.printStackTrace();
}
return "";
}
public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
SimpleDateFormat format=new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);
//设置要读取的时间字符串格式
Date date;
try {
date = format.parse(formatTime);
Long timestamp=date.getTime()/1000;
//转换为Date类
return timestamp;
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
}
package com.genersoft.iot.vmp.gb28181.utils;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 基于dom4j的工具包
*
*
*/
public class XmlUtil
{
/**
* 日志服务
*/
private static Logger LOG = LoggerFactory.getLogger(XmlUtil.class);
/**
* 解析XML为Document对象
*
* @param xml
* 被解析的XMl
* @return Document
*/
public static Element parseXml(String xml)
{
Document document = null;
//
StringReader sr = new StringReader(xml);
SAXReader saxReader = new SAXReader();
try
{
document = saxReader.read(sr);
}
catch (DocumentException e)
{
LOG.error("解析失败", e);
}
return null == document ? null : document.getRootElement();
}
/**
* 获取element对象的text的值
*
* @param em
* 节点的对象
* @param tag
* 节点的tag
* @return 节点
*/
public static String getText(Element em, String tag)
{
if (null == em)
{
return null;
}
Element e = em.element(tag);
//
return null == e ? null : e.getText();
}
/**
* 递归解析xml节点,适用于 多节点数据
*
* @param node
* node
* @param nodeName
* nodeName
* @return List<Map<String, Object>>
*/
public static List<Map<String, Object>> listNodes(Element node, String nodeName)
{
if (null == node)
{
return null;
}
// 初始化返回
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
// 首先获取当前节点的所有属性节点
List<Attribute> list = node.attributes();
Map<String, Object> map = null;
// 遍历属性节点
for (Attribute attribute : list)
{
if (nodeName.equals(node.getName()))
{
if (null == map)
{
map = new HashMap<String, Object>();
listMap.add(map);
}
// 取到的节点属性放到map中
map.put(attribute.getName(), attribute.getValue());
}
}
// 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称
// 使用递归
Iterator<Element> iterator = node.elementIterator();
while (iterator.hasNext())
{
Element e = iterator.next();
listMap.addAll(listNodes(e, nodeName));
}
return listMap;
}
}
package com.genersoft.iot.vmp.media.zlm;
import java.math.BigInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
/**
* @Description:针对 ZLMediaServer的hook事件监听
* @author: songww
* @date: 2020年5月8日 上午10:46:48
*/
@RestController
@RequestMapping("/index/hook")
public class ZLMHttpHookListener {
private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
@Autowired
private SIPCommander cmder;
/**
* 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_flow_report", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onFlowReport(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_flow_report API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 访问http文件服务器上hls之外的文件时触发。
*
*/
@ResponseBody
@PostMapping(value = "/on_http_access", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onHttpAccess(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_http_access API 调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("err", "");
json.put("path", "");
json.put("second", 600);
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
*
*/
@ResponseBody
@PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onPlay(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_play API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* rtsp/rtmp/rtp推流鉴权事件。
*
*/
@ResponseBody
@PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onPublish(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
json.put("enableHls", true);
json.put("enableMP4", false);
json.put("enableRtxp", true);
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 录制mp4完成后通知事件;此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onRecordMp4(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_record_mp4 API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
*
*/
@ResponseBody
@PostMapping(value = "/on_rtsp_realm", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onRtspRealm(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_rtsp_realm API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("realm", "");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。
*
*/
@ResponseBody
@PostMapping(value = "/on_rtsp_auth", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onRtspAuth(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_rtsp_auth API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("encrypted", false);
json.put("passwd", "test");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* shell登录鉴权,ZLMediaKit提供简单的telnet调试方式,使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。
*
*/
@ResponseBody
@PostMapping(value = "/on_shell_login", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onShellLogin(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_shell_login API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamChanged(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
*
*/
@ResponseBody
@PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamNoneReader(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
}
BigInteger bigint=new BigInteger(json.getString("stream"), 16);
int numb=bigint.intValue();
String ssrc = String.format("%010d", numb);
cmder.streamByeCmd(ssrc);
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("close", true);
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onServerStarted(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_server_started API调用,参数:" + json.toString());
}
// TODO Auto-generated method stub
JSONObject ret = new JSONObject();
json.put("code", 0);
json.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
}
package com.genersoft.iot.vmp.storager;
import java.util.List;
import com.genersoft.iot.vmp.gb28181.bean.Device;
/**
* @Description:视频设备数据存储接口
* @author: songww
* @date: 2020年5月6日 下午2:14:31
*/
public interface IVideoManagerStorager {
/**
* 根据设备ID判断设备是否存在
*
* @param deviceId 设备ID
* @return true:存在 false:不存在
*/
public boolean exists(String deviceId);
/**
* 视频设备创建
*
* @param device 设备对象
* @return true:创建成功 false:创建失败
*/
public boolean create(Device device);
/**
* 视频设备更新
*
* @param device 设备对象
* @return true:创建成功 false:创建失败
*/
public boolean update(Device device);
/**
* 获取设备
*
* @param deviceId 设备ID
* @return DShadow 设备对象
*/
public Device queryVideoDevice(String deviceId);
/**
* 获取多个设备
*
* @param deviceIds 设备ID数组
* @return List<Device> 设备对象数组
*/
public List<Device> queryVideoDeviceList(String[] deviceIds);
/**
* 删除设备
*
* @param deviceId 设备ID
* @return true:删除成功 false:删除失败
*/
public boolean delete(String deviceId);
/**
* 更新设备在线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
public boolean online(String deviceId);
/**
* 更新设备离线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
public boolean outline(String deviceId);
}
package com.genersoft.iot.vmp.storager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.VManagerConfig;
/**
* @Description:视频设备数据存储工厂,根据存储策略,返回对应的存储器
* @author: songww
* @date: 2020年5月6日 下午2:15:16
*/
@Component
public class VideoManagerStoragerFactory {
@Autowired
private VManagerConfig vmConfig;
@Autowired
private IVideoManagerStorager jdbcStorager;
@Autowired
private IVideoManagerStorager redisStorager;
@Bean("storager")
public IVideoManagerStorager getStorager() {
if ("redis".equals(vmConfig.getDatabase().toLowerCase())) {
return redisStorager;
} else if ("jdbc".equals(vmConfig.getDatabase().toLowerCase())) {
return jdbcStorager;
}
return redisStorager;
}
}
package com.genersoft.iot.vmp.storager.jdbc;
import java.util.List;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
/**
* @Description:视频设备数据存储-jdbc实现
* @author: songww
* @date: 2020年5月6日 下午2:28:12
*/
@Component("jdbcStorager")
public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager {
/**
* 根据设备ID判断设备是否存在
*
* @param deviceId 设备ID
* @return true:存在 false:不存在
*/
@Override
public boolean exists(String deviceId) {
// TODO Auto-generated method stub
return false;
}
/**
* 视频设备创建
*
* @param device 设备对象
* @return true:创建成功 false:创建失败
*/
@Override
public boolean create(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 视频设备更新
*
* @param device 设备对象
* @return true:更新成功 false:更新失败
*/
@Override
public boolean update(Device device) {
// TODO Auto-generated method stub
return false;
}
/**
* 获取设备
*
* @param deviceId 设备ID
* @return Device 设备对象
*/
@Override
public Device queryVideoDevice(String deviceId) {
// TODO Auto-generated method stub
return null;
}
/**
* 获取多个设备
*
* @param deviceIds 设备ID数组
* @return List<Device> 设备对象数组
*/
@Override
public List<Device> queryVideoDeviceList(String[] deviceIds) {
// TODO Auto-generated method stub
return null;
}
/**
* 删除设备
*
* @param deviceId 设备ID
* @return true:删除成功 false:删除失败
*/
@Override
public boolean delete(String deviceId) {
// TODO Auto-generated method stub
return false;
}
/**
* 更新设备在线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
@Override
public boolean online(String deviceId) {
// TODO Auto-generated method stub
return false;
}
/**
* 更新设备离线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
@Override
public boolean outline(String deviceId) {
// TODO Auto-generated method stub
return false;
}
}
package com.genersoft.iot.vmp.storager.redis;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description:视频设备数据存储-redis实现
* @author: songww
* @date: 2020年5月6日 下午2:31:42
*/
@Component("redisStorager")
public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
@Autowired
private RedisUtil redis;
/**
* 根据设备ID判断设备是否存在
*
* @param deviceId 设备ID
* @return true:存在 false:不存在
*/
@Override
public boolean exists(String deviceId) {
return redis.hasKey(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
}
/**
* 视频设备创建
*
* @param device 设备对象
* @return true:创建成功 false:创建失败
*/
@Override
public boolean create(Device device) {
return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
}
/**
* 视频设备更新
*
* @param device 设备对象
* @return true:更新成功 false:更新失败
*/
@Override
public boolean update(Device device) {
return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
}
/**
* 获取设备
*
* @param deviceId 设备ID
* @return Device 设备对象
*/
@Override
public Device queryVideoDevice(String deviceId) {
return (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
}
/**
* 获取多个设备
*
* @param deviceIds 设备ID数组
* @return List<Device> 设备对象数组
*/
@Override
public List<Device> queryVideoDeviceList(String[] deviceIds) {
List<Device> devices = new ArrayList<>();
if (deviceIds == null || deviceIds.length == 0) {
List<Object> deviceIdList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX+"*");
for (int i = 0; i < deviceIdList.size(); i++) {
devices.add((Device)redis.get((String)deviceIdList.get(i)));
}
} else {
for (int i = 0; i < deviceIds.length; i++) {
devices.add((Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceIds[i]));
}
}
return devices;
}
/**
* 删除设备
*
* @param deviceId 设备ID
* @return true:删除成功 false:删除失败
*/
@Override
public boolean delete(String deviceId) {
return redis.del(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
}
/**
* 更新设备在线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
@Override
public boolean online(String deviceId) {
Device device = (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
device.setOnline(1);
return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
}
/**
* 更新设备离线
*
* @param deviceId 设备ID
* @return true:更新成功 false:更新失败
*/
@Override
public boolean outline(String deviceId) {
Device device = (Device)redis.get(VideoManagerConstants.CACHEKEY_PREFIX+deviceId);
device.setOnline(0);
return redis.set(VideoManagerConstants.CACHEKEY_PREFIX+device.getDeviceId(), device);
}
}
package com.genersoft.iot.vmp.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @Description:spring bean获取工厂,获取spring中的已初始化的bean
* @author: songww
* @date: 2019年6月25日 下午4:51:52
*
*/
@Component
public class SpringBeanFactory implements ApplicationContextAware {
// Spring应用上下文环境
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringBeanFactory.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取对象 这里重写了bean方法,起主要作用
*/
public static Object getBean(String beanId) throws BeansException {
return applicationContext.getBean(beanId);
}
/**
* 获取当前环境
*/
public static String getActiveProfile() {
return applicationContext.getEnvironment().getActiveProfiles()[0];
}
}
package com.genersoft.iot.vmp.utils.redis;
import java.nio.charset.Charset;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
/**
* @Description:使用fastjson实现redis的序列化
* @author: songww
* @date: 2020年5月6日 下午8:40:11
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
package com.genersoft.iot.vmp.utils.redis;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/**
* @Description:Redis工具类
* @author: songww
* @date: 2020年5月6日 下午8:27:29
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return true / false
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据 key 获取过期时间
* @param key 键
* @return
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断 key 是否存在
* @param key 键
* @return true / false
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @SuppressWarnings("unchecked") 忽略类型转换警告
* @param key 键(一个或者多个)
*/
public boolean del(String... key) {
try {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
// 传入一个 Collection<String> 集合
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ============================== String ==============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true / false
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒),如果 time < 0 则设置无限时间
* @return true / false
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 递增大小
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 递减大小
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
// ============================== Map ==============================
/**
* HashGet
* @param key 键(no null)
* @param item 项(no null)
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取 key 对应的 map
* @param key 键(no null)
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 值
* @return true / false
*/
public boolean hmset(String key, Map<Object, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 值
* @param time 时间
* @return true / false
*/
public boolean hmset(String key, Map<Object, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张 Hash表 中放入数据,如不存在则创建
* @param key 键
* @param item 项
* @param value 值
* @return true / false
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张 Hash表 中放入数据,并设置时间,如不存在则创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(如果原来的 Hash表 设置了时间,这里会覆盖)
* @return true / false
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除 Hash表 中的值
* @param key 键
* @param item 项(可以多个,no null)
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断 Hash表 中是否有该键的值
* @param key 键(no null)
* @param item 值(no null)
* @return true / false
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* Hash递增,如果不存在则创建一个,并把新增的值返回
* @param key 键
* @param item 项
* @param by 递增大小 > 0
* @return
*/
public Double hincr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* Hash递减
* @param key 键
* @param item 项
* @param by 递减大小
* @return
*/
public Double hdecr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================== Set ==============================
/**
* 根据 key 获取 set 中的所有值
* @param key 键
* @return 值
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从键为 key 的 set 中,根据 value 查询是否存在
* @param key 键
* @param value 值
* @return true / false
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入 set缓存
* @param key 键值
* @param values 值(可以多个)
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将数据放入 set缓存,并设置时间
* @param key 键
* @param time 时间
* @param values 值(可以多个)
* @return 成功放入个数
*/
public long sSet(String key, long time, Object... values) {
try {
long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取 set缓存的长度
* @param key 键
* @return 长度
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除 set缓存中,值为 value 的
* @param key 键
* @param values 值
* @return 成功移除个数
*/
public long setRemove(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ============================== List ==============================
/**
* 获取 list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束(0 到 -1 代表所有值)
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取 list缓存的长度
* @param key 键
* @return 长度
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 根据索引 index 获取键为 key 的 list 中的元素
* @param key 键
* @param index 索引
* 当 index >= 0 时 {0:表头, 1:第二个元素}
* 当 index < 0 时 {-1:表尾, -2:倒数第二个元素}
* @return 值
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将值 value 插入键为 key 的 list 中,如果 list 不存在则创建空 list
* @param key 键
* @param value 值
* @return true / false
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将值 value 插入键为 key 的 list 中,并设置时间
* @param key 键
* @param value 值
* @param time 时间
* @return true / false
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将 values 插入键为 key 的 list 中
* @param key 键
* @param values 值
* @return true / false
*/
public boolean lSetList(String key, List<Object> values) {
try {
redisTemplate.opsForList().rightPushAll(key, values);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将 values 插入键为 key 的 list 中,并设置时间
* @param key 键
* @param values 值
* @param time 时间
* @return true / false
*/
public boolean lSetList(String key, List<Object> values, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, values);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引 index 修改键为 key 的值
* @param key 键
* @param index 索引
* @param value 值
* @return true / false
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 在键为 key 的 list 中删除值为 value 的元素
* @param key 键
* @param count 如果 count == 0 则删除 list 中所有值为 value 的元素
* 如果 count > 0 则删除 list 中最左边那个值为 value 的元素
* 如果 count < 0 则删除 list 中最右边那个值为 value 的元素
* @param value
* @return
*/
public long lRemove(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 模糊查询
* @param key 键
* @return true / false
*/
public List<Object> keys(String key) {
try {
Set<String> set = redisTemplate.keys(key);
return new ArrayList<>(set);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
package com.genersoft.iot.vmp.vmanager.device;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceController {
private final static Logger logger = LoggerFactory.getLogger(DeviceController.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
@Autowired
private DeviceOffLineDetector offLineDetector;
@GetMapping("/devices/{deviceId}")
public ResponseEntity<Device> devices(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
logger.debug("查询视频设备API调用,deviceId:" + deviceId);
}
Device device = storager.queryVideoDevice(deviceId);
return new ResponseEntity<>(device,HttpStatus.OK);
}
@GetMapping("/devices")
public ResponseEntity<List<Device>> devices(){
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频设备API调用");
}
List<Device> deviceList = storager.queryVideoDeviceList(null);
return new ResponseEntity<>(deviceList,HttpStatus.OK);
}
@PostMapping("/devices/{deviceId}/sync")
public DeferredResult<ResponseEntity<Device>> devicesSync(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
logger.debug("设备信息同步API调用,deviceId:" + deviceId);
}
Device device = storager.queryVideoDevice(deviceId);
cmder.catalogQuery(device);
DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>();
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
return result;
}
@PostMapping("/devices/{deviceId}/delete")
public ResponseEntity<String> delete(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
logger.debug("设备信息删除API调用,deviceId:" + deviceId);
}
if (offLineDetector.isOnline(deviceId)) {
return new ResponseEntity<String>("不允许删除在线设备!", HttpStatus.NOT_ACCEPTABLE);
}
boolean isSuccess = storager.delete(deviceId);
if (isSuccess) {
JSONObject json = new JSONObject();
json.put("deviceId", deviceId);
return new ResponseEntity<>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备预览API调用失败!");
return new ResponseEntity<String>("设备预览API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
package com.genersoft.iot.vmp.vmanager.play;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class PlayController {
private final static Logger logger = LoggerFactory.getLogger(PlayController.class);
@Autowired
private SIPCommander cmder;
@Autowired
private IVideoManagerStorager storager;
@GetMapping("/play/{deviceId}/{channelId}")
public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){
Device device = storager.queryVideoDevice(deviceId);
String ssrc = cmder.playStreamCmd(device, channelId);
if (logger.isDebugEnabled()) {
logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
logger.debug("设备预览 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
}
if(ssrc!=null) {
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<String>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备预览API调用失败!");
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("/play/{ssrc}/stop")
public ResponseEntity<String> playStop(@PathVariable String ssrc){
cmder.streamByeCmd(ssrc);
if (logger.isDebugEnabled()) {
logger.debug(String.format("设备预览停止API调用,ssrc:%s", ssrc));
}
if(ssrc!=null) {
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<String>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备预览停止API调用失败!");
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
package com.genersoft.iot.vmp.vmanager.playback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class PlaybackController {
private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class);
@Autowired
private SIPCommander cmder;
@Autowired
private IVideoManagerStorager storager;
@GetMapping("/playback/{deviceId}/{channelId}")
public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){
if (logger.isDebugEnabled()) {
logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
}
if (StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(channelId)) {
String log = String.format("设备回放 API调用失败,deviceId:%s ,channelId:%s",deviceId, channelId);
logger.warn(log);
return new ResponseEntity<String>(log,HttpStatus.BAD_REQUEST);
}
Device device = storager.queryVideoDevice(deviceId);
String ssrc = cmder.playbackStreamCmd(device, channelId, startTime, endTime);
if (logger.isDebugEnabled()) {
logger.debug("设备回放 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
}
if(ssrc!=null) {
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<String>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备回放API调用失败!");
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("/playback/{ssrc}/stop")
public ResponseEntity<String> playStop(@PathVariable String ssrc){
cmder.streamByeCmd(ssrc);
if (logger.isDebugEnabled()) {
logger.debug(String.format("设备录像回放停止 API调用,ssrc:%s", ssrc));
}
if(ssrc!=null) {
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<String>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备录像回放停止API调用失败!");
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
package com.genersoft.iot.vmp.vmanager.ptz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class PtzController {
private final static Logger logger = LoggerFactory.getLogger(PtzController.class);
@Autowired
private SIPCommander cmder;
@Autowired
private IVideoManagerStorager storager;
/***
* http://localhost:8080/api/ptz/34020000001320000002_34020000001320000008?leftRight=1&upDown=0&inOut=0&moveSpeed=50&zoomSpeed=0
* @param deviceId
* @param channelId
* @param leftRight
* @param upDown
* @param inOut
* @param moveSpeed
* @param zoomSpeed
* @return
*/
@PostMapping("/ptz/{deviceId}/{channelId}")
public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
if (logger.isDebugEnabled()) {
logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,leftRight:%d ,upDown:%d ,inOut:%d ,moveSpeed:%d ,zoomSpeed:%d",deviceId, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed));
}
Device device = storager.queryVideoDevice(deviceId);
cmder.ptzCmd(device, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed);
return new ResponseEntity<String>("success",HttpStatus.OK);
}
}
package com.genersoft.iot.vmp.vmanager.record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class RecordController {
private final static Logger logger = LoggerFactory.getLogger(RecordController.class);
@Autowired
private SIPCommander cmder;
@Autowired
private IVideoManagerStorager storager;
@Autowired
private DeferredResultHolder resultHolder;
@GetMapping("/record/{deviceId}/{channelId}")
public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){
if (logger.isDebugEnabled()) {
logger.debug(String.format("录像信息查询 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime));
}
Device device = storager.queryVideoDevice(deviceId);
cmder.recordInfoQuery(device, channelId, startTime, endTime);
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>();
// 录像查询以channelId作为deviceId查询
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result);
return result;
}
}
spring:
application:
name: iot-vmp-vmanager
# 影子数据存储方式,支持redis、jdbc
database: redis
# 通信方式,支持kafka、http
communicate: http
redis:
# Redis服务器IP
#host: 10.24.20.63
host: 172.16.11.33
#端口号
port: 6379
datebase: 0
#访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
password: 1234560
#超时时间
timeout: 10000
datasource:
name: eiot
url: jdbc:mysql://10.24.20.63:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
server:
port: 8082
sip:
# ip: 10.200.64.63
ip: 172.16.10.98
port: 5060
# 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
# 后两位为行业编码,定义参照附录D.3
# 3701020049标识山东济南历下区 信息行业接入
domain: 3701020049
id: 37010200492000000001
# 默认设备认证密码,后续扩展使用设备单独密码
password: admin123
media:
# ip: 10.200.64.88
ip: 172.16.10.98
port: 10000
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.1.11" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.1.11" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:log4j-over-slf4j:1.7.25" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.17" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat:tomcat-jdbc:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat:tomcat-juli:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat:tomcat-annotations-api:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.27" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.hibernate:hibernate-validator:5.3.6.Final" level="project" />
<orderEntry type="library" name="Maven: javax.validation:validation-api:1.1.0.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.1.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.8.10" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.8.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.8.10" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid:1.0.11" level="project" />
<orderEntry type="module-library">
<library name="Maven: com.alibaba:jconsole:1.8.0">
<CLASSES>
<root url="jar://C:/Program Files/Java/jdk1.8.0_131/lib/jconsole.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Maven: com.alibaba:tools:1.8.0">
<CLASSES>
<root url="jar://C:/Program Files/Java/jdk1.8.0_131/lib/tools.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.30" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:1.2.4" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper:4.1.1" level="project" />
<orderEntry type="library" name="Maven: com.github.jsqlparser:jsqlparser:0.9.4" level="project" />
<orderEntry type="library" name="Maven: tk.mybatis:mapper:3.4.0" level="project" />
<orderEntry type="library" name="Maven: javax.persistence:persistence-api:1.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.4" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.33" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger2:2.6.1" level="project" />
<orderEntry type="library" name="Maven: io.swagger:swagger-annotations:1.5.10" level="project" />
<orderEntry type="library" name="Maven: io.swagger:swagger-models:1.5.10" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spi:2.6.1" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-core:2.6.1" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-schema:2.6.1" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger-common:2.6.1" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spring-web:2.6.1" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:18.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-metadata:1.2.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.mapstruct:mapstruct:1.0.0.Final" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger-ui:2.6.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:1.5.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.8.13" level="project" />
<orderEntry type="library" name="Maven: javax.sip:jain-sip-ri:1.3.0-91" level="project" />
<orderEntry type="library" name="Maven: org.dom4j:dom4j:2.1.3" level="project" />
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-redis:1.8.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-keyvalue:1.2.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:1.13.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-oxm:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:4.3.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jcl-over-slf4j:1.7.25" level="project" />
<orderEntry type="library" name="Maven: redis.clients:jedis:2.9.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.4.3" level="project" />
</component>
</module>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment