| | |
| | | |
| | | #VUE_APP_API_URL = 'http://192.168.1.82:10010' |
| | | |
| | | VUE_APP_API_URL = 'http://192.168.0.7/system_gateway' |
| | | #VUE_APP_API_URL = 'http://192.168.0.7/system_gateway' |
| | | |
| | | #VUE_APP_API_URL = 'http://localhost:10010' |
| | | VUE_APP_API_URL = 'http://localhost:10010' |
| | | |
| | | #keyï¼045542fc5f436b75e6c911c5c84ff8cd |
| | | #å¯é¥ï¼8bd38497f9aee2b75e7a888a4dfd1e6c |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '../../utils/request' |
| | | |
| | | export function fetchList (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/dockDevices/page', data, { trim: true }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '../../utils/request' |
| | | import axios from 'axios' |
| | | import Cookies from 'js-cookie' |
| | | |
| | | export function fetchList (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/media/page', data, { trim: true }) |
| | | } |
| | | |
| | | /** é¢è§æ¥å£ URL */ |
| | | export function buildPreviewUrl (id) { |
| | | const token = Cookies.get('dm_user_token') || '' |
| | | const base = process.env.VUE_APP_API_PREFIX + '/visitsAdmin/cloudService/business/collectionStation/media/preview/' + id |
| | | return token ? `${base}?dm_user_token=${encodeURIComponent(token)}` : base |
| | | } |
| | | |
| | | /** ä¸è½½å°æ¬å°æ¥å£ URL */ |
| | | export function buildDownloadUrl (id) { |
| | | const token = Cookies.get('dm_user_token') || '' |
| | | const base = process.env.VUE_APP_API_PREFIX + '/visitsAdmin/cloudService/business/collectionStation/media/download/' + id |
| | | return token ? `${base}?dm_user_token=${encodeURIComponent(token)}` : base |
| | | } |
| | | |
| | | function authHeaders () { |
| | | const token = Cookies.get('dm_user_token') |
| | | return token ? { dm_user_token: token } : {} |
| | | } |
| | | |
| | | function rejectBlobError (blob) { |
| | | if (!blob || blob.size === 0) { |
| | | return Promise.reject(new Error('æä»¶ä¸ºç©º')) |
| | | } |
| | | if (blob.type && blob.type.includes('json')) { |
| | | return blob.text().then(text => { |
| | | let message = 'æä½å¤±è´¥' |
| | | try { |
| | | const json = JSON.parse(text) |
| | | message = json.message || message |
| | | } catch (e) { |
| | | message = text || message |
| | | } |
| | | return Promise.reject(new Error(message)) |
| | | }) |
| | | } |
| | | return Promise.resolve(blob) |
| | | } |
| | | |
| | | /** é¢è§æ¥å£æåææ¬ï¼txt/logï¼ */ |
| | | export function fetchPreviewText (id) { |
| | | return axios({ |
| | | url: buildPreviewUrl(id), |
| | | method: 'get', |
| | | responseType: 'text', |
| | | headers: authHeaders() |
| | | }).then(res => { |
| | | const data = res.data |
| | | if (typeof data === 'string' && data.trim().startsWith('{') && data.includes('"success"')) { |
| | | try { |
| | | const json = JSON.parse(data) |
| | | if (json.success === false) { |
| | | return Promise.reject(new Error(json.message || 'é¢è§å¤±è´¥')) |
| | | } |
| | | } catch (e) { |
| | | // é JSON ååºï¼æææ¬å±ç¤º |
| | | } |
| | | } |
| | | return data || '' |
| | | }) |
| | | } |
| | | |
| | | export function fetchPreviewBlob (id) { |
| | | return axios({ |
| | | url: buildPreviewUrl(id), |
| | | method: 'get', |
| | | responseType: 'blob', |
| | | headers: authHeaders() |
| | | }).then(res => rejectBlobError(res.data)) |
| | | } |
| | | |
| | | /** ä¸è½½å·²å
¥åºåªä½å°æ¬å° */ |
| | | export function fetchMediaFile (id) { |
| | | return axios({ |
| | | url: buildDownloadUrl(id), |
| | | method: 'get', |
| | | responseType: 'blob', |
| | | headers: authHeaders() |
| | | }).then(res => { |
| | | if (res.headers['content-type'] === 'application/json') { |
| | | return rejectBlobError(res.data).then(() => res) |
| | | } |
| | | return res |
| | | }) |
| | | } |
| | | |
| | | /** æ ¡éª Blob æ¯å¦ä¸º MP4 å®¹å¨ */ |
| | | export function ensureMp4Blob (blob) { |
| | | return blob.slice(0, 12).arrayBuffer().then(buf => { |
| | | const arr = new Uint8Array(buf) |
| | | const isMp4 = arr.length >= 8 && arr[4] === 0x66 && arr[5] === 0x74 && arr[6] === 0x79 && arr[7] === 0x70 |
| | | if (!isMp4) { |
| | | return Promise.reject(new Error('æä»¶ä¸æ¯ææç MP4 æ ¼å¼ï¼è¯·éæ°ä¸è½½è¯¥åªä½')) |
| | | } |
| | | return new Blob([blob], { type: 'video/mp4' }) |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '../../utils/request' |
| | | |
| | | export function fetchList (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/page', data, { trim: true }) |
| | | } |
| | | |
| | | export function list (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/list', data, { trim: true }) |
| | | } |
| | | |
| | | export function fetchMediaList (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/media/page', data, { trim: true }) |
| | | } |
| | | |
| | | export function create (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/create', data) |
| | | } |
| | | |
| | | export function updateById (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/updateById', data) |
| | | } |
| | | |
| | | export function deleteById (id) { |
| | | return request.get(`/visitsAdmin/cloudService/business/collectionStation/delete/${id}`) |
| | | } |
| | | |
| | | export function deleteByIdInBatch (ids) { |
| | | return request.get('/visitsAdmin/cloudService/business/collectionStation/delete/batch', { params: { ids } }) |
| | | } |
| | | |
| | | export function syncDevices () { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/syncDevices', {}) |
| | | } |
| | | |
| | | export function syncDevice (id) { |
| | | return request.post(`/visitsAdmin/cloudService/business/collectionStation/syncDevice/${id}`, {}) |
| | | } |
| | | |
| | | export function syncMedia (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/syncMedia', data) |
| | | } |
| | | |
| | | export function downloadMedia (id) { |
| | | return request.post(`/visitsAdmin/cloudService/business/collectionStation/downloadMedia/${id}`, {}) |
| | | } |
| | | |
| | | export function batchDownloadMedia (data) { |
| | | return request.post('/visitsAdmin/cloudService/business/collectionStation/batchDownloadMedia', data) |
| | | } |
| | | |
| | | export function probeIsapi (id) { |
| | | return request.get(`/visitsAdmin/cloudService/business/collectionStation/probe/${id}`) |
| | | } |
| | | |
| | | export function fetchDockDevices (stationId) { |
| | | return request.get(`/visitsAdmin/cloudService/business/collectionStation/dockDevices/${stationId}`) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <GlobalWindow |
| | | :title="title" |
| | | :visible.sync="visible" |
| | | :confirm-working="isWorking" |
| | | @confirm="confirm" |
| | | > |
| | | <el-form :model="form" ref="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="åç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="ééç«åç§°" v-trim /> |
| | | </el-form-item> |
| | | <el-form-item label="IP" prop="ip"> |
| | | <el-input v-model="form.ip" placeholder="设å¤IP" v-trim /> |
| | | </el-form-item> |
| | | <el-form-item label="端å£" prop="port"> |
| | | <el-input-number v-model="form.port" :min="1" :max="65535" /> |
| | | </el-form-item> |
| | | <el-form-item label="HTTPS" prop="useHttps"> |
| | | <el-select v-model="form.useHttps" placeholder="è¯·éæ©"> |
| | | <el-option label="å¦" :value="0" /> |
| | | <el-option label="æ¯" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¨æ·å" prop="username"> |
| | | <el-input v-model="form.username" placeholder="ISAPIç¨æ·å" v-trim /> |
| | | </el-form-item> |
| | | <el-form-item label="å¯ç " prop="password"> |
| | | <el-input v-model="form.password" type="password" placeholder="ISAPIå¯ç " show-password /> |
| | | </el-form-item> |
| | | <el-form-item label="åå·" prop="model"> |
| | | <el-input v-model="form.model" placeholder="UD39625B" v-trim /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©"> |
| | | <el-option label="å¯ç¨" :value="1" /> |
| | | <el-option label="ç¦ç¨" :value="0" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" placeholder="夿³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </GlobalWindow> |
| | | </template> |
| | | |
| | | <script> |
| | | import BaseOpera from '@/components/base/BaseOpera' |
| | | import GlobalWindow from '@/components/common/GlobalWindow' |
| | | export default { |
| | | name: 'OperaCollectionStationWindow', |
| | | extends: BaseOpera, |
| | | components: { GlobalWindow }, |
| | | data () { |
| | | return { |
| | | form: { |
| | | id: null, |
| | | name: '', |
| | | ip: '', |
| | | port: 80, |
| | | useHttps: 0, |
| | | username: 'admin', |
| | | password: '', |
| | | model: 'UD39625B', |
| | | status: 1, |
| | | remark: '' |
| | | }, |
| | | rules: { |
| | | ip: [{ required: true, message: '请è¾å
¥IP', trigger: 'blur' }], |
| | | username: [{ required: true, message: '请è¾å
¥ç¨æ·å', trigger: 'blur' }], |
| | | password: [{ required: true, message: '请è¾å
¥å¯ç ', trigger: 'blur' }] |
| | | } |
| | | } |
| | | }, |
| | | created () { |
| | | this.config({ |
| | | api: '/business/collectionStation', |
| | | 'field.id': 'id' |
| | | }) |
| | | } |
| | | } |
| | | </script> |
| | |
| | | <scrollbar> |
| | | <el-menu |
| | | ref="menu" |
| | | :default-active="activeIndex" |
| | | :key="menuRenderKey" |
| | | :default-active="activeMenuIndex" |
| | | text-color="#333333" |
| | | active-text-color="#207FF7" |
| | | :collapse="menuData.collapse" |
| | | :default-openeds="defaultOpeneds" |
| | | :default-openeds="openMenuIndexes" |
| | | :collapse-transition="false" |
| | | unique-opened |
| | | @select="handleSelect" |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { mapState } from 'vuex' |
| | | import { mapState, mapMutations } from 'vuex' |
| | | import MenuItems from './MenuItems' |
| | | import Scrollbar from './Scrollbar' |
| | | import { findMenuByRoute } from '@/utils/menuRoute' |
| | | |
| | | export default { |
| | | name: 'Menu', |
| | | components: { Scrollbar, MenuItems }, |
| | | computed: { |
| | | ...mapState(['menuData']), |
| | | // éä¸çèåindex |
| | | activeIndex() { |
| | | let path = this.$route.path |
| | | if (path.endsWith('/')) { |
| | | path = path.substring(0, path.length - 1) |
| | | } |
| | | const menuConfig = this.__getMenuConfig(path, 'index', this.menuData.list) |
| | | if (menuConfig == null) { |
| | | return null |
| | | } else { |
| | | this.$store.commit('pushtags', menuConfig) |
| | | } |
| | | // console.log(menuConfig.index); |
| | | return menuConfig.index |
| | | }, |
| | | // é»è®¤å±å¼çèåindex |
| | | defaultOpeneds() { |
| | | // return this.menuData.list.map(menu => menu.index) |
| | | return [this.menuData.list[0].index] |
| | | data () { |
| | | return { |
| | | activeMenuIndex: '', |
| | | openMenuIndexes: [], |
| | | menuRenderKey: 0 |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState(['menuData', 'topMenuList']) |
| | | }, |
| | | watch: { |
| | | /* $route (to, from) { |
| | | var that =this |
| | | this.$nextTick(() => { |
| | | setTimeout(function(){ that.computeTableHeight()},1000) |
| | | }) |
| | | }*/ |
| | | '$route' () { |
| | | this.syncMenuFromRoute() |
| | | }, |
| | | 'menuData.list' () { |
| | | this.syncMenuFromRoute() |
| | | } |
| | | }, |
| | | mounted () { |
| | | this.syncMenuFromRoute() |
| | | }, |
| | | methods: { |
| | | ...mapMutations(['pushtags', 'syncTopMenuFromRoute']), |
| | | syncMenuFromRoute () { |
| | | const path = this.normalizePath(this.$route.path) |
| | | const menuIndex = this.$route.query.index |
| | | const result = findMenuByRoute(this.topMenuList.list, path, menuIndex) |
| | | if (result == null) { |
| | | return |
| | | } |
| | | if (result.topMenu.id !== this.$store.state.topMenuCurrent.id) { |
| | | this.syncTopMenuFromRoute({ |
| | | topMenu: result.topMenu, |
| | | topIndex: result.topIndex |
| | | }) |
| | | } |
| | | this.activeMenuIndex = result.menu.index |
| | | this.openMenuIndexes = result.parents.map(item => item.index) |
| | | this.menuRenderKey += 1 |
| | | this.pushtags(result.menu) |
| | | this.$nextTick(() => { |
| | | this.openParentMenus() |
| | | }) |
| | | }, |
| | | openParentMenus () { |
| | | const menuRef = this.$refs.menu |
| | | if (!menuRef || this.openMenuIndexes.length === 0) { |
| | | return |
| | | } |
| | | this.openMenuIndexes.forEach(index => { |
| | | if (typeof menuRef.open === 'function') { |
| | | menuRef.open(index) |
| | | } |
| | | }) |
| | | }, |
| | | normalizePath (path) { |
| | | if (path == null || path === '') { |
| | | return '' |
| | | } |
| | | return path.endsWith('/') ? path.substring(0, path.length - 1) : path |
| | | }, |
| | | // å¤çèåéä¸ |
| | | handleSelect(menuIndex) { |
| | | handleSelect (menuIndex) { |
| | | const menuConfig = this.__getMenuConfig(menuIndex, 'index', this.menuData.list) |
| | | if (menuConfig == null) { |
| | | return |
| | | } |
| | | // æ¾ä¸å°é¡µé¢ |
| | | try { |
| | | require('@/views' + menuConfig.url) |
| | |
| | | return |
| | | } |
| | | // ç¹å»å½åèåä¸åå¤ç |
| | | if (menuConfig.url === this.$route.path && (menuConfig.params == null || menuConfig.params == undefined || menuConfig.params == '' || menuConfig.params === this.$route.query.param)) { |
| | | if (menuConfig.url === this.$route.path && (menuConfig.params == null || menuConfig.params === undefined || menuConfig.params === '' || menuConfig.params === this.$route.query.param)) { |
| | | return |
| | | } |
| | | if (menuConfig.url == null || menuConfig.url.trim().length === 0) { |
| | | return |
| | | } |
| | | if (menuConfig.params != null && menuConfig.params != '') { |
| | | // this.$router.push({ path: menuConfig.url, query: { index: menuConfig.index, param: menuConfig.params } }) |
| | | } else { |
| | | // this.$router.push(menuConfig.url) |
| | | } |
| | | |
| | | this.$router.push({ path: menuConfig.url, query: { index: menuConfig.index, param: menuConfig.params, time: (Math.random().toString()) } }) |
| | | this.$store.commit('pushtags', menuConfig) |
| | | this.pushtags(menuConfig) |
| | | }, |
| | | // è·åèåé
ç½® |
| | | __getMenuConfig(value, key, menus) { |
| | | __getMenuConfig (value, key, menus) { |
| | | for (const menu of menus) { |
| | | if (menu[key] === value) { |
| | | return menu |
| | |
| | | computeTableHeight () { |
| | | this.$nextTick(() => { |
| | | const height = window.innerHeight |
| | | // console.log('main_app========================ï¼'+height) |
| | | const height13 = this.getEleHeghtByClassName('common-header',0) |
| | | const height5 = document.getElementsByTagName( 'thead') && document.getElementsByTagName('thead')[0] ? document.getElementsByTagName('thead')[0].clientHeight : 0 |
| | | const height13 = this.getEleHeghtByClassName('common-header', 0) |
| | | const height5 = document.getElementsByTagName('thead') && document.getElementsByTagName('thead')[0] ? document.getElementsByTagName('thead')[0].clientHeight : 0 |
| | | if (document.getElementsByClassName('main_app') && document.getElementsByClassName('main_app')[0]) { |
| | | // console.log('main_app========================') |
| | | // alert(height) |
| | | const height3 = this.getEleHeghtByClassName('main-header',0) |
| | | const height4 = this.getEleHeghtByClassName('table-pagination',0) |
| | | const height2 = this.getEleHeghtByClassName('toolbar',0) |
| | | const height6 = this.getEleHeghtByClassName('doumee-filter',0,16) |
| | | const height7 = this.getEleHeghtByClassName('pt16',0,0) |
| | | const height9 = this.getEleHeghtByClassName('static_wrap',0) |
| | | const height10 = this.getEleHeghtByClassName('query_btns',0) |
| | | const height11 = this.getEleHeghtByClassName('el-tabs-ele',0) |
| | | const height12 = this.getEleHeghtByClassName('platgroup_tabs',0) |
| | | this.$router.app.$store.commit('setTableHeightNew', height - height13- height3 - height5 - height6 - height2 - height7 - height4 - height9 - height10 - height11 - height12) |
| | | // console.log('gableHeightNew', this.$router.app.$store.state.tableHeightNew) |
| | | } else { |
| | | // console.log('tableLayout========================') |
| | | const height1 = this.getEleHeghtByClassName('table-search-form', 40,16) |
| | | const height3 = this.getEleHeghtByClassName('main-header', 0) |
| | | const height4 = this.getEleHeghtByClassName('table-pagination', 0) |
| | | const height2 = this.getEleHeghtByClassName('toolbar', 0) |
| | | // console.log('defualtlength', document.getElementsByClassName('table-search-form').length) |
| | | const height6 = this.getEleHeghtByClassName('doumee-filter', 0, 16) |
| | | const height7 = this.getEleHeghtByClassName('pt16', 0, 0) |
| | | const height9 = this.getEleHeghtByClassName('static_wrap', 0) |
| | | const height10 = this.getEleHeghtByClassName('query_btns', 0) |
| | | const height11 = this.getEleHeghtByClassName('el-tabs-ele', 0) |
| | | const height12 = this.getEleHeghtByClassName('platgroup_tabs', 0) |
| | | this.$router.app.$store.commit('setTableHeightNew', height - height13 - height3 - height5 - height6 - height2 - height7 - height4 - height9 - height10 - height11 - height12) |
| | | } else { |
| | | const height1 = this.getEleHeghtByClassName('table-search-form', 40, 16) |
| | | const height3 = this.getEleHeghtByClassName('main-header', 0) |
| | | const height4 = this.getEleHeghtByClassName('table-pagination', 0) |
| | | const height2 = this.getEleHeghtByClassName('toolbar', 0) |
| | | this.$router.app.$store.commit('setTableHeightNew', height - height4 - height3 - height2 - height1 - height5 - height13) |
| | | // console.log('gableHeightNew', this.$router.app.$store.state.tableHeightNew) |
| | | } |
| | | }) |
| | | }, |
| | | getEleHeghtByClassName (name, dv,margin) { |
| | | getEleHeghtByClassName (name, dv, margin) { |
| | | if ((document.getElementsByClassName(name) && document.getElementsByClassName(name)[0])) { |
| | | let t = 0 |
| | | document.getElementsByClassName(name).forEach(e => { |
| | | // console.log(name+'========================' + t + ':' + e.clientHeight) |
| | | t++ |
| | | }) |
| | | return document.getElementsByClassName(name)[document.getElementsByClassName(name).length - 1].clientHeight+(margin||0) |
| | | return document.getElementsByClassName(name)[document.getElementsByClassName(name).length - 1].clientHeight + (margin || 0) |
| | | } |
| | | return dv || 0 |
| | | } |
| | |
| | | // è§é¢ä¸ä¼ |
| | | uploadVideo: { |
| | | fieldName: 'file', |
| | | server: process.env.VUE_APP_API_PREFIX + '/public/upload?folder=richeditor', |
| | | server: process.env.VUE_APP_API_PREFIX + '/visitsAdmin/cloudService/public/upload?folder=richeditor', |
| | | // å个æä»¶çæå¤§ä½ç§¯éå¶ï¼é»è®¤ä¸º 10M |
| | | maxFileSize: 50 * 1024 * 1024, // 50M |
| | | maxFileSize: 500 * 1024 * 1024, // 50M |
| | | // æå¤å¯ä¸ä¼ å 个æä»¶ï¼é»è®¤ä¸º 5 |
| | | maxNumberOfFiles: 3, |
| | | // éæ©æä»¶æ¶çç±»åéå¶ï¼é»è®¤ä¸º ['video/*'] ãå¦ä¸æ³éå¶ï¼å设置为 [] |
| | |
| | | :key="index" |
| | | :id="'tags-box-' + index" |
| | | @contextmenu.prevent="openMenu(item, $event)" |
| | | :class="isActive(item.url, item.index,index) ? 'active' : ''" |
| | | :class="isActive(item.url, item.index) ? 'active' : ''" |
| | | class="tagsview" |
| | | @click="tagsmenu(item, index)" |
| | | > |
| | |
| | | } |
| | | } else { |
| | | // é£ä¹ï¼å¦æä¸é¢çæ¡ä»¶é½ä¸æç«ï¼æ²¡ælength=0.ä¹å°±æ¯è¯´ä½ è¿æå¥½å 个æ ç¾ï¼å¹¶ä¸ä½ å é¤çæ¯æåä¸ä½æ ç¾ï¼é£ä¹å°±å¾å·¦è¾¹æªä¸ä½è·³è½¬è·¯ç± |
| | | this.$router.push({ path: this.tags[index - 1].url, query: { param: this.tags[index - 1].params } }) |
| | | this.$router.push({ |
| | | path: this.tags[index - 1].url, |
| | | query: { index: this.tags[index - 1].index, param: this.tags[index - 1].params } |
| | | }) |
| | | } |
| | | } else { |
| | | // å¦æä½ ç¹å»ä¸æ¯æåä¸ä½æ ç¾ï¼ç¹çåé¢çï¼é£å°±å¾å³è¾¹è·³è½¬ |
| | | this.$router.push({ path: this.tags[index].url, query: { param: this.tags[index].params } }) |
| | | this.$router.push({ |
| | | path: this.tags[index].url, |
| | | query: { index: this.tags[index].index, param: this.tags[index].params } |
| | | }) |
| | | } |
| | | }, |
| | | // ç¹å»è·³è½¬è·¯ç± |
| | | tagsmenu (item, index) { |
| | | console.log('tagsmenu') |
| | | // 夿ï¼å½åè·¯ç±ä¸çäºå½åéä¸é¡¹çurlï¼ä¹å°±ä»£è¡¨ä½ ç¹å»ç䏿¯ç°å¨éä¸çæ ç¾ï¼æ¯å¦ä¸ä¸ªæ ç¾å°±è·³è½¬è¿å»ï¼å¦æä½ ç¹å»çæ¯ç°å¨å·²ç»éä¸çæ ç¾å°±ä¸ç¨è·³è½¬äºï¼å ä¸ºä½ å·²ç»å¨è¿ä¸ªè·¯ç±äºè¿è·³ä»ä¹å¢ã |
| | | if (this.$route.path !== item.url) { |
| | | // ç¨pathçè·³è½¬æ¹æ³æå½å项çurlå½ä½å°å跳转ã |
| | | this.$router.push({ path: item.url, query: { index: this.tags[index].index, param: this.tags[index].params, time: (Math.random().toString())} }) |
| | | // this.$router.push( item.url) |
| | | if (this.$route.path !== item.url || this.$route.query.index !== item.index) { |
| | | this.$router.push({ |
| | | path: item.url, |
| | | query: { |
| | | index: item.index, |
| | | param: item.params, |
| | | time: (Math.random().toString()) |
| | | } |
| | | }) |
| | | const tagsDiv = document.getElementById('tags-box') |
| | | if (index) { |
| | | if (tagsDiv && index) { |
| | | tagsDiv.scrollTo(index * 110, 0) |
| | | } |
| | | } |
| | | // this.computeTableHeight() |
| | | }, |
| | | computeTableHeight () { |
| | | this.$nextTick(() => { |
| | |
| | | return dv || 0 |
| | | }, |
| | | // éè¿å¤æè·¯ç±ä¸è´è¿åå¸å°å¼æ·»å classï¼æ·»å é«äº®ææ |
| | | isActive (route, params, index) { |
| | | const res = (route === this.$route.path && params == this.$route.query.index) |
| | | return res |
| | | isActive (route, index) { |
| | | return route === this.$route.path && index === this.$route.query.index |
| | | }, |
| | | scrollToStart () { |
| | | const tagsDiv = document.getElementById('tags-box') |
| | |
| | | if (this.topMenuCurrent == null) { |
| | | return |
| | | } |
| | | if (this.$store.state.skipTopMenuNavigation) { |
| | | this.$store.state.skipTopMenuNavigation = false |
| | | return |
| | | } |
| | | await this.chagneRoutes() |
| | | } |
| | | }, |
| | |
| | | // tagsviewæ ç¾æ¾ç¤ºéè |
| | | isCollapse: false, |
| | | // é¡¶é¨èåç´¢å¼ |
| | | currentIndex: 0 |
| | | currentIndex: 0, |
| | | // è·¯ç±åæ¥é¡¶çº§èåæ¶è·³è¿é¦é¡µè·³è½¬ |
| | | skipTopMenuNavigation: false |
| | | } |
| | | |
| | | const mutations = { |
| | |
| | | // 设置é¦é¡µè·¯ç±ä¿¡æ¯ |
| | | setTopMenuCurrent (state, current) { |
| | | console.log('setTopMenuCurrent', current) |
| | | state.skipTopMenuNavigation = false |
| | | if (current.id !== state.topMenuCurrent.id) { |
| | | state.topMenuList.list.forEach(item => { |
| | | state.topMenuList.list.forEach((item, index) => { |
| | | console.log(item.id, item.id) |
| | | if (current.id == item.id) { |
| | | state.topMenuCurrent = current |
| | | state.menuData.list = item.children |
| | | state.currentIndex = index |
| | | } |
| | | }) |
| | | } |
| | | }, |
| | | syncTopMenuFromRoute (state, { topMenu, topIndex }) { |
| | | if (!topMenu || topMenu.id === state.topMenuCurrent.id) { |
| | | return |
| | | } |
| | | state.skipTopMenuNavigation = true |
| | | state.topMenuCurrent = topMenu |
| | | state.menuData.list = topMenu.children || [] |
| | | state.currentIndex = topIndex |
| | | }, |
| | | // éç½®èå |
| | | resetMenus: (state) => { |
| | | state.topMenuId = null |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | function normalizePath (path) { |
| | | if (!path) { |
| | | return '' |
| | | } |
| | | return path.endsWith('/') ? path.substring(0, path.length - 1) : path |
| | | } |
| | | |
| | | function matchMenuRoute (menu, path, index) { |
| | | if (!menu || !menu.url) { |
| | | return false |
| | | } |
| | | if (normalizePath(menu.url) !== path) { |
| | | return false |
| | | } |
| | | if (index != null && index !== '' && menu.index !== index) { |
| | | return false |
| | | } |
| | | return true |
| | | } |
| | | |
| | | function findMenuInTree (menus, path, index, parents = []) { |
| | | if (!menus || menus.length === 0) { |
| | | return null |
| | | } |
| | | for (const menu of menus) { |
| | | if (matchMenuRoute(menu, path, index)) { |
| | | return { menu, parents } |
| | | } |
| | | if (menu.children && menu.children.length > 0) { |
| | | const found = findMenuInTree(menu.children, path, index, parents.concat(menu)) |
| | | if (found) { |
| | | return found |
| | | } |
| | | } |
| | | } |
| | | return null |
| | | } |
| | | |
| | | export function findMenuByRoute (topMenuList, path, index) { |
| | | const normalizedPath = normalizePath(path) |
| | | if (!normalizedPath || !topMenuList || topMenuList.length === 0) { |
| | | return null |
| | | } |
| | | for (let topIndex = 0; topIndex < topMenuList.length; topIndex++) { |
| | | const topMenu = topMenuList[topIndex] |
| | | if (topMenu.linkType !== 0) { |
| | | continue |
| | | } |
| | | let found = null |
| | | if (index != null && index !== '') { |
| | | found = findMenuInTree(topMenu.children || [], normalizedPath, index) |
| | | } |
| | | if (!found) { |
| | | found = findMenuInTree(topMenu.children || [], normalizedPath, null) |
| | | } |
| | | if (found) { |
| | | return { |
| | | topMenu, |
| | | topIndex, |
| | | menu: found.menu, |
| | | parents: found.parents |
| | | } |
| | | } |
| | | } |
| | | return null |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <TableLayout :permissions="['business:collectionDockDevice:query', 'business:collectionStation:query']"> |
| | | <div ref="QueryFormRef" slot="search-form"> |
| | | <el-form ref="searchForm" :model="searchForm" label-width="100px" inline> |
| | | <el-form-item label="ééç«" prop="stationId"> |
| | | <el-select v-model="searchForm.stationId" placeholder="å
¨é¨" clearable filterable @change="search"> |
| | | <el-option v-for="item in stationOptions" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="searchForm.deviceName" placeholder="设å¤åç§°" @keypress.enter.native="search" /> |
| | | </el-form-item> |
| | | <el-form-item label="åºåå·" prop="shortSerialNumber"> |
| | | <el-input v-model="searchForm.shortSerialNumber" placeholder="çåºåå·" @keypress.enter.native="search" /> |
| | | </el-form-item> |
| | | <section> |
| | | <el-button type="primary" @click="search">æç´¢</el-button> |
| | | <el-button @click="reset">éç½®</el-button> |
| | | </section> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <template v-slot:table-wrap> |
| | | <ul class="toolbar"> |
| | | <li> |
| | | <el-button type="primary" v-permissions="['business:collectionStation:sync']" @click="handleSyncAll">忥å
¨é¨ééç«</el-button> |
| | | </li> |
| | | <li> |
| | | <el-button v-permissions="['business:collectionStation:sync']" :disabled="!searchForm.stationId" @click="handleSyncOne">忥å½åééç«</el-button> |
| | | </li> |
| | | </ul> |
| | | <el-table :height="tableHeightNew" v-loading="isWorking.search" :data="tableData.list" stripe> |
| | | <el-table-column label="åºå·" width="55"><template slot-scope="scope">{{ scope.$index + 1 }}</template></el-table-column> |
| | | <el-table-column prop="stationName" label="ééç«" min-width="120" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="deviceId" label="设å¤ID" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="shortSerialNumber" label="çåºåå·" min-width="140" /> |
| | | <el-table-column prop="accessDeviceId" label="平尿³¨åID" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="networkedDevice" label="èç½è®¾å¤" width="90"> |
| | | <template slot-scope="{row}"> |
| | | <span v-if="row.networkedDevice === 1">æ¯</span> |
| | | <span v-else-if="row.networkedDevice === 0">å¦</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="lastSyncTime" label="æè¿åæ¥" min-width="160" /> |
| | | <el-table-column prop="createDate" label="å建æ¶é´" min-width="160" /> |
| | | </el-table> |
| | | <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination" /> |
| | | </template> |
| | | </TableLayout> |
| | | </template> |
| | | |
| | | <script> |
| | | import BaseTable from '@/components/base/BaseTable' |
| | | import TableLayout from '@/layouts/TableLayout' |
| | | import Pagination from '@/components/common/Pagination' |
| | | import { list as fetchStationList, syncDevices, syncDevice } from '@/api/business/collectionStation' |
| | | |
| | | export default { |
| | | name: 'CollectionDockDevice', |
| | | extends: BaseTable, |
| | | components: { TableLayout, Pagination }, |
| | | data () { |
| | | return { |
| | | stationOptions: [], |
| | | searchForm: { |
| | | stationId: null, |
| | | deviceName: '', |
| | | shortSerialNumber: '' |
| | | } |
| | | } |
| | | }, |
| | | created () { |
| | | const stationId = this.$route.query.stationId ? parseInt(this.$route.query.stationId) : null |
| | | if (stationId) { |
| | | this.searchForm.stationId = stationId |
| | | } |
| | | this.config({ |
| | | module: 'æ§æ³è®°å½ä»ª', |
| | | api: '/business/collectionDockDevice', |
| | | 'field.id': 'id', |
| | | 'field.main': 'deviceName' |
| | | }) |
| | | this.loadStations() |
| | | this.search() |
| | | }, |
| | | methods: { |
| | | loadStations () { |
| | | fetchStationList({ status: 1 }).then(res => { |
| | | this.stationOptions = res || [] |
| | | }) |
| | | }, |
| | | handleSyncAll () { |
| | | syncDevices().then(res => { |
| | | this.$message.success(res.data || '忥宿') |
| | | this.search() |
| | | }) |
| | | }, |
| | | handleSyncOne () { |
| | | syncDevice(this.searchForm.stationId).then(res => { |
| | | this.$message.success(res.data || '忥æå') |
| | | this.search() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <TableLayout :permissions="['business:collectionMedia:query', 'business:collectionStation:query']"> |
| | | <div ref="QueryFormRef" slot="search-form"> |
| | | <el-form ref="searchForm" :model="searchForm" label-width="100px" inline> |
| | | <el-form-item label="ééç«" prop="stationId"> |
| | | <el-select v-model="searchForm.stationId" placeholder="å
¨é¨" clearable filterable @change="onStationChange"> |
| | | <el-option v-for="item in stationOptions" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¸è½½ç¶æ" prop="downloadStatus"> |
| | | <el-select v-model="searchForm.downloadStatus" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="å¾
ä¸è½½" :value="0" /> |
| | | <el-option label="å·²ä¸è½½" :value="1" /> |
| | | <el-option label="失败" :value="2" /> |
| | | <el-option label="ä¸è½½ä¸" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç±»å" prop="mediaType"> |
| | | <el-select v-model="searchForm.mediaType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="è§é¢" :value="0" /> |
| | | <el-option label="å¾ç" :value="1" /> |
| | | <el-option label="é³é¢" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <section> |
| | | <el-button type="primary" @click="search">æç´¢</el-button> |
| | | <el-button @click="reset">éç½®</el-button> |
| | | </section> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <template v-slot:table-wrap> |
| | | <ul class="toolbar"> |
| | | <li> |
| | | <el-button v-permissions="['business:collectionMedia:sync', 'business:collectionStation:sync']" @click="handleSyncMedia">åæ¥ç´¢å¼</el-button> |
| | | </li> |
| | | <li> |
| | | <el-button type="primary" v-permissions="['business:collectionMedia:download', 'business:collectionStation:sync']" @click="handleBatchDownload">æ¹éä¸è½½</el-button> |
| | | </li> |
| | | </ul> |
| | | <el-table :height="tableHeightNew" v-loading="isWorking.search" :data="tableData.list" stripe> |
| | | <el-table-column label="åºå·" width="55"><template slot-scope="scope">{{ scope.$index + 1 }}</template></el-table-column> |
| | | <el-table-column prop="fileName" label="æä»¶å" min-width="180" show-overflow-tooltip /> |
| | | <el-table-column prop="mediaType" label="ç±»å" width="80"> |
| | | <template slot-scope="{row}"> |
| | | <span>{{ mediaTypeLabel(row) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="fileSize" label="大å°(åè)" min-width="100" /> |
| | | <el-table-column prop="startTime" label="å¼å§æ¶é´" min-width="160" /> |
| | | <el-table-column prop="endTime" label="ç»ææ¶é´" min-width="160" /> |
| | | <el-table-column prop="downloadStatus" label="ä¸è½½ç¶æ" width="90"> |
| | | <template slot-scope="{row}"> |
| | | <span v-if="row.downloadStatus === 1">å·²ä¸è½½</span> |
| | | <span v-else-if="row.downloadStatus === 2">失败</span> |
| | | <span v-else-if="row.downloadStatus === 3">ä¸è½½ä¸</span> |
| | | <span v-else>å¾
ä¸è½½</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="filePathLocal" label="æ¬å°è·¯å¾" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" width="220" fixed="right"> |
| | | <template slot-scope="{row}"> |
| | | <el-button type="text" v-if="canPreview(row)" @click="handlePreview(row)">é¢è§</el-button> |
| | | <el-button type="text" v-if="canDownloadToStation(row)" v-permissions="['business:collectionMedia:download', 'business:collectionStation:sync']" |
| | | @click="handleDownload(row)">ä¸è½½</el-button> |
| | | <el-button type="text" v-if="row.downloadStatus === 1" @click="handleSaveLocal(row)">ä¸è½½å°æ¬å°</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination" /> |
| | | </template> |
| | | |
| | | <el-dialog :title="previewTitle" :visible.sync="previewVisible" width="860px" append-to-body @close="closePreview"> |
| | | <div v-loading="previewLoading" class="preview-wrap"> |
| | | <video v-if="previewMode === 'video' && previewSrc" :key="previewSrc" :src="previewSrc" controls |
| | | preload="metadata" playsinline class="preview-video" @error="onPreviewMediaError" /> |
| | | <audio v-else-if="previewMode === 'audio' && previewSrc" :key="previewSrc" :src="previewSrc" controls class="preview-audio" |
| | | @error="onPreviewMediaError" /> |
| | | <el-image v-else-if="previewMode === 'image' && previewSrc" :src="previewSrc" fit="contain" class="preview-image" |
| | | :preview-src-list="[previewSrc]" /> |
| | | <pre v-else-if="previewMode === 'text'" class="preview-text">{{ previewText }}</pre> |
| | | <div v-else-if="!previewLoading" class="preview-empty">æä¸æ¯æè¯¥ç±»åé¢è§</div> |
| | | </div> |
| | | </el-dialog> |
| | | </TableLayout> |
| | | </template> |
| | | |
| | | <script> |
| | | import BaseTable from '@/components/base/BaseTable' |
| | | import TableLayout from '@/layouts/TableLayout' |
| | | import Pagination from '@/components/common/Pagination' |
| | | import { syncMedia, downloadMedia, batchDownloadMedia, list as fetchStationList } from '@/api/business/collectionStation' |
| | | import { fetchPreviewText, fetchPreviewBlob, fetchMediaFile, ensureMp4Blob, buildPreviewUrl } from '@/api/business/collectionMedia' |
| | | |
| | | export default { |
| | | name: 'CollectionMedia', |
| | | extends: BaseTable, |
| | | components: { TableLayout, Pagination }, |
| | | data () { |
| | | return { |
| | | stationOptions: [], |
| | | searchForm: { |
| | | stationId: null, |
| | | downloadStatus: null, |
| | | mediaType: null |
| | | }, |
| | | previewVisible: false, |
| | | previewLoading: false, |
| | | previewTitle: 'åªä½é¢è§', |
| | | previewMode: '', |
| | | previewSrc: '', |
| | | previewText: '', |
| | | previewRow: null, |
| | | previewUseDirectUrl: false, |
| | | previewBlobUrl: '', |
| | | downloadPollTimer: null |
| | | } |
| | | }, |
| | | created () { |
| | | if (this.$route.query.stationId) { |
| | | this.searchForm.stationId = parseInt(this.$route.query.stationId) |
| | | } |
| | | this.config({ |
| | | module: 'ééç«åªä½', |
| | | api: '/business/collectionMedia', |
| | | 'field.id': 'id', |
| | | 'field.main': 'id' |
| | | }) |
| | | this.loadStations() |
| | | this.search() |
| | | }, |
| | | beforeDestroy () { |
| | | this.revokePreviewUrl() |
| | | this.stopDownloadPoll() |
| | | }, |
| | | methods: { |
| | | loadStations () { |
| | | fetchStationList({ status: 1 }).then(res => { |
| | | this.stationOptions = res || [] |
| | | }) |
| | | }, |
| | | onStationChange () { |
| | | this.search() |
| | | }, |
| | | mediaTypeLabel (row) { |
| | | const mode = this.resolvePreviewMode(row) |
| | | if (mode === 'image') return 'å¾ç' |
| | | if (mode === 'audio') return 'é³é¢' |
| | | if (mode === 'text') return 'ææ¬' |
| | | if (row.mediaType === 1) return 'å¾ç' |
| | | if (row.mediaType === 2) return 'é³é¢' |
| | | return 'è§é¢' |
| | | }, |
| | | canPreview (row) { |
| | | return row.downloadStatus === 1 && (row.fileUrlFull || row.filePathLocal) |
| | | }, |
| | | canDownloadToStation (row) { |
| | | return row.downloadStatus !== 1 && row.downloadStatus !== 3 |
| | | }, |
| | | resolvePreviewMode (row) { |
| | | const name = (row.fileName || '').toLowerCase() |
| | | if (/\.(jpg|jpeg|png|gif|bmp|webp)$/.test(name) || row.mediaType === 1) { |
| | | return 'image' |
| | | } |
| | | if (/\.(mp3|wav|aac|m4a)$/.test(name) || row.mediaType === 2) { |
| | | return 'audio' |
| | | } |
| | | if (/\.(txt|log)$/.test(name)) { |
| | | return 'text' |
| | | } |
| | | if (/\.(mp4|mov|avi|mkv|webm|flv|m4v)$/.test(name) || row.mediaType === 0) { |
| | | return 'video' |
| | | } |
| | | return 'video' |
| | | }, |
| | | handleSyncMedia () { |
| | | syncMedia({ stationId: this.searchForm.stationId }).then(res => { |
| | | this.$message.success(res || '忥宿') |
| | | this.search() |
| | | }) |
| | | }, |
| | | handleBatchDownload () { |
| | | batchDownloadMedia({ stationId: this.searchForm.stationId, limit: 10 }).then(res => { |
| | | this.$message.success(res || 'å·²æäº¤ä¸è½½ä»»å¡') |
| | | this.search() |
| | | this.startDownloadPoll() |
| | | }) |
| | | }, |
| | | handleDownload (row) { |
| | | downloadMedia(row.id).then(res => { |
| | | this.$message.success(res || 'å·²æäº¤ä¸è½½ä»»å¡') |
| | | this.search() |
| | | this.startDownloadPoll() |
| | | }) |
| | | }, |
| | | handleSaveLocal (row) { |
| | | fetchMediaFile(row.id).then(response => { |
| | | this.download(response) |
| | | }).catch(err => { |
| | | this.$message.error(err.message || 'ä¸è½½å°æ¬å°å¤±è´¥') |
| | | }) |
| | | }, |
| | | startDownloadPoll () { |
| | | this.stopDownloadPoll() |
| | | let count = 0 |
| | | this.downloadPollTimer = setInterval(() => { |
| | | count++ |
| | | if (count > 40) { |
| | | this.stopDownloadPoll() |
| | | return |
| | | } |
| | | this.api.fetchList({ |
| | | page: this.tableData.pagination.pageIndex, |
| | | capacity: this.tableData.pagination.pageSize, |
| | | model: this.searchForm, |
| | | sorts: this.tableData.sorts |
| | | }).then(data => { |
| | | this.tableData.list = data.records |
| | | this.tableData.pagination.total = data.total |
| | | if (!(data.records || []).some(item => item.downloadStatus === 3)) { |
| | | this.stopDownloadPoll() |
| | | } |
| | | }).catch(() => {}) |
| | | }, 3000) |
| | | }, |
| | | stopDownloadPoll () { |
| | | if (this.downloadPollTimer) { |
| | | clearInterval(this.downloadPollTimer) |
| | | this.downloadPollTimer = null |
| | | } |
| | | }, |
| | | handlePreview (row) { |
| | | if (row.downloadStatus !== 1) { |
| | | this.$message.warning('请å
ä¸è½½æä»¶ååé¢è§') |
| | | return |
| | | } |
| | | this.previewTitle = row.fileName || 'åªä½é¢è§' |
| | | this.previewVisible = true |
| | | this.previewLoading = true |
| | | this.previewMode = this.resolvePreviewMode(row) |
| | | this.previewRow = row |
| | | this.previewSrc = '' |
| | | this.previewText = '' |
| | | this.previewUseDirectUrl = false |
| | | this.revokePreviewUrl() |
| | | |
| | | if (this.previewMode === 'text') { |
| | | fetchPreviewText(row.id).then(text => { |
| | | this.previewText = text || '' |
| | | this.previewLoading = false |
| | | }).catch(err => { |
| | | this.previewLoading = false |
| | | this.$message.error(err.message || 'ææ¬é¢è§å¤±è´¥') |
| | | }) |
| | | return |
| | | } |
| | | |
| | | if (this.previewMode === 'video') { |
| | | this.loadVideoPreview(row) |
| | | return |
| | | } |
| | | if (this.previewMode === 'audio') { |
| | | this.previewUseDirectUrl = !!row.fileUrlFull |
| | | this.previewSrc = row.fileUrlFull || buildPreviewUrl(row.id) |
| | | this.previewLoading = false |
| | | return |
| | | } |
| | | |
| | | if (row.fileUrlFull) { |
| | | this.previewSrc = row.fileUrlFull |
| | | this.previewLoading = false |
| | | return |
| | | } |
| | | |
| | | this.previewLoading = false |
| | | this.$message.warning('ææ å¯é¢è§å°å') |
| | | }, |
| | | loadVideoPreview (row) { |
| | | this.revokePreviewUrl() |
| | | fetchPreviewBlob(row.id) |
| | | .then(blob => ensureMp4Blob(blob)) |
| | | .then(blob => { |
| | | this.previewBlobUrl = URL.createObjectURL(blob) |
| | | this.previewSrc = this.previewBlobUrl |
| | | this.previewUseDirectUrl = false |
| | | this.previewLoading = false |
| | | }) |
| | | .catch(err => { |
| | | if (row.fileUrlFull) { |
| | | this.previewUseDirectUrl = true |
| | | this.previewSrc = row.fileUrlFull |
| | | this.previewLoading = false |
| | | return |
| | | } |
| | | this.previewLoading = false |
| | | this.$message.error(err.message || 'è§é¢é¢è§å¤±è´¥ï¼è¯·éæ°ä¸è½½è¯¥æä»¶') |
| | | }) |
| | | }, |
| | | onPreviewMediaError () { |
| | | if (!this.previewRow) { |
| | | this.$message.error('åªä½å 载失败') |
| | | return |
| | | } |
| | | if (this.previewMode === 'video') { |
| | | if (this.previewUseDirectUrl) { |
| | | this.$message.error('è§é¢æ æ³ææ¾ï¼è¯·éæ°ä¸è½½è¯¥æä»¶ï¼éééç«è½¬ MP4ï¼') |
| | | return |
| | | } |
| | | if (this.previewRow.fileUrlFull) { |
| | | this.previewUseDirectUrl = true |
| | | this.revokePreviewUrl() |
| | | this.previewSrc = this.previewRow.fileUrlFull |
| | | return |
| | | } |
| | | } |
| | | if (this.previewUseDirectUrl && this.previewMode === 'audio') { |
| | | this.previewUseDirectUrl = false |
| | | this.previewSrc = buildPreviewUrl(this.previewRow.id) |
| | | return |
| | | } |
| | | this.$message.error('è§é¢å 载失败ï¼è¯·éæ°ä¸è½½è¯¥æä»¶æè系管ç忣æ¥ééç«è½¬ç é
ç½®') |
| | | }, |
| | | closePreview () { |
| | | this.revokePreviewUrl() |
| | | this.previewSrc = '' |
| | | this.previewText = '' |
| | | this.previewMode = '' |
| | | this.previewRow = null |
| | | this.previewUseDirectUrl = false |
| | | }, |
| | | revokePreviewUrl () { |
| | | if (this.previewBlobUrl) { |
| | | URL.revokeObjectURL(this.previewBlobUrl) |
| | | this.previewBlobUrl = '' |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .preview-wrap { |
| | | min-height: 200px; |
| | | } |
| | | .preview-video { |
| | | width: 100%; |
| | | max-height: 520px; |
| | | background: #000; |
| | | } |
| | | .preview-audio { |
| | | width: 100%; |
| | | } |
| | | .preview-image { |
| | | width: 100%; |
| | | max-height: 520px; |
| | | } |
| | | .preview-text { |
| | | max-height: 520px; |
| | | overflow: auto; |
| | | white-space: pre-wrap; |
| | | word-break: break-all; |
| | | margin: 0; |
| | | padding: 12px; |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | .preview-empty { |
| | | text-align: center; |
| | | color: #909399; |
| | | padding: 40px 0; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <TableLayout :permissions="['business:collectionStation:query']"> |
| | | <div ref="QueryFormRef" slot="search-form"> |
| | | <el-form ref="searchForm" :model="searchForm" label-width="100px" inline> |
| | | <el-form-item label="åç§°" prop="name"> |
| | | <el-input v-model="searchForm.name" placeholder="ééç«åç§°" @keypress.enter.native="search" /> |
| | | </el-form-item> |
| | | <el-form-item label="IP" prop="ip"> |
| | | <el-input v-model="searchForm.ip" placeholder="设å¤IP" @keypress.enter.native="search" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¨çº¿ç¶æ" prop="online"> |
| | | <el-select v-model="searchForm.online" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="离线" :value="0" /> |
| | | <el-option label="å¨çº¿" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <section> |
| | | <el-button type="primary" @click="search">æç´¢</el-button> |
| | | <el-button @click="reset">éç½®</el-button> |
| | | </section> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <template v-slot:table-wrap> |
| | | <ul class="toolbar"> |
| | | <li> |
| | | <el-button type="primary" icon="el-icon-plus" v-permissions="['business:collectionStation:create']" |
| | | @click="$refs.operaWindow.open('æ°å»ºééç«')">æ°å»º</el-button> |
| | | </li> |
| | | <li> |
| | | <el-button type="primary" v-permissions="['business:collectionStation:sync']" @click="handleSyncAll">åæ¥ç¶æ</el-button> |
| | | </li> |
| | | <li> |
| | | <el-button type="primary" v-permissions="['business:collectionMedia:sync', 'business:collectionStation:sync']" @click="handleSyncMedia">忥åªä½ç´¢å¼</el-button> |
| | | </li> |
| | | <li> |
| | | <el-button type="primary" v-permissions="['business:collectionMedia:download', 'business:collectionStation:sync']" @click="handleBatchDownload">æ¹éä¸è½½</el-button> |
| | | </li> |
| | | </ul> |
| | | <el-table :height="tableHeightNew" v-loading="isWorking.search" :data="tableData.list" stripe> |
| | | <el-table-column label="åºå·" width="55"><template slot-scope="scope">{{ scope.$index + 1 }}</template></el-table-column> |
| | | <el-table-column prop="name" label="åç§°" min-width="120" /> |
| | | <el-table-column prop="ip" label="IP" min-width="120" /> |
| | | <el-table-column prop="port" label="端å£" width="80" /> |
| | | <el-table-column prop="model" label="åå·" min-width="100" /> |
| | | <el-table-column prop="serialNo" label="åºåå·" min-width="140" /> |
| | | <el-table-column prop="online" label="å¨çº¿" width="80"> |
| | | <template slot-scope="{row}"> |
| | | <el-link type="success" :underline="false" v-if="row.online === 1">å¨çº¿</el-link> |
| | | <el-link type="danger" :underline="false" v-else>离线</el-link> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åå¨(GB)" min-width="180"> |
| | | <template slot-scope="{row}"> |
| | | <span v-if="row.totalSpace != null">{{ formatStorage(row.freeSpace) }} / {{ formatStorage(row.totalSpace) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="softwareVersion" label="çæ¬" min-width="100" /> |
| | | <el-table-column prop="lastSyncTime" label="æè¿åæ¥" min-width="160" /> |
| | | <el-table-column label="æä½" min-width="220" fixed="right"> |
| | | <template slot-scope="{row}"> |
| | | <el-button type="text" v-permissions="['business:collectionStation:update']" |
| | | @click="$refs.operaWindow.open('ç¼è¾ééç«', row)">ç¼è¾</el-button> |
| | | <el-button type="text" v-permissions="['business:collectionStation:sync']" @click="handleSyncOne(row)">忥</el-button> |
| | | <el-button type="text" v-permissions="['business:collectionStation:sync']" @click="handleProbe(row)">ISAPIæ¢æµ</el-button> |
| | | <el-button type="text" v-permissions="['business:collectionStation:delete']" @click="deleteById(row)">å é¤</el-button> |
| | | <el-button type="text" @click="goMedia(row)">åªä½</el-button> |
| | | <el-button type="text" @click="goDockDevice(row)">æ§æ³è®°å½ä»ª</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination" /> |
| | | </template> |
| | | <OperaCollectionStationWindow ref="operaWindow" @success="handlePageChange" /> |
| | | </TableLayout> |
| | | </template> |
| | | |
| | | <script> |
| | | import BaseTable from '@/components/base/BaseTable' |
| | | import TableLayout from '@/layouts/TableLayout' |
| | | import Pagination from '@/components/common/Pagination' |
| | | import OperaCollectionStationWindow from '@/components/business/OperaCollectionStationWindow' |
| | | import { syncDevices, syncDevice, syncMedia, batchDownloadMedia, probeIsapi } from '@/api/business/collectionStation' |
| | | |
| | | export default { |
| | | name: 'CollectionStation', |
| | | extends: BaseTable, |
| | | components: { TableLayout, Pagination, OperaCollectionStationWindow }, |
| | | data () { |
| | | return { |
| | | searchForm: { |
| | | name: '', |
| | | ip: '', |
| | | online: null |
| | | } |
| | | } |
| | | }, |
| | | created () { |
| | | this.config({ |
| | | module: 'ééç«', |
| | | api: '/business/collectionStation', |
| | | 'field.id': 'id', |
| | | 'field.main': 'id' |
| | | }) |
| | | this.search() |
| | | }, |
| | | methods: { |
| | | formatStorage (val) { |
| | | if (val == null || val === '') { |
| | | return '0' |
| | | } |
| | | const num = Number(val) |
| | | if (Number.isNaN(num)) { |
| | | return val |
| | | } |
| | | return Number.isInteger(num) ? String(num) : num.toFixed(2) |
| | | }, |
| | | goMedia (row) { |
| | | this.$router.push({ path: '/business/collectionMedia', query: { stationId: row.id } }) |
| | | }, |
| | | goDockDevice (row) { |
| | | this.$router.push({ path: '/business/collectionDockDevice', query: { stationId: row.id } }) |
| | | }, |
| | | handleSyncAll () { |
| | | syncDevices().then(res => { |
| | | this.$message.success(res.data || '忥宿') |
| | | this.search() |
| | | }) |
| | | }, |
| | | handleSyncOne (row) { |
| | | syncDevice(row.id).then(res => { |
| | | this.$message.success(res.data || '忥æå') |
| | | this.search() |
| | | }) |
| | | }, |
| | | handleSyncMedia () { |
| | | syncMedia({}).then(res => { |
| | | this.$message.success(res.data || '忥宿') |
| | | }) |
| | | }, |
| | | handleBatchDownload () { |
| | | batchDownloadMedia({ limit: 10 }).then(res => { |
| | | this.$message.success(res.data || 'ä¸è½½å®æ') |
| | | }) |
| | | }, |
| | | handleProbe (row) { |
| | | probeIsapi(row.id).then(res => { |
| | | this.$alert('<pre style="max-height:400px;overflow:auto;text-align:left">' + (res.data || '') + '</pre>', 'ISAPIæ¢æµ', { |
| | | dangerouslyUseHTMLString: true, |
| | | customClass: 'isapi-probe-dialog' |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | |
| | | :tree-props="{children: 'children', hasChildren: 'hasChildren'}" |
| | | row-key="id" |
| | | stripe |
| | | default-expand-all |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" fixed="left"></el-table-column> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionDockDevice:query', 'æ¥è¯¢æ§æ³è®°å½ä»ª', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | CREATE TABLE IF NOT EXISTS `collection_dock_device` ( |
| | | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | `station_id` int(11) NOT NULL COMMENT 'ééç«ID', |
| | | `device_id` varchar(64) NOT NULL COMMENT 'ISAPI deviceId', |
| | | `device_name` varchar(128) DEFAULT NULL COMMENT '设å¤åç§°', |
| | | `short_serial_number` varchar(64) DEFAULT NULL COMMENT 'æ§æ³è®°å½ä»ªçåºåå·', |
| | | `access_device_id` varchar(64) DEFAULT NULL COMMENT '平尿³¨åID', |
| | | `networked_device` int(11) DEFAULT NULL COMMENT 'æ¯å¦èç½è®¾å¤ 0å¦1æ¯', |
| | | `last_sync_time` datetime DEFAULT NULL COMMENT 'æè¿åæ¥æ¶é´', |
| | | `create_date` datetime DEFAULT NULL COMMENT 'å建æ¶é´', |
| | | `isdeleted` int(11) DEFAULT '0' COMMENT 'æ¯å¦å é¤0å¦1æ¯', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_station_device` (`station_id`,`device_id`), |
| | | KEY `idx_station_id` (`station_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ééç«æ§æ³è®°å½ä»ªè®¾å¤'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | ALTER TABLE `collection_media` |
| | | ADD COLUMN `track_id` varchar(32) DEFAULT NULL COMMENT 'ISAPI trackID/mediaID' AFTER `file_index`; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä¸è½½ç¶æï¼0å¾
ä¸è½½ 1å·²ä¸è½½ 2失败 3ä¸è½½ä¸ |
| | | ALTER TABLE `collection_media` |
| | | MODIFY COLUMN `download_status` int(11) DEFAULT '0' COMMENT '0å¾
ä¸è½½ 1å·²ä¸è½½ 2失败 3ä¸è½½ä¸'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionMedia:query', 'æ¥è¯¢ééç«åªä½', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionMedia:sync', '忥ééç«åªä½', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionMedia:download', 'ä¸è½½ééç«åªä½', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | CREATE TABLE IF NOT EXISTS `collection_media` ( |
| | | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | `station_id` int(11) NOT NULL COMMENT 'ééç«ID', |
| | | `file_index` varchar(128) NOT NULL COMMENT '设å¤ä¾§æä»¶å¯ä¸æ è¯(mediaID)', |
| | | `track_id` varchar(32) DEFAULT NULL COMMENT 'ISAPI trackID', |
| | | `file_name` varchar(256) DEFAULT NULL COMMENT 'æä»¶å', |
| | | `playback_uri` varchar(1024) DEFAULT NULL COMMENT 'ISAPI searchè¿åçplaybackURI', |
| | | `media_type` int(11) DEFAULT '0' COMMENT '0è§é¢ 1å¾ç 2é³é¢', |
| | | `content_type` varchar(64) DEFAULT NULL COMMENT 'video/picture/audio', |
| | | `file_size` bigint(20) DEFAULT NULL COMMENT 'æä»¶å¤§å°(åè)', |
| | | `start_time` datetime DEFAULT NULL COMMENT 'å½å¶å¼å§æ¶é´', |
| | | `end_time` datetime DEFAULT NULL COMMENT 'å½å¶ç»ææ¶é´', |
| | | `recorder_sn` varchar(64) DEFAULT NULL COMMENT 'æ§æ³è®°å½ä»ªåºåå·', |
| | | `user_name` varchar(64) DEFAULT NULL COMMENT 'å
³èç¨æ·', |
| | | `file_path_local` varchar(512) DEFAULT NULL COMMENT 'FTPæ¬å°ç¸å¯¹è·¯å¾', |
| | | `download_status` int(11) DEFAULT '0' COMMENT '0å¾
ä¸è½½ 1å·²ä¸è½½ 2失败 3ä¸è½½ä¸', |
| | | `download_time` datetime DEFAULT NULL COMMENT 'ä¸è½½å®ææ¶é´', |
| | | `create_date` datetime DEFAULT NULL COMMENT 'å建æ¶é´', |
| | | `isdeleted` int(11) DEFAULT '0' COMMENT 'æ¯å¦å é¤0å¦ 1æ¯', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_station_file` (`station_id`,`file_index`), |
| | | KEY `idx_download_status` (`download_status`), |
| | | KEY `idx_station_id` (`station_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ééç«åªä½æä»¶'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- MySQL 5.xï¼è¥åå·²åå¨è¯·è·³è¿ |
| | | ALTER TABLE `collection_station` ADD COLUMN `use_https` int(11) DEFAULT '0' COMMENT 'æ¯å¦HTTPS 0å¦1æ¯' AFTER `port`; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- å·²æç¯å¢ï¼å° track æ£ç´¢æ¹ä¸ºèªå¨æ¨¡å¼ï¼æ£ç´¢è®¾å¤å
¨é¨ trackï¼ |
| | | UPDATE `SYSTEM_DICT_DATA` dd |
| | | INNER JOIN `SYSTEM_DICT` d ON d.`ID` = dd.`DICT_ID` AND d.`CODE` = 'CS_PARAM' AND d.`DELETED` = 0 |
| | | SET dd.`CODE` = 'auto', dd.`REMARK` = 'åªä½æ£ç´¢trackIDï¼auto/空=èªå¨ä»record/tracksè·åå
¨é¨' |
| | | WHERE dd.`LABEL` = 'CS_SEARCH_TRACK_ID' AND dd.`DELETED` = 0 AND dd.`CODE` = '101'; |
| | | |
| | | INSERT INTO `SYSTEM_DICT_DATA`(`DICT_ID`, `LABEL`, `CODE`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT d.`ID`, 'CS_FFMPEG_PATH', 'ffmpeg', 'FFmpeg坿§è¡æä»¶è·¯å¾ï¼ç¨äºééç«è§é¢è½¬ç 为æµè§å¨å¯æMP4', 3, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_DICT` d |
| | | WHERE d.`CODE` = 'CS_PARAM' AND d.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_DICT_DATA` dd |
| | | WHERE dd.`DICT_ID` = d.`ID` AND dd.`LABEL` = 'CS_FFMPEG_PATH' AND dd.`DELETED` = 0 |
| | | ); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ééç« ISAPI åæ°ï¼ç³»ç»åå
¸ CS_PARAMï¼ |
| | | -- è¡¨ç»æï¼SYSTEM_DICTï¼åå
¸ç±»åï¼+ SYSTEM_DICT_DATAï¼åå
¸é¡¹ï¼LABEL=é®åï¼CODE=é
ç½®å¼ï¼ |
| | | INSERT INTO `SYSTEM_DICT`(`CODE`, `NAME`, `REMARK`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT 'CS_PARAM', 'ééç«ISAPIåæ°', '海康ééç«ISAPI对æ¥é
ç½®', 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_DICT` WHERE `CODE` = 'CS_PARAM' AND `DELETED` = 0); |
| | | |
| | | INSERT INTO `SYSTEM_DICT_DATA`(`DICT_ID`, `LABEL`, `CODE`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT d.`ID`, 'CS_SEARCH_TRACK_ID', 'auto', 'åªä½æ£ç´¢trackIDï¼auto/空=èªå¨ä»record/tracksè·åå
¨é¨', 1, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_DICT` d |
| | | WHERE d.`CODE` = 'CS_PARAM' AND d.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_DICT_DATA` dd |
| | | WHERE dd.`DICT_ID` = d.`ID` AND dd.`LABEL` = 'CS_SEARCH_TRACK_ID' AND dd.`DELETED` = 0 |
| | | ); |
| | | |
| | | INSERT INTO `SYSTEM_DICT_DATA`(`DICT_ID`, `LABEL`, `CODE`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT d.`ID`, 'CS_DOWNLOAD_BATCH_SIZE', '10', 'æ¹éä¸è½½åªä½æä»¶æ°é', 2, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_DICT` d |
| | | WHERE d.`CODE` = 'CS_PARAM' AND d.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_DICT_DATA` dd |
| | | WHERE dd.`DICT_ID` = d.`ID` AND dd.`LABEL` = 'CS_DOWNLOAD_BATCH_SIZE' AND dd.`DELETED` = 0 |
| | | ); |
| | | |
| | | INSERT INTO `SYSTEM_DICT_DATA`(`DICT_ID`, `LABEL`, `CODE`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT d.`ID`, 'CS_FFMPEG_PATH', 'ffmpeg', 'FFmpeg坿§è¡æä»¶è·¯å¾ï¼ç¨äºééç«è§é¢è½¬ç 为æµè§å¨å¯æMP4', 3, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_DICT` d |
| | | WHERE d.`CODE` = 'CS_PARAM' AND d.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_DICT_DATA` dd |
| | | WHERE dd.`DICT_ID` = d.`ID` AND dd.`LABEL` = 'CS_FFMPEG_PATH' AND dd.`DELETED` = 0 |
| | | ); |
| | | |
| | | -- FTP åå
¸é常已åå¨ï¼ä»
追å ééç«åªä½åå¨ç®å½ |
| | | INSERT INTO `SYSTEM_DICT_DATA`(`DICT_ID`, `LABEL`, `CODE`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT d.`ID`, 'COLLECTION_MEDIA', '/collection_media/', 'ééç«åªä½FTPåå¨ç®å½', 99, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_DICT` d |
| | | WHERE d.`CODE` = 'FTP' AND d.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_DICT_DATA` dd |
| | | WHERE dd.`DICT_ID` = d.`ID` AND dd.`LABEL` = 'COLLECTION_MEDIA' AND dd.`DELETED` = 0 |
| | | ); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 交æ§ä¸å¿ > æ°æ®ééç« > ééç«/åªä½/æ§æ³è®°å½ä»ª èå |
| | | -- æ§è¡å请确认 SYSTEM_MENU ä¸å·²åå¨ NAME='交æ§ä¸å¿' ä¸ TYPE=1 ç顶级èå |
| | | -- æ§è¡å请å¨ãè§è²ç®¡ç â ææèåãä¸ä¸ºç¸å
³è§è²å¾éæ°èå |
| | | |
| | | -- 1. æä»¶å¤¹ï¼æ°æ®ééç« |
| | | INSERT INTO `SYSTEM_MENU`(`PARENT_ID`, `TYPE`, `LINK_TYPE`, `NAME`, `PATH`, `PARAMS`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT jk.`ID`, 0, 0, 'æ°æ®ééç«', NULL, NULL, 'el-icon-upload2', 0, |
| | | IFNULL((SELECT MAX(m.`SORT`) FROM `SYSTEM_MENU` m WHERE m.`PARENT_ID` = jk.`ID` AND m.`DELETED` = 0), -1) + 1, |
| | | 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_MENU` jk |
| | | WHERE jk.`NAME` = '交æ§ä¸å¿' AND jk.`TYPE` = 1 AND jk.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_MENU` m WHERE m.`PARENT_ID` = jk.`ID` AND m.`NAME` = 'æ°æ®ééç«' AND m.`DELETED` = 0 |
| | | ); |
| | | |
| | | -- 2. ééç«ç®¡ç |
| | | INSERT INTO `SYSTEM_MENU`(`PARENT_ID`, `TYPE`, `LINK_TYPE`, `NAME`, `PATH`, `PARAMS`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT folder.`ID`, 0, 0, 'ééç«ç®¡ç', '/business/collectionStation', NULL, 'el-icon-monitor', 0, 1, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_MENU` folder |
| | | INNER JOIN `SYSTEM_MENU` jk ON jk.`ID` = folder.`PARENT_ID` AND jk.`DELETED` = 0 |
| | | WHERE jk.`NAME` = '交æ§ä¸å¿' AND jk.`TYPE` = 1 |
| | | AND folder.`NAME` = 'æ°æ®ééç«' AND folder.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_MENU` m WHERE m.`PARENT_ID` = folder.`ID` AND m.`PATH` = '/business/collectionStation' AND m.`DELETED` = 0 |
| | | ); |
| | | |
| | | -- 3. åªä½æä»¶ |
| | | INSERT INTO `SYSTEM_MENU`(`PARENT_ID`, `TYPE`, `LINK_TYPE`, `NAME`, `PATH`, `PARAMS`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT folder.`ID`, 0, 0, 'åªä½æä»¶', '/business/collectionMedia', NULL, 'el-icon-video-camera', 0, 2, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_MENU` folder |
| | | INNER JOIN `SYSTEM_MENU` jk ON jk.`ID` = folder.`PARENT_ID` AND jk.`DELETED` = 0 |
| | | WHERE jk.`NAME` = '交æ§ä¸å¿' AND jk.`TYPE` = 1 |
| | | AND folder.`NAME` = 'æ°æ®ééç«' AND folder.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_MENU` m WHERE m.`PARENT_ID` = folder.`ID` AND m.`PATH` = '/business/collectionMedia' AND m.`DELETED` = 0 |
| | | ); |
| | | |
| | | -- 4. æ§æ³è®°å½ä»ª |
| | | INSERT INTO `SYSTEM_MENU`(`PARENT_ID`, `TYPE`, `LINK_TYPE`, `NAME`, `PATH`, `PARAMS`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT folder.`ID`, 0, 0, 'æ§æ³è®°å½ä»ª', '/business/collectionDockDevice', NULL, 'el-icon-mobile-phone', 0, 3, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_MENU` folder |
| | | INNER JOIN `SYSTEM_MENU` jk ON jk.`ID` = folder.`PARENT_ID` AND jk.`DELETED` = 0 |
| | | WHERE jk.`NAME` = '交æ§ä¸å¿' AND jk.`TYPE` = 1 |
| | | AND folder.`NAME` = 'æ°æ®ééç«' AND folder.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_MENU` m WHERE m.`PARENT_ID` = folder.`ID` AND m.`PATH` = '/business/collectionDockDevice' AND m.`DELETED` = 0 |
| | | ); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionStation:create', 'æ°å»ºééç«', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionStation:delete', 'å é¤ééç«', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionStation:update', 'ä¿®æ¹ééç«', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionStation:query', 'æ¥è¯¢ééç«', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| | | INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:collectionStation:sync', '忥ééç«æ°æ®', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä¸ºå·²æ¥æééç«æéçè§è²ï¼èªå¨è¡¥æåªä½/æ§æ³è®°å½ä»ªæé |
| | | -- æ§è¡åç¨æ·ééæ°ç»å½ä»¥å·æ°æéç¼å |
| | | |
| | | INSERT INTO `SYSTEM_ROLE_PERMISSION`(`ROLE_ID`, `PERMISSION_ID`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT rp.`ROLE_ID`, p_new.`ID`, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_ROLE_PERMISSION` rp |
| | | INNER JOIN `SYSTEM_PERMISSION` p_old ON p_old.`ID` = rp.`PERMISSION_ID` AND p_old.`CODE` = 'business:collectionStation:query' AND p_old.`DELETED` = 0 |
| | | INNER JOIN `SYSTEM_PERMISSION` p_new ON p_new.`CODE` = 'business:collectionMedia:query' AND p_new.`DELETED` = 0 |
| | | WHERE rp.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` x |
| | | WHERE x.`ROLE_ID` = rp.`ROLE_ID` AND x.`PERMISSION_ID` = p_new.`ID` AND x.`DELETED` = 0 |
| | | ); |
| | | |
| | | INSERT INTO `SYSTEM_ROLE_PERMISSION`(`ROLE_ID`, `PERMISSION_ID`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT rp.`ROLE_ID`, p_new.`ID`, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_ROLE_PERMISSION` rp |
| | | INNER JOIN `SYSTEM_PERMISSION` p_old ON p_old.`ID` = rp.`PERMISSION_ID` AND p_old.`CODE` = 'business:collectionStation:sync' AND p_old.`DELETED` = 0 |
| | | INNER JOIN `SYSTEM_PERMISSION` p_new ON p_new.`CODE` = 'business:collectionMedia:sync' AND p_new.`DELETED` = 0 |
| | | WHERE rp.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` x |
| | | WHERE x.`ROLE_ID` = rp.`ROLE_ID` AND x.`PERMISSION_ID` = p_new.`ID` AND x.`DELETED` = 0 |
| | | ); |
| | | |
| | | INSERT INTO `SYSTEM_ROLE_PERMISSION`(`ROLE_ID`, `PERMISSION_ID`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT rp.`ROLE_ID`, p_new.`ID`, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_ROLE_PERMISSION` rp |
| | | INNER JOIN `SYSTEM_PERMISSION` p_old ON p_old.`ID` = rp.`PERMISSION_ID` AND p_old.`CODE` = 'business:collectionStation:sync' AND p_old.`DELETED` = 0 |
| | | INNER JOIN `SYSTEM_PERMISSION` p_new ON p_new.`CODE` = 'business:collectionMedia:download' AND p_new.`DELETED` = 0 |
| | | WHERE rp.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` x |
| | | WHERE x.`ROLE_ID` = rp.`ROLE_ID` AND x.`PERMISSION_ID` = p_new.`ID` AND x.`DELETED` = 0 |
| | | ); |
| | | |
| | | INSERT INTO `SYSTEM_ROLE_PERMISSION`(`ROLE_ID`, `PERMISSION_ID`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) |
| | | SELECT rp.`ROLE_ID`, p_new.`ID`, 1, CURRENT_TIMESTAMP, NULL, NULL, 0 |
| | | FROM `SYSTEM_ROLE_PERMISSION` rp |
| | | INNER JOIN `SYSTEM_PERMISSION` p_old ON p_old.`ID` = rp.`PERMISSION_ID` AND p_old.`CODE` = 'business:collectionStation:query' AND p_old.`DELETED` = 0 |
| | | INNER JOIN `SYSTEM_PERMISSION` p_new ON p_new.`CODE` = 'business:collectionDockDevice:query' AND p_new.`DELETED` = 0 |
| | | WHERE rp.`DELETED` = 0 |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` x |
| | | WHERE x.`ROLE_ID` = rp.`ROLE_ID` AND x.`PERMISSION_ID` = p_new.`ID` AND x.`DELETED` = 0 |
| | | ); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | CREATE TABLE IF NOT EXISTS `collection_station` ( |
| | | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | `creator` varchar(64) DEFAULT NULL COMMENT 'å建人ç¼ç ', |
| | | `create_date` datetime DEFAULT NULL COMMENT 'å建æ¶é´', |
| | | `edirot` varchar(64) DEFAULT NULL COMMENT 'æ´æ°äººç¼ç ', |
| | | `edit_date` datetime DEFAULT NULL COMMENT 'æ´æ°æ¶é´', |
| | | `isdeleted` int(11) DEFAULT '0' COMMENT 'æ¯å¦å é¤0å¦ 1æ¯', |
| | | `remark` varchar(512) DEFAULT NULL COMMENT '夿³¨', |
| | | `name` varchar(128) DEFAULT NULL COMMENT 'ééç«åç§°', |
| | | `serial_no` varchar(64) DEFAULT NULL COMMENT '设å¤åºåå·', |
| | | `ip` varchar(64) DEFAULT NULL COMMENT '设å¤IP', |
| | | `port` int(11) DEFAULT '80' COMMENT '设å¤ç«¯å£(ISAPIé»è®¤80)', |
| | | `use_https` int(11) DEFAULT '0' COMMENT 'æ¯å¦HTTPS 0å¦1æ¯', |
| | | `username` varchar(64) DEFAULT NULL COMMENT 'ç»å½ç¨æ·å', |
| | | `password` varchar(128) DEFAULT NULL COMMENT 'ç»å½å¯ç ', |
| | | `model` varchar(64) DEFAULT 'UD39625B' COMMENT '设å¤åå·', |
| | | `online` int(11) DEFAULT '0' COMMENT 'å¨çº¿ç¶æ 0离线 1å¨çº¿', |
| | | `total_space` bigint(20) DEFAULT NULL COMMENT 'æ»åå¨ç©ºé´(GB)', |
| | | `free_space` bigint(20) DEFAULT NULL COMMENT 'å©ä½åå¨ç©ºé´(GB)', |
| | | `software_version` varchar(64) DEFAULT NULL COMMENT 'è½¯ä»¶çæ¬', |
| | | `last_sync_time` datetime DEFAULT NULL COMMENT 'æè¿åæ¥æ¶é´', |
| | | `status` int(11) DEFAULT '1' COMMENT 'ç¶æ 0ç¦ç¨ 1å¯ç¨', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_serial_no` (`serial_no`), |
| | | KEY `idx_ip` (`ip`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='海康æ¡é¢ééç«è®¾å¤'; |
| | |
| | | HandlerMethod handlerMethod = (HandlerMethod) handler; |
| | | Class<?> beanType = handlerMethod.getBeanType(); |
| | | if (!beanType.isAnnotationPresent(LoginNoRequired.class) && !handlerMethod.hasMethodAnnotation(LoginNoRequired.class)) { |
| | | //è·åtoken |
| | | Cookie[] cookies = request.getCookies(); |
| | | String token = request.getHeader(Constants.HEADER_USER_TOKEN); // ä» http 请æ±å¤´ä¸ååº token |
| | | if(StringUtils.isBlank(token)){ |
| | | for(Cookie c :cookies){ |
| | | if(StringUtils.equals(c.getName(),Constants.HEADER_USER_TOKEN)){ |
| | | token = c.getValue(); |
| | | token = request.getParameter(Constants.HEADER_USER_TOKEN); |
| | | } |
| | | if(StringUtils.isBlank(token)){ |
| | | Cookie[] cookies = request.getCookies(); |
| | | if (cookies != null) { |
| | | for(Cookie c :cookies){ |
| | | if(StringUtils.equals(c.getName(),Constants.HEADER_USER_TOKEN)){ |
| | | token = c.getValue(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | traceLog.setOperaSpendTime(Integer.valueOf("" + (System.currentTimeMillis() - Long.valueOf(traceTime.toString())))); |
| | | // è®°å½æä½æ¥å¿ç¶æ |
| | | String operaType = response.getHeader("eva-opera-type"); |
| | | // - ä¸è½½æ¥å£å¤çï¼æ éè®°å½ååºå
容 |
| | | if ("download".equals(operaType)) { |
| | | // - ä¸è½½/é¢è§æ¥å£å¤çï¼æ éè®°å½ååºå
容 |
| | | if ("download".equals(operaType) || "preview".equals(operaType)) { |
| | | handleDownloadResponse(traceLog, ex); |
| | | return; |
| | | } |
| | |
| | | public static boolean DEALING_HK_EMPOWER_DETAIL = false; |
| | | public static boolean DEALING_HK_EMPOWER_RESULT = false; |
| | | public static boolean DEALING_HK_PARKBOOK = false; |
| | | public static boolean DEALING_HK_COLLECTION_STATION = false; |
| | | public static boolean DEALING_HK_COLLECTION_MEDIA = false; |
| | | |
| | | public static final String CS_PARAM = "CS_PARAM"; |
| | | public static final String CS_SEARCH_TRACK_ID = "CS_SEARCH_TRACK_ID"; |
| | | public static final String CS_DOWNLOAD_BATCH_SIZE = "CS_DOWNLOAD_BATCH_SIZE"; |
| | | public static final String CS_FFMPEG_PATH = "CS_FFMPEG_PATH"; |
| | | public static final String COLLECTION_MEDIA_FOLDER = "COLLECTION_MEDIA"; |
| | | /** åªä½ä¸è½½ç¶æï¼ä¸è½½ä¸ */ |
| | | public static final int COLLECTION_MEDIA_DOWNLOADING = 3; |
| | | public static final String SMS ="SMS" ; |
| | | public static final String SMS_COMNAME = "SMS_COMNAME"; |
| | | public static final String SMS_IP ="SMS_IP" ; |
| | |
| | | return false; |
| | | } |
| | | |
| | | /** ä» FTP 读åè¿ç¨æä»¶å¹¶åå
¥è¾åºæµï¼ç¨äºå¨çº¿é¢è§/ä¸è½½ï¼ */ |
| | | public boolean streamRemoteFile(String remote, OutputStream outputStream) throws IOException { |
| | | if (streamRemoteFileInternal(remote, outputStream, 0, -1)) { |
| | | return true; |
| | | } |
| | | if (remote.startsWith("/")) { |
| | | return streamRemoteFileInternal(remote.substring(1), outputStream, 0, -1); |
| | | } |
| | | return streamRemoteFileByCwd(remote, outputStream, 0, -1); |
| | | } |
| | | |
| | | /** æåèèå´ä» FTP 读åè¿ç¨æä»¶ï¼æ¯æ MP4 åæ®µææ¾ï¼ */ |
| | | public boolean streamRemoteFileRange(String remote, OutputStream outputStream, long start, long length) throws IOException { |
| | | if (streamRemoteFileInternal(remote, outputStream, start, length)) { |
| | | return true; |
| | | } |
| | | if (remote.startsWith("/")) { |
| | | return streamRemoteFileInternal(remote.substring(1), outputStream, start, length); |
| | | } |
| | | return streamRemoteFileByCwd(remote, outputStream, start, length); |
| | | } |
| | | |
| | | public long getRemoteFileSize(String remote) throws IOException { |
| | | Long size = resolveRemoteFileSize(remote); |
| | | if (size != null) { |
| | | return size; |
| | | } |
| | | if (remote.startsWith("/")) { |
| | | size = resolveRemoteFileSize(remote.substring(1)); |
| | | } |
| | | return size != null ? size : -1L; |
| | | } |
| | | |
| | | private Long resolveRemoteFileSize(String remote) throws IOException { |
| | | String originalCwd = ftpClient.printWorkingDirectory(); |
| | | try { |
| | | ftpClient.enterLocalPassiveMode(); |
| | | FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes("GBK"), "iso-8859-1")); |
| | | if (files != null && files.length == 1 && files[0].isFile()) { |
| | | return files[0].getSize(); |
| | | } |
| | | if (remote.contains("/")) { |
| | | String directory = remote.substring(0, remote.lastIndexOf('/') + 1); |
| | | String fileName = remote.substring(remote.lastIndexOf('/') + 1); |
| | | if (ftpClient.changeWorkingDirectory(new String(directory.getBytes("GBK"), "iso-8859-1"))) { |
| | | files = ftpClient.listFiles(new String(fileName.getBytes("GBK"), "iso-8859-1")); |
| | | if (files != null && files.length == 1 && files[0].isFile()) { |
| | | return files[0].getSize(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } finally { |
| | | if (originalCwd != null) { |
| | | ftpClient.changeWorkingDirectory(originalCwd); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean streamRemoteFileInternal(String remote, OutputStream outputStream, long start, long length) throws IOException { |
| | | InputStream in = null; |
| | | try { |
| | | ftpClient.enterLocalPassiveMode(); |
| | | ftpClient.setFileType(FTP.BINARY_FILE_TYPE); |
| | | if (start > 0) { |
| | | ftpClient.setRestartOffset(start); |
| | | } |
| | | String encoded = new String(remote.getBytes("GBK"), "iso-8859-1"); |
| | | in = ftpClient.retrieveFileStream(encoded); |
| | | if (in == null) { |
| | | return false; |
| | | } |
| | | byte[] buffer = new byte[8192]; |
| | | long remaining = length; |
| | | int len; |
| | | while ((len = in.read(buffer)) != -1) { |
| | | if (length >= 0 && remaining <= 0) { |
| | | break; |
| | | } |
| | | int writeLen = len; |
| | | if (length >= 0 && writeLen > remaining) { |
| | | writeLen = (int) remaining; |
| | | } |
| | | outputStream.write(buffer, 0, writeLen); |
| | | if (length >= 0) { |
| | | remaining -= writeLen; |
| | | } |
| | | } |
| | | outputStream.flush(); |
| | | return ftpClient.completePendingCommand(); |
| | | } finally { |
| | | if (in != null) { |
| | | in.close(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean streamRemoteFileByCwd(String remote, OutputStream outputStream, long start, long length) throws IOException { |
| | | if (!remote.contains("/")) { |
| | | return false; |
| | | } |
| | | String directory = remote.substring(0, remote.lastIndexOf('/') + 1); |
| | | String fileName = remote.substring(remote.lastIndexOf('/') + 1); |
| | | ftpClient.enterLocalPassiveMode(); |
| | | ftpClient.setFileType(FTP.BINARY_FILE_TYPE); |
| | | if (!ftpClient.changeWorkingDirectory(new String(directory.getBytes("GBK"), "iso-8859-1"))) { |
| | | return false; |
| | | } |
| | | if (start > 0) { |
| | | ftpClient.setRestartOffset(start); |
| | | } |
| | | InputStream in = null; |
| | | try { |
| | | in = ftpClient.retrieveFileStream(new String(fileName.getBytes("GBK"), "iso-8859-1")); |
| | | if (in == null) { |
| | | return false; |
| | | } |
| | | byte[] buffer = new byte[8192]; |
| | | long remaining = length; |
| | | int len; |
| | | while ((len = in.read(buffer)) != -1) { |
| | | if (length >= 0 && remaining <= 0) { |
| | | break; |
| | | } |
| | | int writeLen = len; |
| | | if (length >= 0 && writeLen > remaining) { |
| | | writeLen = (int) remaining; |
| | | } |
| | | outputStream.write(buffer, 0, writeLen); |
| | | if (length >= 0) { |
| | | remaining -= writeLen; |
| | | } |
| | | } |
| | | outputStream.flush(); |
| | | return ftpClient.completePendingCommand(); |
| | | } finally { |
| | | if (in != null) { |
| | | in.close(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public boolean uploadInputstreamBatch(InputStream inputStream, String remote, Boolean close , Integer index ) { |
| | | // 设置PassiveModeä¼ è¾ |
| | | try { |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** ISAPI GETï¼Digest 认è¯ï¼ */ |
| | | public static String doIsapiGet(String host, int port, String username, String password, String uri) { |
| | | return IsapiHttpUtil.doGet(host, port, username, password, uri); |
| | | } |
| | | |
| | | /** ISAPI POSTï¼Digest 认è¯ï¼ */ |
| | | public static String doIsapiPost(String host, int port, String username, String password, String uri, String xmlBody) { |
| | | return IsapiHttpUtil.doPost(host, port, username, password, uri, xmlBody, "application/xml"); |
| | | } |
| | | |
| | | /** ISAPI åªä½ä¸è½½ */ |
| | | public static InputStream doIsapiDownload(String host, int port, String username, String password, |
| | | String uri, String downloadBody) { |
| | | return IsapiHttpUtil.doDownload(host, port, username, password, uri, downloadBody); |
| | | } |
| | | |
| | | public static String connection(String url,String method,String data,String contentType,boolean ignoreSSL){ |
| | | HttpsURLConnection connection = null; |
| | | try { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.utils; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.http.Header; |
| | | import org.apache.http.HttpEntity; |
| | | import org.apache.http.HttpHost; |
| | | import org.apache.http.auth.AuthScope; |
| | | import org.apache.http.auth.UsernamePasswordCredentials; |
| | | import org.apache.http.client.CredentialsProvider; |
| | | import org.apache.http.client.config.RequestConfig; |
| | | import org.apache.http.client.methods.CloseableHttpResponse; |
| | | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; |
| | | import org.apache.http.client.methods.HttpGet; |
| | | import org.apache.http.client.methods.HttpPost; |
| | | import org.apache.http.client.methods.HttpPut; |
| | | import org.apache.http.client.methods.HttpRequestBase; |
| | | import org.apache.http.client.protocol.HttpClientContext; |
| | | import org.apache.http.conn.ssl.NoopHostnameVerifier; |
| | | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |
| | | import org.apache.http.entity.StringEntity; |
| | | import org.apache.http.impl.auth.DigestScheme; |
| | | import org.apache.http.impl.client.BasicAuthCache; |
| | | import org.apache.http.impl.client.BasicCredentialsProvider; |
| | | import org.apache.http.impl.client.CloseableHttpClient; |
| | | import org.apache.http.impl.client.HttpClientBuilder; |
| | | import org.apache.http.impl.client.HttpClients; |
| | | import org.apache.http.util.EntityUtils; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.TrustManager; |
| | | import javax.net.ssl.X509TrustManager; |
| | | import java.io.InputStream; |
| | | import java.net.URI; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.SecureRandom; |
| | | import java.security.cert.X509Certificate; |
| | | |
| | | /** |
| | | * 海康 ISAPI HTTP 客æ·ç«¯ï¼Digest 认è¯ï¼æ¯æ HTTP/HTTPSï¼ |
| | | */ |
| | | @Slf4j |
| | | public class IsapiHttpUtil { |
| | | |
| | | private static final int CONNECT_TIMEOUT = 10000; |
| | | private static final int SOCKET_TIMEOUT = 30000; |
| | | private static final int DOWNLOAD_SOCKET_TIMEOUT = 300000; |
| | | |
| | | private IsapiHttpUtil() { |
| | | } |
| | | |
| | | public static String doGet(String host, int port, String username, String password, String uri) { |
| | | return doGet(host, port, false, username, password, uri); |
| | | } |
| | | |
| | | public static String doGet(String host, int port, boolean https, String username, String password, String uri) { |
| | | String url = buildUrl(host, port, https, uri); |
| | | HttpGet request = new HttpGet(url); |
| | | return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); |
| | | } |
| | | |
| | | public static String doPost(String host, int port, String username, String password, String uri, String body, String contentType) { |
| | | return doPost(host, port, false, username, password, uri, body, contentType); |
| | | } |
| | | |
| | | public static String doPost(String host, int port, boolean https, String username, String password, |
| | | String uri, String body, String contentType) { |
| | | String url = buildUrl(host, port, https, uri); |
| | | HttpPost request = new HttpPost(url); |
| | | if (body != null) { |
| | | request.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); |
| | | } |
| | | if (contentType != null) { |
| | | request.setHeader("Content-Type", contentType); |
| | | } |
| | | return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); |
| | | } |
| | | |
| | | public static String doPut(String host, int port, boolean https, String username, String password, |
| | | String uri, String body, String contentType) { |
| | | String url = buildUrl(host, port, https, uri); |
| | | HttpPut request = new HttpPut(url); |
| | | if (body != null) { |
| | | request.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); |
| | | } |
| | | if (contentType != null) { |
| | | request.setHeader("Content-Type", contentType); |
| | | } |
| | | return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); |
| | | } |
| | | |
| | | /** |
| | | * ISAPI æä»¶ä¸è½½ï¼GET /ISAPI/ContentMgmt/download?token=xxxï¼Body å« playbackURI |
| | | */ |
| | | public static InputStream doDownload(String host, int port, String username, String password, |
| | | String uri, String downloadBody) { |
| | | return doDownload(host, port, false, username, password, uri, downloadBody); |
| | | } |
| | | |
| | | public static InputStream doDownload(String host, int port, boolean https, String username, String password, |
| | | String uri, String downloadBody) { |
| | | String url = buildUrl(host, port, https, uri); |
| | | HttpGetWithEntity request = new HttpGetWithEntity(url); |
| | | if (downloadBody != null) { |
| | | request.setEntity(new StringEntity(downloadBody, StandardCharsets.UTF_8)); |
| | | request.setHeader("Content-Type", "application/xml"); |
| | | } |
| | | return executeStream(request, host, port, https, username, password, DOWNLOAD_SOCKET_TIMEOUT); |
| | | } |
| | | |
| | | /** 海康 ISAPI ä¸è½½æ¥å£ä½¿ç¨ GET + Body */ |
| | | static class HttpGetWithEntity extends HttpEntityEnclosingRequestBase { |
| | | HttpGetWithEntity(String uri) { |
| | | setURI(URI.create(uri)); |
| | | } |
| | | |
| | | @Override |
| | | public String getMethod() { |
| | | return "GET"; |
| | | } |
| | | } |
| | | |
| | | private static String buildUrl(String host, int port, boolean https, String uri) { |
| | | String scheme = https ? "https" : "http"; |
| | | if (!uri.startsWith("/")) { |
| | | uri = "/" + uri; |
| | | } |
| | | return scheme + "://" + host + ":" + port + uri; |
| | | } |
| | | |
| | | private static String executeString(HttpRequestBase request, String host, int port, boolean https, |
| | | String username, String password, int socketTimeout) { |
| | | try (CloseableHttpClient httpClient = buildClient(https, buildCredentials(host, port, https, username, password)); |
| | | CloseableHttpResponse response = execute(httpClient, request, host, port, https, username, password, socketTimeout)) { |
| | | HttpEntity entity = response.getEntity(); |
| | | if (entity == null) { |
| | | return null; |
| | | } |
| | | return EntityUtils.toString(entity, StandardCharsets.UTF_8); |
| | | } catch (Exception e) { |
| | | log.error("ISAPI请æ±å¤±è´¥ {}: {}", request.getURI(), e.getMessage(), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static InputStream executeStream(HttpRequestBase request, String host, int port, boolean https, |
| | | String username, String password, int socketTimeout) { |
| | | try { |
| | | CloseableHttpClient httpClient = buildClient(https, buildCredentials(host, port, https, username, password)); |
| | | CloseableHttpResponse response = execute(httpClient, request, host, port, https, username, password, socketTimeout); |
| | | int status = response.getStatusLine().getStatusCode(); |
| | | if (status < 200 || status >= 300) { |
| | | log.warn("ISAPIä¸è½½HTTP失败 {} status={}", request.getURI(), status); |
| | | EntityUtils.consumeQuietly(response.getEntity()); |
| | | response.close(); |
| | | httpClient.close(); |
| | | return null; |
| | | } |
| | | HttpEntity entity = response.getEntity(); |
| | | if (entity == null) { |
| | | response.close(); |
| | | httpClient.close(); |
| | | return null; |
| | | } |
| | | return entity.getContent(); |
| | | } catch (Exception e) { |
| | | log.error("ISAPIä¸è½½å¤±è´¥ {}: {}", request.getURI(), e.getMessage(), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static CredentialsProvider buildCredentials(String host, int port, boolean https, |
| | | String username, String password) { |
| | | CredentialsProvider credsProvider = new BasicCredentialsProvider(); |
| | | String scheme = https ? "https" : "http"; |
| | | credsProvider.setCredentials(new AuthScope(host, port, AuthScope.ANY_REALM, scheme), |
| | | new UsernamePasswordCredentials(username, password)); |
| | | credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); |
| | | return credsProvider; |
| | | } |
| | | |
| | | private static CloseableHttpResponse execute(CloseableHttpClient httpClient, HttpRequestBase request, |
| | | String host, int port, boolean https, |
| | | String username, String password, int socketTimeout) throws Exception { |
| | | RequestConfig requestConfig = RequestConfig.custom() |
| | | .setConnectTimeout(CONNECT_TIMEOUT) |
| | | .setSocketTimeout(socketTimeout) |
| | | .setConnectionRequestTimeout(CONNECT_TIMEOUT) |
| | | .build(); |
| | | request.setConfig(requestConfig); |
| | | |
| | | CredentialsProvider credsProvider = buildCredentials(host, port, https, username, password); |
| | | HttpHost httpHost = new HttpHost(host, port, https ? "https" : "http"); |
| | | |
| | | HttpClientContext context = HttpClientContext.create(); |
| | | context.setCredentialsProvider(credsProvider); |
| | | |
| | | log.info("ISAPI请æ±: {}://{}:{}{}", https ? "https" : "http", host, port, request.getURI().getRawPath()); |
| | | CloseableHttpResponse response = httpClient.execute(httpHost, request, context); |
| | | int status = response.getStatusLine().getStatusCode(); |
| | | if (status != 401) { |
| | | return response; |
| | | } |
| | | |
| | | Header authHeader = findDigestAuthHeader(response); |
| | | if (authHeader == null) { |
| | | log.warn("ISAPI 401 使ªè¿å Digest ææå¤´: {} status={}", request.getURI(), status); |
| | | return response; |
| | | } |
| | | |
| | | EntityUtils.consumeQuietly(response.getEntity()); |
| | | response.close(); |
| | | |
| | | DigestScheme digestScheme = new DigestScheme(); |
| | | digestScheme.processChallenge(authHeader); |
| | | |
| | | BasicAuthCache authCache = new BasicAuthCache(); |
| | | authCache.put(httpHost, digestScheme); |
| | | |
| | | HttpClientContext retryContext = HttpClientContext.create(); |
| | | retryContext.setCredentialsProvider(credsProvider); |
| | | retryContext.setAuthCache(authCache); |
| | | |
| | | CloseableHttpResponse retryResponse = httpClient.execute(httpHost, request, retryContext); |
| | | log.info("ISAPI Digest éè¯: {} status={}", request.getURI(), retryResponse.getStatusLine().getStatusCode()); |
| | | return retryResponse; |
| | | } |
| | | |
| | | private static Header findDigestAuthHeader(CloseableHttpResponse response) { |
| | | Header[] headers = response.getHeaders("WWW-Authenticate"); |
| | | if (headers == null || headers.length == 0) { |
| | | return null; |
| | | } |
| | | for (Header header : headers) { |
| | | if (header.getValue() != null && header.getValue().toLowerCase().contains("digest")) { |
| | | return header; |
| | | } |
| | | } |
| | | return headers[0]; |
| | | } |
| | | |
| | | private static CloseableHttpClient buildClient(boolean https, CredentialsProvider credsProvider) throws Exception { |
| | | HttpClientBuilder builder = HttpClients.custom().setDefaultCredentialsProvider(credsProvider); |
| | | if (!https) { |
| | | return builder.build(); |
| | | } |
| | | TrustManager[] trustManagers = new TrustManager[]{ |
| | | new X509TrustManager() { |
| | | @Override |
| | | public void checkClientTrusted(X509Certificate[] chain, String authType) { |
| | | } |
| | | |
| | | @Override |
| | | public void checkServerTrusted(X509Certificate[] chain, String authType) { |
| | | } |
| | | |
| | | @Override |
| | | public X509Certificate[] getAcceptedIssuers() { |
| | | return new X509Certificate[0]; |
| | | } |
| | | } |
| | | }; |
| | | SSLContext sslContext = SSLContext.getInstance("TLS"); |
| | | sslContext.init(null, trustManagers, new SecureRandom()); |
| | | SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); |
| | | return builder.setSSLSocketFactory(sslFactory).build(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.utils; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStreamReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * å°æµ·åº·ç设å¤ä¸è½½çåªä½æµè½¬ä¸ºæµè§å¨ video æ ç¾å¯æç MP4ï¼H.264 + AACï¼ã |
| | | * 海康 MP4 å¸¸è§æåç MP2 é³é¢è½¨ï¼é»è®¤ä¸¢å¼å¼å¸¸é³é¢ä»
ä¿çè§é¢ã |
| | | */ |
| | | @Slf4j |
| | | public final class VideoTranscodeUtil { |
| | | |
| | | private static final long TRANSCODE_TIMEOUT_MINUTES = 30; |
| | | |
| | | private VideoTranscodeUtil() { |
| | | } |
| | | |
| | | /** |
| | | * å° MP4 è§é¢ï¼å«æµ·åº· .mp4 æ©å±åä½å
容为 MPEG-PS çæ
åµï¼è½¬ä¸ºæµè§å¨å¯æ MP4ã |
| | | * è°ç¨æ¹éå·²éè¿æä»¶åçæ¡ä»¶ç¡®è®¤æ¯ MP4 è§é¢ï¼æ¬æ¹æ³ä¸åå ç¼ºå° ftyp 头èè·³è¿ã |
| | | */ |
| | | public static boolean transcodeToBrowserMp4(String ffmpegPath, File source, File target) { |
| | | if (source == null || !source.exists() || source.length() <= 0) { |
| | | return false; |
| | | } |
| | | String ffmpeg = resolveExecutable(ffmpegPath, "ffmpeg"); |
| | | String ffprobe = resolveFfprobe(ffmpegPath); |
| | | boolean mpegPs = false; |
| | | boolean mp4Container = false; |
| | | try { |
| | | mpegPs = isMpegPs(source); |
| | | mp4Container = hasMp4Header(source); |
| | | } catch (IOException e) { |
| | | log.warn("è§é¢æä»¶å¤´æ£æµå¤±è´¥ï¼ç»§ç»å°è¯ FFmpeg 转ç : {}", e.getMessage()); |
| | | } |
| | | if (mpegPs) { |
| | | log.info("æ£æµå° MPEG-PS æµï¼æ©å±åå¯è½ä¸º .mp4ï¼ï¼å°è½¬ç 为æ å MP4: size={}", source.length()); |
| | | } else if (!mp4Container) { |
| | | log.info("æä»¶æ MP4 容å¨å¤´(æ ftyp)ï¼å°è¯ FFmpeg æè§é¢æµè½¬ç : size={}", source.length()); |
| | | } |
| | | |
| | | try { |
| | | if (mp4Container && isBrowserPlayableMp4(ffmpeg, source)) { |
| | | Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| | | log.info("è§é¢å·²æ¯æµè§å¨å¯æ MP4ï¼è·³è¿è½¬ç size={}", source.length()); |
| | | return target.exists() && target.length() > 0; |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("æ£æµè§é¢ç¼ç 失败ï¼å°å°è¯è½¬ç : {}", e.getMessage()); |
| | | } |
| | | |
| | | String inputPath = source.getAbsolutePath(); |
| | | String outputPath = target.getAbsolutePath(); |
| | | String videoCodec = probeStreamCodec(ffprobe, source, "v:0"); |
| | | |
| | | // 1. MP4 å®¹å¨ + H.264ï¼éå°è£
并䏢弿åé³é¢ |
| | | if (mp4Container && "h264".equalsIgnoreCase(videoCodec)) { |
| | | if (runFfmpeg(buildRemuxCommand(ffmpeg, source, inputPath, outputPath), "éå°è£
(H.264 copy, æ é³é¢)")) { |
| | | return isValidOutput(target); |
| | | } |
| | | } |
| | | |
| | | // 2. 转ç è§é¢è½¨ï¼éç¨äº MPEG-PSãæå MP4 çï¼ |
| | | if (runFfmpeg(buildEncodeVideoNoAudioCommand(ffmpeg, source, inputPath, outputPath), "转ç (ä»
è§é¢)")) { |
| | | return isValidOutput(target); |
| | | } |
| | | |
| | | // 3. é³é¢è½¨æ£å¸¸æ¶æå°è¯å¸¦ä¸é³é¢ |
| | | String audioCodec = probeStreamCodec(ffprobe, source, "a:0"); |
| | | if (isDecodableAudio(audioCodec)) { |
| | | if (runFfmpeg(buildEncodeVideoWithAudioCommand(ffmpeg, source, inputPath, outputPath), "转ç (è§é¢+é³é¢)")) { |
| | | return isValidOutput(target); |
| | | } |
| | | } |
| | | |
| | | log.error("è§é¢è½¬ç å
¨é¨çç¥å¤±è´¥ source={}", source.getAbsolutePath()); |
| | | return false; |
| | | } |
| | | |
| | | public static boolean isBrowserPlayableMp4(String ffmpegPath, File file) throws IOException { |
| | | if (!hasMp4Header(file)) { |
| | | return false; |
| | | } |
| | | if (isMpegPs(file)) { |
| | | return false; |
| | | } |
| | | String ffprobe = resolveFfprobe(ffmpegPath); |
| | | String videoCodec = probeStreamCodec(ffprobe, file, "v:0"); |
| | | if (!"h264".equalsIgnoreCase(videoCodec)) { |
| | | return false; |
| | | } |
| | | String audioCodec = probeStreamCodec(ffprobe, file, "a:0"); |
| | | if (StringUtils.isBlank(audioCodec)) { |
| | | return true; |
| | | } |
| | | return "aac".equalsIgnoreCase(audioCodec); |
| | | } |
| | | |
| | | public static boolean hasMp4Header(File file) throws IOException { |
| | | byte[] head = readHead(file, 12); |
| | | return head.length >= 8 && head[4] == 'f' && head[5] == 't' && head[6] == 'y' && head[7] == 'p'; |
| | | } |
| | | |
| | | public static boolean isMpegPs(File file) throws IOException { |
| | | byte[] head = readHead(file, 4); |
| | | return head.length >= 4 && head[0] == 0 && head[1] == 0 && head[2] == 1 && (head[3] & 0xFF) == 0xBA; |
| | | } |
| | | |
| | | private static boolean isDecodableAudio(String codec) { |
| | | if (StringUtils.isBlank(codec)) { |
| | | return false; |
| | | } |
| | | return "aac".equalsIgnoreCase(codec) |
| | | || "mp3".equalsIgnoreCase(codec) |
| | | || codec.toLowerCase().startsWith("pcm"); |
| | | } |
| | | |
| | | private static boolean isValidOutput(File target) { |
| | | return target != null && target.exists() && target.length() > 0; |
| | | } |
| | | |
| | | private static List<String> buildInputArgs(String ffmpeg, File source, String inputPath) { |
| | | List<String> cmd = new ArrayList<>(); |
| | | cmd.add(ffmpeg); |
| | | cmd.add("-y"); |
| | | cmd.add("-fflags"); |
| | | cmd.add("+discardcorrupt"); |
| | | cmd.add("-err_detect"); |
| | | cmd.add("ignore_err"); |
| | | try { |
| | | if (isMpegPs(source)) { |
| | | cmd.add("-f"); |
| | | cmd.add("mpeg"); |
| | | } |
| | | } catch (IOException ignored) { |
| | | } |
| | | cmd.add("-i"); |
| | | cmd.add(inputPath); |
| | | return cmd; |
| | | } |
| | | |
| | | /** éå°è£
ï¼å¤å¶ H.264 è§é¢ï¼ä¸¢å¼æåé³é¢ */ |
| | | private static List<String> buildRemuxCommand(String ffmpeg, File source, String inputPath, String outputPath) { |
| | | List<String> cmd = buildInputArgs(ffmpeg, source, inputPath); |
| | | cmd.add("-map"); |
| | | cmd.add("0:v:0"); |
| | | cmd.add("-an"); |
| | | cmd.add("-c:v"); |
| | | cmd.add("copy"); |
| | | cmd.add("-movflags"); |
| | | cmd.add("+faststart"); |
| | | cmd.add(outputPath); |
| | | return cmd; |
| | | } |
| | | |
| | | /** 转ç ï¼ä»
è§é¢è½¨ï¼æ é³é¢ */ |
| | | private static List<String> buildEncodeVideoNoAudioCommand(String ffmpeg, File source, String inputPath, String outputPath) { |
| | | List<String> cmd = buildInputArgs(ffmpeg, source, inputPath); |
| | | cmd.add("-map"); |
| | | cmd.add("0:v:0"); |
| | | cmd.add("-an"); |
| | | cmd.add("-c:v"); |
| | | cmd.add("libx264"); |
| | | cmd.add("-preset"); |
| | | cmd.add("fast"); |
| | | cmd.add("-crf"); |
| | | cmd.add("23"); |
| | | cmd.add("-pix_fmt"); |
| | | cmd.add("yuv420p"); |
| | | cmd.add("-movflags"); |
| | | cmd.add("+faststart"); |
| | | cmd.add(outputPath); |
| | | return cmd; |
| | | } |
| | | |
| | | /** 转ç ï¼è§é¢ + å¯è§£ç é³é¢ */ |
| | | private static List<String> buildEncodeVideoWithAudioCommand(String ffmpeg, File source, String inputPath, String outputPath) { |
| | | List<String> cmd = buildInputArgs(ffmpeg, source, inputPath); |
| | | cmd.add("-map"); |
| | | cmd.add("0:v:0"); |
| | | cmd.add("-map"); |
| | | cmd.add("0:a:0?"); |
| | | cmd.add("-c:v"); |
| | | cmd.add("libx264"); |
| | | cmd.add("-preset"); |
| | | cmd.add("fast"); |
| | | cmd.add("-crf"); |
| | | cmd.add("23"); |
| | | cmd.add("-pix_fmt"); |
| | | cmd.add("yuv420p"); |
| | | cmd.add("-c:a"); |
| | | cmd.add("aac"); |
| | | cmd.add("-b:a"); |
| | | cmd.add("128k"); |
| | | cmd.add("-ac"); |
| | | cmd.add("2"); |
| | | cmd.add("-movflags"); |
| | | cmd.add("+faststart"); |
| | | cmd.add(outputPath); |
| | | return cmd; |
| | | } |
| | | |
| | | private static boolean runFfmpeg(List<String> command, String label) { |
| | | try { |
| | | int exitCode = runCommand(command, TRANSCODE_TIMEOUT_MINUTES); |
| | | if (exitCode == 0) { |
| | | log.info("FFmpeg {} æå", label); |
| | | return true; |
| | | } |
| | | log.warn("FFmpeg {} 失败 exitCode={}", label, exitCode); |
| | | } catch (Exception e) { |
| | | log.warn("FFmpeg {} å¼å¸¸: {}", label, e.getMessage()); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private static byte[] readHead(File file, int len) throws IOException { |
| | | byte[] head = new byte[len]; |
| | | try (FileInputStream in = new FileInputStream(file)) { |
| | | int n = in.read(head); |
| | | if (n <= 0) { |
| | | return new byte[0]; |
| | | } |
| | | if (n < len) { |
| | | byte[] actual = new byte[n]; |
| | | System.arraycopy(head, 0, actual, 0, n); |
| | | return actual; |
| | | } |
| | | } |
| | | return head; |
| | | } |
| | | |
| | | private static String probeStreamCodec(String ffprobe, File file, String stream) { |
| | | List<String> command = Arrays.asList( |
| | | ffprobe, |
| | | "-v", "error", |
| | | "-select_streams", stream, |
| | | "-show_entries", "stream=codec_name", |
| | | "-of", "default=noprint_wrappers=1:nokey=1", |
| | | file.getAbsolutePath() |
| | | ); |
| | | try { |
| | | ProcessBuilder builder = new ProcessBuilder(command); |
| | | builder.redirectErrorStream(true); |
| | | Process process = builder.start(); |
| | | String output = readStream(process.getInputStream()); |
| | | boolean finished = process.waitFor(2, TimeUnit.MINUTES); |
| | | if (!finished) { |
| | | process.destroyForcibly(); |
| | | return null; |
| | | } |
| | | if (process.exitValue() != 0) { |
| | | return null; |
| | | } |
| | | return output.trim(); |
| | | } catch (Exception e) { |
| | | log.warn("ffprobe æ£æµå¤±è´¥ stream={}: {}", stream, e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static String resolveExecutable(String configuredPath, String defaultName) { |
| | | if (StringUtils.isNotBlank(configuredPath)) { |
| | | return configuredPath.trim(); |
| | | } |
| | | return defaultName; |
| | | } |
| | | |
| | | private static String resolveFfprobe(String ffmpegPath) { |
| | | if (StringUtils.isNotBlank(ffmpegPath)) { |
| | | String path = ffmpegPath.trim(); |
| | | if (path.toLowerCase().endsWith("ffmpeg.exe") || path.toLowerCase().endsWith("ffmpeg")) { |
| | | int idx = path.lastIndexOf('/'); |
| | | if (idx < 0) { |
| | | idx = path.lastIndexOf('\\'); |
| | | } |
| | | String dir = idx >= 0 ? path.substring(0, idx + 1) : ""; |
| | | String name = path.substring(idx + 1); |
| | | if (name.toLowerCase().endsWith(".exe")) { |
| | | return dir + name.replaceAll("(?i)ffmpeg\\.exe$", "ffprobe.exe"); |
| | | } |
| | | return dir + name.replaceAll("(?i)ffmpeg$", "ffprobe"); |
| | | } |
| | | return path.replaceAll("(?i)ffmpeg", "ffprobe"); |
| | | } |
| | | return "ffprobe"; |
| | | } |
| | | |
| | | private static int runCommand(List<String> command, long timeoutMinutes) throws IOException, InterruptedException { |
| | | ProcessBuilder builder = new ProcessBuilder(command); |
| | | builder.redirectErrorStream(true); |
| | | Process process = builder.start(); |
| | | String output = readStream(process.getInputStream()); |
| | | boolean finished = process.waitFor(timeoutMinutes, TimeUnit.MINUTES); |
| | | if (!finished) { |
| | | process.destroyForcibly(); |
| | | log.error("FFmpeg æ§è¡è¶
æ¶: {}", String.join(" ", command)); |
| | | return -1; |
| | | } |
| | | if (StringUtils.isNotBlank(output)) { |
| | | if (process.exitValue() != 0) { |
| | | log.warn("FFmpeg è¾åº: {}", output.length() > 2000 ? output.substring(0, 2000) + "..." : output); |
| | | } else { |
| | | log.debug("FFmpeg è¾åº: {}", output.length() > 2000 ? output.substring(0, 2000) + "..." : output); |
| | | } |
| | | } |
| | | return process.exitValue(); |
| | | } |
| | | |
| | | private static String readStream(java.io.InputStream inputStream) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | sb.append(line).append('\n'); |
| | | } |
| | | } |
| | | return sb.toString().trim(); |
| | | } |
| | | } |
| | |
| | | @ApiOperation("ãæè·¯å¨ãå¼å¯å®æ¶è¿ç¨æ§å¶æè·¯å¨åé¸") |
| | | @PostMapping("/timer/duanluqi/autoCloseCmd") |
| | | ApiResponse autoCloseCmd(); |
| | | |
| | | @ApiOperation("ãééç«ã宿¶åæ¥ééç«å¨çº¿ç¶æ") |
| | | @PostMapping("/timer/collectionStation/syncStations") |
| | | ApiResponse syncCollectionStations(); |
| | | |
| | | @ApiOperation("ãééç«ã宿¶åæ¥åªä½ç´¢å¼å¹¶ä¸è½½") |
| | | @PostMapping("/timer/collectionStation/syncMediaAndDownload") |
| | | ApiResponse syncCollectionMediaAndDownload(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.api; |
| | | |
| | | import com.doumee.dao.admin.request.CollectionMediaSyncRequest; |
| | | import com.doumee.service.business.CollectionMediaSyncService; |
| | | import com.doumee.service.business.CollectionStationService; |
| | | import com.doumee.service.business.third.model.ApiResponse; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | 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.RestController; |
| | | |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | |
| | | @Api(tags = "海康ééç«å®æ¶å¨æ¥å£") |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/timer/collectionStation") |
| | | public class HkCollectionStationTimerController extends BaseController { |
| | | |
| | | @Autowired |
| | | private CollectionStationService collectionStationService; |
| | | @Autowired |
| | | private CollectionMediaSyncService collectionMediaSyncService; |
| | | |
| | | @ApiOperation("宿¶åæ¥ééç«å¨çº¿ç¶æ") |
| | | @PostMapping("/syncStations") |
| | | public ApiResponse<String> syncStations() { |
| | | log.info("宿¶ä»»å¡ï¼åæ¥ééç«ç¶æ"); |
| | | return ApiResponse.success(collectionStationService.syncAllStations()); |
| | | } |
| | | |
| | | @ApiOperation("宿¶åæ¥ééç«åªä½ç´¢å¼å¹¶æ¹éä¸è½½") |
| | | @PostMapping("/syncMediaAndDownload") |
| | | public ApiResponse<String> syncMediaAndDownload() { |
| | | log.info("宿¶ä»»å¡ï¼åæ¥ééç«åªä½ç´¢å¼"); |
| | | CollectionMediaSyncRequest request = new CollectionMediaSyncRequest(); |
| | | Calendar cal = Calendar.getInstance(); |
| | | request.setEndTime(cal.getTime()); |
| | | cal.add(Calendar.HOUR_OF_DAY, -24); |
| | | request.setStartTime(cal.getTime()); |
| | | String syncResult = collectionMediaSyncService.syncMediaList(request); |
| | | String downloadResult = collectionMediaSyncService.batchDownload(request); |
| | | return ApiResponse.success(syncResult + "ï¼" + downloadResult); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.cloud.admin; |
| | | |
| | | import com.doumee.api.BaseController; |
| | | import com.doumee.config.annotation.CloudRequiredPermission; |
| | | import com.doumee.core.annotation.pr.PreventRepeat; |
| | | import com.doumee.core.utils.Constants; |
| | | import com.doumee.dao.admin.request.CollectionMediaSyncRequest; |
| | | import com.doumee.dao.business.model.CollectionMedia; |
| | | import com.doumee.dao.business.model.CollectionDockDevice; |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import com.doumee.service.business.CollectionMediaSyncService; |
| | | import com.doumee.service.business.CollectionStationService; |
| | | import com.doumee.service.business.third.model.ApiResponse; |
| | | import com.doumee.service.business.third.model.PageData; |
| | | import com.doumee.service.business.third.model.PageWrap; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Api(tags = "海康ééç«") |
| | | @RestController |
| | | @RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/collectionStation") |
| | | public class CollectionStationCloudController extends BaseController { |
| | | |
| | | @Autowired |
| | | private CollectionStationService collectionStationService; |
| | | @Autowired |
| | | private CollectionMediaSyncService collectionMediaSyncService; |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("æ°å»ºééç«") |
| | | @PostMapping("/create") |
| | | @CloudRequiredPermission("business:collectionStation:create") |
| | | public ApiResponse<Integer> create(@RequestBody CollectionStation station, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | station.setLoginUserInfo(getLoginUser(token)); |
| | | return ApiResponse.success(collectionStationService.create(station)); |
| | | } |
| | | |
| | | @ApiOperation("æ ¹æ®IDå é¤ééç«") |
| | | @GetMapping("/delete/{id}") |
| | | @CloudRequiredPermission("business:collectionStation:delete") |
| | | public ApiResponse<Void> deleteById(@PathVariable Integer id, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | collectionStationService.deleteById(id, getLoginUser(token)); |
| | | return ApiResponse.success(null); |
| | | } |
| | | |
| | | @ApiOperation("æ¹éå é¤ééç«") |
| | | @GetMapping("/delete/batch") |
| | | @CloudRequiredPermission("business:collectionStation:delete") |
| | | public ApiResponse<Void> deleteByIdInBatch(@RequestParam String ids, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | String[] idArray = ids.split(","); |
| | | List<Integer> idList = new ArrayList<>(); |
| | | for (String id : idArray) { |
| | | idList.add(Integer.valueOf(id)); |
| | | } |
| | | collectionStationService.deleteByIdInBatch(idList, getLoginUser(token)); |
| | | return ApiResponse.success(null); |
| | | } |
| | | |
| | | @ApiOperation("ä¿®æ¹ééç«") |
| | | @PostMapping("/updateById") |
| | | @CloudRequiredPermission("business:collectionStation:update") |
| | | public ApiResponse<Void> updateById(@RequestBody CollectionStation station, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | station.setLoginUserInfo(getLoginUser(token)); |
| | | collectionStationService.updateById(station); |
| | | return ApiResponse.success(null); |
| | | } |
| | | |
| | | @ApiOperation("å页æ¥è¯¢ééç«") |
| | | @PostMapping("/page") |
| | | @CloudRequiredPermission("business:collectionStation:query") |
| | | public ApiResponse<PageData<CollectionStation>> findPage(@RequestBody PageWrap<CollectionStation> pageWrap, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | return ApiResponse.success(collectionStationService.findPage(pageWrap)); |
| | | } |
| | | |
| | | @ApiOperation("æ¥è¯¢å
¨é¨ééç«") |
| | | @PostMapping("/list") |
| | | @CloudRequiredPermission("business:collectionStation:query") |
| | | public ApiResponse<List<CollectionStation>> findList(@RequestBody CollectionStation model, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | return ApiResponse.success(collectionStationService.findList(model)); |
| | | } |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("忥ææééç«ç¶æ") |
| | | @PostMapping("/syncDevices") |
| | | @CloudRequiredPermission("business:collectionStation:sync") |
| | | public ApiResponse<String> syncDevices() { |
| | | return ApiResponse.success(collectionStationService.syncAllStations()); |
| | | } |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("忥å个ééç«ç¶æ") |
| | | @PostMapping("/syncDevice/{id}") |
| | | @CloudRequiredPermission("business:collectionStation:sync") |
| | | public ApiResponse<String> syncDevice(@PathVariable Integer id) { |
| | | return ApiResponse.success(collectionStationService.syncStationStatus(id)); |
| | | } |
| | | |
| | | @ApiOperation("ISAPIèè°æ¢æµ(è¿ååå§XML/JSON)") |
| | | @GetMapping("/probe/{id}") |
| | | @CloudRequiredPermission("business:collectionStation:sync") |
| | | public ApiResponse<String> probe(@PathVariable Integer id) { |
| | | return ApiResponse.success(collectionStationService.probeIsapi(id)); |
| | | } |
| | | |
| | | @ApiOperation("æ¥è¯¢ééç«æ§æ³è®°å½ä»ªå表") |
| | | @GetMapping("/dockDevices/{stationId}") |
| | | @CloudRequiredPermission({"business:collectionDockDevice:query", "business:collectionStation:query"}) |
| | | public ApiResponse<List<CollectionDockDevice>> dockDevices(@PathVariable Integer stationId) { |
| | | return ApiResponse.success(collectionStationService.findDockDevices(stationId)); |
| | | } |
| | | |
| | | @ApiOperation("å页æ¥è¯¢æ§æ³è®°å½ä»ª") |
| | | @PostMapping("/dockDevices/page") |
| | | @CloudRequiredPermission({"business:collectionDockDevice:query", "business:collectionStation:query"}) |
| | | public ApiResponse<PageData<CollectionDockDevice>> findDockDevicePage(@RequestBody PageWrap<CollectionDockDevice> pageWrap, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | return ApiResponse.success(collectionStationService.findDockDevicePage(pageWrap)); |
| | | } |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("忥åªä½æä»¶ç´¢å¼") |
| | | @PostMapping("/syncMedia") |
| | | @CloudRequiredPermission({"business:collectionMedia:sync", "business:collectionStation:sync"}) |
| | | public ApiResponse<String> syncMedia(@RequestBody CollectionMediaSyncRequest request) { |
| | | return ApiResponse.success(collectionMediaSyncService.syncMediaList(request)); |
| | | } |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("ä¸è½½å个åªä½æä»¶") |
| | | @PostMapping("/downloadMedia/{id}") |
| | | @CloudRequiredPermission({"business:collectionMedia:download", "business:collectionStation:sync"}) |
| | | public ApiResponse<String> downloadMedia(@PathVariable Integer id) { |
| | | return ApiResponse.success(collectionMediaSyncService.downloadMedia(id)); |
| | | } |
| | | |
| | | @PreventRepeat |
| | | @ApiOperation("æ¹éä¸è½½åªä½æä»¶") |
| | | @PostMapping("/batchDownloadMedia") |
| | | @CloudRequiredPermission({"business:collectionMedia:download", "business:collectionStation:sync"}) |
| | | public ApiResponse<String> batchDownloadMedia(@RequestBody CollectionMediaSyncRequest request) { |
| | | return ApiResponse.success(collectionMediaSyncService.batchDownload(request)); |
| | | } |
| | | |
| | | @ApiOperation("å页æ¥è¯¢åªä½æä»¶") |
| | | @PostMapping("/media/page") |
| | | @CloudRequiredPermission({"business:collectionMedia:query", "business:collectionStation:query"}) |
| | | public ApiResponse<PageData<CollectionMedia>> findMediaPage(@RequestBody PageWrap<CollectionMedia> pageWrap, |
| | | @RequestHeader(Constants.HEADER_USER_TOKEN) String token) { |
| | | return ApiResponse.success(collectionMediaSyncService.findPage(pageWrap)); |
| | | } |
| | | |
| | | @ApiOperation("é¢è§å·²ä¸è½½åªä½æä»¶") |
| | | @GetMapping("/media/preview/{id}") |
| | | @CloudRequiredPermission({"business:collectionMedia:query", "business:collectionStation:query"}) |
| | | public void previewMedia(@PathVariable Integer id, javax.servlet.http.HttpServletRequest request, |
| | | javax.servlet.http.HttpServletResponse response) { |
| | | collectionMediaSyncService.previewMedia(id, request, response); |
| | | } |
| | | |
| | | @ApiOperation("ä¸è½½å·²ä¸è½½åªä½æä»¶å°æ¬å°") |
| | | @GetMapping("/media/download/{id}") |
| | | @CloudRequiredPermission({"business:collectionMedia:query", "business:collectionStation:query"}) |
| | | public void downloadMediaFile(@PathVariable Integer id, javax.servlet.http.HttpServletRequest request, |
| | | javax.servlet.http.HttpServletResponse response) { |
| | | collectionMediaSyncService.downloadMediaFile(id, request, response); |
| | | } |
| | | } |
| | |
| | | spring: |
| | | profiles: |
| | | active: pro |
| | | active: dev |
| | | application: |
| | | name: visitsAdmin |
| | | # å®å
¨é
ç½® |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.doumee.core.haikang.isapi.model.DeviceInfoDTO; |
| | | import com.doumee.core.haikang.isapi.model.DockDeviceDTO; |
| | | import com.doumee.core.haikang.isapi.model.DockStationBasicInfoDTO; |
| | | import com.doumee.core.haikang.isapi.model.MediaItemDTO; |
| | | import com.doumee.core.haikang.isapi.model.RecordTrackDTO; |
| | | import com.doumee.core.haikang.isapi.model.SearchPageResult; |
| | | import com.doumee.core.haikang.isapi.model.StorageInfoDTO; |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.io.InputStream; |
| | | import java.io.PushbackInputStream; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.TimeZone; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * 海康ééç« ISAPI 客æ·ç«¯ |
| | | * åèï¼ISAPIå¼åæå_ææç©¿æ´äº§å_è¡ä¸ç©¿æ´äº§å |
| | | */ |
| | | @Slf4j |
| | | public class IsapiClient { |
| | | |
| | | public DeviceInfoDTO getDeviceInfo(CollectionStation station) { |
| | | String xml = IsapiRequestHelper.doGet(station, IsapiConstants.DEVICE_INFO); |
| | | return IsapiXmlParser.parseDeviceInfo(xml); |
| | | } |
| | | |
| | | public StorageInfoDTO getStorageInfo(CollectionStation station) { |
| | | String xml = IsapiRequestHelper.doGet(station, IsapiConstants.STORAGE); |
| | | return IsapiXmlParser.parseStorage(xml); |
| | | } |
| | | |
| | | public DockStationBasicInfoDTO getDockBasicInfo(CollectionStation station) { |
| | | String json = IsapiRequestHelper.doGet(station, IsapiConstants.DOCK_BASIC_INFO); |
| | | return IsapiJsonParser.parseDockBasicInfo(json); |
| | | } |
| | | |
| | | public List<DockDeviceDTO> getDockDevices(CollectionStation station) { |
| | | String json = IsapiRequestHelper.doGet(station, IsapiConstants.DOCK_DEVICE_MANAGEMENT); |
| | | return IsapiJsonParser.parseDockDeviceList(json); |
| | | } |
| | | |
| | | public List<RecordTrackDTO> getRecordTracks(CollectionStation station) { |
| | | String xml = IsapiRequestHelper.doGet(station, IsapiConstants.RECORD_TRACKS); |
| | | List<RecordTrackDTO> tracks = IsapiXmlParser.parseRecordTracks(xml); |
| | | if (tracks.isEmpty()) { |
| | | RecordTrackDTO video = new RecordTrackDTO(); |
| | | video.setId(IsapiConstants.DEFAULT_TRACK_ID); |
| | | video.setStreamType(0); |
| | | RecordTrackDTO picture = new RecordTrackDTO(); |
| | | picture.setId(IsapiConstants.DEFAULT_PICTURE_TRACK_ID); |
| | | picture.setStreamType(2); |
| | | tracks.add(video); |
| | | tracks.add(picture); |
| | | } |
| | | return tracks; |
| | | } |
| | | |
| | | public boolean isOnline(CollectionStation station) { |
| | | String xml = IsapiRequestHelper.doGet(station, IsapiConstants.DEVICE_INFO); |
| | | return StringUtils.isNotBlank(xml) |
| | | && !xml.contains("Unauthorized") |
| | | && !xml.contains("Not Found") |
| | | && xml.contains("deviceName"); |
| | | } |
| | | |
| | | /** |
| | | * æ£ç´¢åªä½ï¼å trackãåé¡µï¼ |
| | | */ |
| | | public List<MediaItemDTO> searchMedia(CollectionStation station, Date startTime, Date endTime, |
| | | String trackId, int maxResults) { |
| | | return searchMediaPage(station, startTime, endTime, trackId, 0, maxResults).getItems(); |
| | | } |
| | | |
| | | /** |
| | | * æ£ç´¢åªä½ï¼å¤ track + å页ï¼ç¬¦å ISAPI æåéææµç¨ï¼ |
| | | */ |
| | | public List<MediaItemDTO> searchMediaAll(CollectionStation station, Date startTime, Date endTime, |
| | | String trackId, int maxResults) { |
| | | List<String> trackIds = resolveTrackIds(station, trackId); |
| | | List<MediaItemDTO> all = new ArrayList<>(); |
| | | Set<String> seen = new LinkedHashSet<>(); |
| | | for (String tid : trackIds) { |
| | | int position = 0; |
| | | int pageSize = Math.min(maxResults > 0 ? maxResults : IsapiConstants.DEFAULT_MAX_RESULTS, |
| | | IsapiConstants.MAX_PAGE_RESULTS); |
| | | while (true) { |
| | | SearchPageResult page = searchMediaPage(station, startTime, endTime, tid, position, pageSize); |
| | | for (MediaItemDTO item : page.getItems()) { |
| | | String key = tid + ":" + item.getFileIndex(); |
| | | if (seen.add(key)) { |
| | | all.add(item); |
| | | } |
| | | } |
| | | if (!page.isMore() || page.getItems().isEmpty()) { |
| | | break; |
| | | } |
| | | position += page.getItems().size(); |
| | | } |
| | | } |
| | | return all; |
| | | } |
| | | |
| | | public SearchPageResult searchMediaPage(CollectionStation station, Date startTime, Date endTime, |
| | | String trackId, int searchResultPosition, int maxResults) { |
| | | String body = buildSearchXml(startTime, endTime, trackId, searchResultPosition, maxResults); |
| | | String xml = IsapiRequestHelper.doPost(station, IsapiConstants.SEARCH_STD, body, IsapiConstants.CONTENT_TYPE_XML); |
| | | SearchPageResult result = IsapiXmlParser.parseSearchPage(xml); |
| | | if (result.getItems().isEmpty()) { |
| | | log.warn("ISAPI search æ åªä½ trackId={} pos={} status={} body={} response={}", |
| | | trackId, searchResultPosition, result.getResponseStatusStrg(), body, |
| | | StringUtils.abbreviate(StringUtils.defaultString(xml), 800)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | public String getDownloadToken(CollectionStation station) { |
| | | String json = IsapiRequestHelper.doGet(station, IsapiConstants.SECURITY_TOKEN_JSON); |
| | | if (StringUtils.isBlank(json)) { |
| | | return null; |
| | | } |
| | | try { |
| | | JSONObject obj = JSONObject.parseObject(json); |
| | | if (obj.containsKey("Token")) { |
| | | return obj.getJSONObject("Token").getString("value"); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return IsapiXmlParser.parseSecurityToken(json); |
| | | } |
| | | |
| | | public InputStream downloadMedia(CollectionStation station, String playbackUri) { |
| | | return downloadMedia(station, playbackUri, null, null, null, null, null, null, null); |
| | | } |
| | | |
| | | /** |
| | | * ä¸è½½åªä½ï¼ä¼å
playbackURIï¼ééç«æ URI æ¶ä½¿ç¨ mediaID / ææä»¶åæé URI |
| | | */ |
| | | public InputStream downloadMedia(CollectionStation station, String playbackUri, String mediaId, |
| | | String fileName, String trackId, Date startTime, Date endTime, |
| | | Long fileSize, Integer mediaType) { |
| | | if (StringUtils.isNotBlank(playbackUri)) { |
| | | InputStream stream = downloadByPlaybackUri(station, playbackUri); |
| | | if (stream != null) { |
| | | return stream; |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(mediaId)) { |
| | | InputStream stream = downloadByMediaId(station, mediaId, fileName, trackId, startTime, endTime, fileSize, mediaType); |
| | | if (stream != null) { |
| | | log.info("ISAPI æ mediaID ä¸è½½æå mediaID={} fileName={}", mediaId, fileName); |
| | | return stream; |
| | | } |
| | | } |
| | | String builtUri = buildPlaybackUri(station, fileName, trackId, startTime, endTime, fileSize); |
| | | if (StringUtils.isNotBlank(builtUri)) { |
| | | InputStream stream = downloadByPlaybackUri(station, builtUri); |
| | | if (stream != null) { |
| | | log.info("ISAPI ææé playbackURI ä¸è½½æå fileName={}", fileName); |
| | | return stream; |
| | | } |
| | | } |
| | | log.warn("ISAPI ä¸è½½å¤±è´¥ playbackUri={} mediaID={} fileName={}", playbackUri, mediaId, fileName); |
| | | return null; |
| | | } |
| | | |
| | | private InputStream downloadByMediaId(CollectionStation station, String mediaId, String fileName, |
| | | String trackId, Date startTime, Date endTime, Long fileSize, |
| | | Integer mediaType) { |
| | | boolean video = isVideoFile(fileName) || isVideoMediaType(mediaType); |
| | | boolean[][] strategies = video |
| | | ? new boolean[][]{{true, true}, {true, false}, {false, true}, {false, false}} |
| | | : new boolean[][]{{false, true}, {false, false}}; |
| | | for (boolean[] strategy : strategies) { |
| | | boolean useMp4Encode = strategy[0]; |
| | | boolean useTime = strategy[1]; |
| | | InputStream stream = executeMediaIdDownload(station, mediaId, fileName, startTime, endTime, useMp4Encode, useTime); |
| | | if (stream != null) { |
| | | log.info("ISAPI mediaIDä¸è½½æå mediaID={} encodeMp4={} withTime={}", mediaId, useMp4Encode, useTime); |
| | | return stream; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private InputStream executeMediaIdDownload(CollectionStation station, String mediaId, String fileName, |
| | | Date startTime, Date endTime, boolean useMp4Encode, boolean useTime) { |
| | | StringBuilder query = new StringBuilder(IsapiConstants.DOWNLOAD) |
| | | .append("?mediaID=").append(urlEncode(mediaId.trim())) |
| | | .append("&downType=").append(IsapiConstants.DOWNLOAD_DOWN_TYPE_FILE); |
| | | if (useMp4Encode) { |
| | | query.append("&encodeType=").append(IsapiConstants.DOWNLOAD_ENCODE_MP4); |
| | | } |
| | | if (useTime) { |
| | | appendDownloadTimeParams(query, startTime, endTime); |
| | | } |
| | | String queryString = query.toString(); |
| | | |
| | | String token = getDownloadToken(station); |
| | | if (StringUtils.isNotBlank(token)) { |
| | | String uri = queryString + "&token=" + urlEncode(token); |
| | | String body = buildDownloadRequestXml(null, mediaId, fileName); |
| | | InputStream stream = validateDownloadStream(IsapiRequestHelper.doDownload(station, uri, body)); |
| | | if (stream != null) { |
| | | return stream; |
| | | } |
| | | } |
| | | InputStream stream = validateDownloadStream(IsapiRequestHelper.doDownload(station, queryString, null)); |
| | | if (stream != null) { |
| | | return stream; |
| | | } |
| | | String body = buildDownloadRequestXml(null, mediaId, fileName); |
| | | return validateDownloadStream(IsapiRequestHelper.doDownload(station, queryString, body)); |
| | | } |
| | | |
| | | private static InputStream validateDownloadStream(InputStream raw) { |
| | | if (raw == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | PushbackInputStream pb = new PushbackInputStream(raw, 512); |
| | | byte[] head = new byte[512]; |
| | | int n = 0; |
| | | int read; |
| | | while (n < head.length && (read = pb.read(head, n, head.length - n)) != -1) { |
| | | n += read; |
| | | } |
| | | if (n <= 0) { |
| | | pb.close(); |
| | | return null; |
| | | } |
| | | if (isErrorPayload(head, n)) { |
| | | log.warn("ISAPIä¸è½½è¿åé误å
容: {}", new String(head, 0, Math.min(n, 200), StandardCharsets.UTF_8)); |
| | | pb.close(); |
| | | return null; |
| | | } |
| | | pb.unread(head, 0, n); |
| | | return pb; |
| | | } catch (Exception e) { |
| | | log.warn("ISAPIä¸è½½æµæ ¡éªå¤±è´¥: {}", e.getMessage()); |
| | | try { |
| | | raw.close(); |
| | | } catch (Exception ignored) { |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static boolean isErrorPayload(byte[] head, int n) { |
| | | String snippet = new String(head, 0, Math.min(n, 64), StandardCharsets.UTF_8).trim(); |
| | | return snippet.startsWith("<?xml") || snippet.startsWith("{") |
| | | || (snippet.startsWith("<") && snippet.contains("ResponseStatus")); |
| | | } |
| | | |
| | | private InputStream downloadByPlaybackUri(CollectionStation station, String playbackUri) { |
| | | String token = getDownloadToken(station); |
| | | if (StringUtils.isNotBlank(token)) { |
| | | String uri = IsapiConstants.DOWNLOAD + "?token=" + urlEncode(token); |
| | | String body = buildDownloadRequestXml(playbackUri, null, null); |
| | | InputStream is = validateDownloadStream(IsapiRequestHelper.doDownload(station, uri, body)); |
| | | if (is != null) { |
| | | return is; |
| | | } |
| | | } |
| | | try { |
| | | String uri = IsapiConstants.DOWNLOAD + "?playbackURI=" + urlEncode(playbackUri); |
| | | return validateDownloadStream(IsapiRequestHelper.doDownload(station, uri, null)); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static String buildDownloadRequestXml(String playbackUri, String mediaId, String fileName) { |
| | | StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?><downloadRequest>"); |
| | | if (StringUtils.isNotBlank(playbackUri)) { |
| | | xml.append("<playbackURI>").append(escapeXml(playbackUri)).append("</playbackURI>"); |
| | | } |
| | | if (StringUtils.isNotBlank(mediaId)) { |
| | | xml.append("<mediaID>").append(escapeXml(mediaId.trim())).append("</mediaID>"); |
| | | } |
| | | if (StringUtils.isNotBlank(fileName)) { |
| | | xml.append("<name>").append(escapeXml(fileName)).append("</name>"); |
| | | } |
| | | xml.append("</downloadRequest>"); |
| | | return xml.toString(); |
| | | } |
| | | |
| | | /** ééç« search æ playbackURI æ¶ï¼ææä»¶å+æ¶é´æ®µæé ä¸è½½ URI */ |
| | | private static String buildPlaybackUri(CollectionStation station, String fileName, String trackId, |
| | | Date startTime, Date endTime, Long fileSize) { |
| | | if (StringUtils.isBlank(fileName) || startTime == null || endTime == null) { |
| | | return null; |
| | | } |
| | | String host = station.getIp(); |
| | | if (StringUtils.isBlank(host)) { |
| | | return null; |
| | | } |
| | | String track = StringUtils.isNotBlank(trackId) ? trackId.trim() : IsapiConstants.DEFAULT_TRACK_ID; |
| | | StringBuilder uri = new StringBuilder("rtsp://").append(host) |
| | | .append("/Streaming/tracks/").append(track) |
| | | .append("?starttime=").append(formatPlaybackUriTime(startTime)) |
| | | .append("&endtime=").append(formatPlaybackUriTime(endTime)) |
| | | .append("&name=").append(fileName); |
| | | if (fileSize != null && fileSize > 0) { |
| | | uri.append("&size=").append(fileSize); |
| | | } |
| | | uri.append("&timeType=STD"); |
| | | return uri.toString(); |
| | | } |
| | | |
| | | private static void appendDownloadTimeParams(StringBuilder query, Date startTime, Date endTime) { |
| | | if (startTime != null) { |
| | | query.append("&startTime=").append(urlEncode(formatDownloadTime(startTime))); |
| | | } |
| | | if (endTime != null) { |
| | | query.append("&endTime=").append(urlEncode(formatDownloadTime(endTime))); |
| | | } |
| | | } |
| | | |
| | | /** download æ¥å£æ¶é´ï¼ISO8601 带æ¶åº */ |
| | | private static String formatDownloadTime(Date time) { |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); |
| | | sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); |
| | | return sdf.format(time); |
| | | } |
| | | |
| | | /** playbackURI å
æ¶é´ï¼UTC ç´§åæ ¼å¼ */ |
| | | private static String formatPlaybackUriTime(Date time) { |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); |
| | | sdf.setTimeZone(TimeZone.getTimeZone("UTC")); |
| | | return sdf.format(time); |
| | | } |
| | | |
| | | private static boolean isVideoFile(String fileName) { |
| | | if (StringUtils.isBlank(fileName)) { |
| | | return true; |
| | | } |
| | | String lower = fileName.toLowerCase(); |
| | | return lower.endsWith(".mp4") || lower.endsWith(".avi") || lower.endsWith(".mov") |
| | | || lower.endsWith(".mkv") || lower.endsWith(".264") || lower.endsWith(".h264") |
| | | || lower.endsWith(".m4v"); |
| | | } |
| | | |
| | | private static boolean isVideoMediaType(Integer mediaType) { |
| | | return mediaType == null || mediaType == 0; |
| | | } |
| | | |
| | | private List<String> resolveTrackIds(CollectionStation station, String trackId) { |
| | | if (StringUtils.isNotBlank(trackId) && !isAutoTrack(trackId)) { |
| | | return Collections.singletonList(trackId.trim()); |
| | | } |
| | | List<String> ids = new ArrayList<>(); |
| | | for (RecordTrackDTO track : getRecordTracks(station)) { |
| | | if (track.getStreamType() == 0 || track.getStreamType() == 1 || track.getStreamType() == 2) { |
| | | ids.add(track.getId()); |
| | | } |
| | | } |
| | | if (ids.isEmpty()) { |
| | | ids.add(IsapiConstants.DEFAULT_TRACK_ID); |
| | | ids.add(IsapiConstants.DEFAULT_PICTURE_TRACK_ID); |
| | | } |
| | | return ids; |
| | | } |
| | | |
| | | private static boolean isAutoTrack(String trackId) { |
| | | String val = trackId.trim(); |
| | | return "auto".equalsIgnoreCase(val) || "*".equals(val) || "0".equals(val); |
| | | } |
| | | |
| | | private static String buildSearchXml(Date startTime, Date endTime, String trackId, |
| | | int searchResultPosition, int maxResults) { |
| | | String start = formatSearchTime(startTime, true); |
| | | String end = formatSearchTime(endTime, true); |
| | | StringBuilder trackXml = new StringBuilder(); |
| | | if (StringUtils.isNotBlank(trackId) && !isAutoTrack(trackId)) { |
| | | trackXml.append("<trackID>").append(trackId.trim()).append("</trackID>"); |
| | | } else { |
| | | trackXml.append("<trackID>").append(IsapiConstants.DEFAULT_TRACK_ID).append("</trackID>"); |
| | | trackXml.append("<trackID>").append(IsapiConstants.DEFAULT_PICTURE_TRACK_ID).append("</trackID>"); |
| | | } |
| | | int pageSize = maxResults > 0 ? maxResults : IsapiConstants.DEFAULT_MAX_RESULTS; |
| | | return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
| | | + "<CMSearchDescription>" |
| | | + "<searchID>" + UUID.randomUUID() + "</searchID>" |
| | | + "<trackList>" + trackXml + "</trackList>" |
| | | + "<timeSpanList><timeSpan>" |
| | | + "<startTime>" + start + "</startTime>" |
| | | + "<endTime>" + end + "</endTime>" |
| | | + "</timeSpan></timeSpanList>" |
| | | + "<maxResults>" + pageSize + "</maxResults>" |
| | | + "<searchResultPostion>" + searchResultPosition + "</searchResultPostion>" |
| | | + "</CMSearchDescription>"; |
| | | } |
| | | |
| | | /** timeType=STD æ¶ä½¿ç¨è®¾å¤æ¬å°æ åæ¶é´ï¼ä¸å¸¦ Z åç¼ */ |
| | | private static String formatSearchTime(Date time, boolean stdTime) { |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); |
| | | if (!stdTime) { |
| | | sdf.setTimeZone(TimeZone.getTimeZone("UTC")); |
| | | return sdf.format(time) + "Z"; |
| | | } |
| | | sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); |
| | | return sdf.format(time); |
| | | } |
| | | |
| | | private static String escapeXml(String val) { |
| | | if (val == null) { |
| | | return ""; |
| | | } |
| | | return val.replace("&", "&").replace("<", "<").replace(">", ">"); |
| | | } |
| | | |
| | | private static String urlEncode(String val) { |
| | | try { |
| | | return URLEncoder.encode(val, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | return val; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | /** |
| | | * ISAPI URI 常éï¼ææç©¿æ´/è¡ä¸ç©¿æ´/ééç«ï¼ |
| | | * åèï¼ISAPIå¼åæå_ææç©¿æ´äº§å_è¡ä¸ç©¿æ´äº§å |
| | | */ |
| | | public final class IsapiConstants { |
| | | |
| | | private IsapiConstants() { |
| | | } |
| | | |
| | | // --- ç³»ç» --- |
| | | public static final String DEVICE_INFO = "/ISAPI/System/deviceInfo"; |
| | | public static final String DEVICE_CAPABILITIES = "/ISAPI/System/capabilities"; |
| | | public static final String SYSTEM_TIME_TYPE = "/ISAPI/System/time/timeType?format=json"; |
| | | |
| | | // --- å
容管çï¼å½å/å¾çæ£ç´¢ä¸ä¸è½½ --- |
| | | public static final String STORAGE = "/ISAPI/ContentMgmt/Storage"; |
| | | public static final String RECORD_TRACKS = "/ISAPI/ContentMgmt/record/tracks"; |
| | | public static final String SEARCH_CAPABILITIES = "/ISAPI/ContentMgmt/search/capabilities"; |
| | | public static final String SEARCH_PROFILE = "/ISAPI/ContentMgmt/search/profile"; |
| | | public static final String SEARCH = "/ISAPI/ContentMgmt/search"; |
| | | public static final String SEARCH_STD = "/ISAPI/ContentMgmt/search?timeType=STD"; |
| | | public static final String DOWNLOAD = "/ISAPI/ContentMgmt/download"; |
| | | public static final String DOWNLOAD_CAPABILITIES = "/ISAPI/ContentMgmt/download/capabilities"; |
| | | |
| | | // --- å®å
¨ä»¤ç --- |
| | | public static final String SECURITY_TOKEN_JSON = "/ISAPI/Security/token?format=json"; |
| | | |
| | | // --- ééç«ï¼æ§æ³è®°å½/æ¡é¢ééç«ï¼--- |
| | | public static final String DOCK_BASIC_INFO = "/ISAPI/Traffic/dockStation/basicInfo?format=json"; |
| | | public static final String DOCK_DEVICE_MANAGEMENT = "/ISAPI/Traffic/dockStation/deviceManagement?format=json"; |
| | | public static final String DOCK_DEVICE_CAPABILITIES = "/ISAPI/Traffic/dockStation/deviceManagement/capabilities?format=json"; |
| | | public static final String DOCK_PERSON_MANAGEMENT = "/ISAPI/Traffic/dockStation/personManagement?format=json"; |
| | | public static final String DOCK_PLATFORM_SCHEDULE = "/ISAPI/Traffic/dockStation/platformConfig/schedule?format=json"; |
| | | |
| | | public static final String CONTENT_TYPE_XML = "application/xml"; |
| | | public static final String CONTENT_TYPE_JSON = "application/json"; |
| | | |
| | | /** é»è®¤ trackï¼éé1ä¸»ç æµï¼å½åï¼ */ |
| | | public static final String DEFAULT_TRACK_ID = "101"; |
| | | /** é»è®¤ trackï¼éé1æå¾ï¼å¾çï¼ */ |
| | | public static final String DEFAULT_PICTURE_TRACK_ID = "103"; |
| | | |
| | | public static final int DEFAULT_MAX_RESULTS = 100; |
| | | public static final int MAX_PAGE_RESULTS = 500; |
| | | |
| | | /** ä¸è½½æä»¶ï¼downType=2ï¼ */ |
| | | public static final String DOWNLOAD_DOWN_TYPE_FILE = "2"; |
| | | /** è§é¢å°è£
mp4 */ |
| | | public static final String DOWNLOAD_ENCODE_MP4 = "mp4"; |
| | | |
| | | /** åé¡µç¶æï¼è¿ææ´å¤ç»æ */ |
| | | public static final String SEARCH_STATUS_MORE = "MORE"; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.doumee.core.haikang.isapi.model.DockDeviceDTO; |
| | | import com.doumee.core.haikang.isapi.model.DockStationBasicInfoDTO; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ISAPI JSON ååºè§£æï¼ééç« dockStation æ¥å£ï¼ |
| | | */ |
| | | public final class IsapiJsonParser { |
| | | |
| | | private IsapiJsonParser() { |
| | | } |
| | | |
| | | public static DockStationBasicInfoDTO parseDockBasicInfo(String json) { |
| | | if (json == null || json.isEmpty()) { |
| | | return null; |
| | | } |
| | | try { |
| | | JSONObject root = JSONObject.parseObject(json); |
| | | JSONObject basic = root.getJSONObject("BasicInfo"); |
| | | if (basic == null) { |
| | | return null; |
| | | } |
| | | DockStationBasicInfoDTO dto = new DockStationBasicInfoDTO(); |
| | | dto.setDockStationId(basic.getString("dockStationID")); |
| | | dto.setDockStationType(basic.getString("dockStationType")); |
| | | return dto; |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public static List<DockDeviceDTO> parseDockDeviceList(String json) { |
| | | List<DockDeviceDTO> list = new ArrayList<>(); |
| | | if (json == null || json.isEmpty()) { |
| | | return list; |
| | | } |
| | | try { |
| | | JSONObject root = JSONObject.parseObject(json); |
| | | JSONArray deviceInfoList = root.getJSONArray("DeviceInfoList"); |
| | | if (deviceInfoList == null) { |
| | | return list; |
| | | } |
| | | for (int i = 0; i < deviceInfoList.size(); i++) { |
| | | JSONObject item = deviceInfoList.getJSONObject(i); |
| | | if (item == null) { |
| | | continue; |
| | | } |
| | | JSONObject info = item.getJSONObject("DeviceInfo"); |
| | | if (info == null) { |
| | | continue; |
| | | } |
| | | DockDeviceDTO dto = new DockDeviceDTO(); |
| | | dto.setDeviceId(info.getString("deviceId")); |
| | | dto.setDeviceName(info.getString("deviceName")); |
| | | dto.setShortSerialNumber(info.getString("shortSerialNumber")); |
| | | dto.setAccessDeviceId(info.getString("accessDeviceID")); |
| | | dto.setNetworkedDevice(info.getBoolean("isNetworkedDevice")); |
| | | list.add(dto); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | return list; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | import com.doumee.core.utils.IsapiHttpUtil; |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | |
| | | import java.io.InputStream; |
| | | |
| | | /** |
| | | * ISAPI 请æ±è¾
å©ï¼ä»ééç«é
ç½®æåè¿æ¥åæ°ï¼é¿å
system_service ååä¾èµä¸å¡æ¨¡åã |
| | | */ |
| | | final class IsapiRequestHelper { |
| | | |
| | | private IsapiRequestHelper() { |
| | | } |
| | | |
| | | static String doGet(CollectionStation station, String uri) { |
| | | IsapiStationContext ctx = context(station); |
| | | return IsapiHttpUtil.doGet(ctx.getHost(), ctx.getPort(), ctx.isHttps(), |
| | | ctx.getUsername(), ctx.getPassword(), uri); |
| | | } |
| | | |
| | | static String doPost(CollectionStation station, String uri, String body, String contentType) { |
| | | IsapiStationContext ctx = context(station); |
| | | return IsapiHttpUtil.doPost(ctx.getHost(), ctx.getPort(), ctx.isHttps(), |
| | | ctx.getUsername(), ctx.getPassword(), uri, body, contentType); |
| | | } |
| | | |
| | | static InputStream doDownload(CollectionStation station, String uri, String downloadBody) { |
| | | IsapiStationContext ctx = context(station); |
| | | return IsapiHttpUtil.doDownload(ctx.getHost(), ctx.getPort(), ctx.isHttps(), |
| | | ctx.getUsername(), ctx.getPassword(), uri, downloadBody); |
| | | } |
| | | |
| | | static IsapiStationContext context(CollectionStation station) { |
| | | return new IsapiStationContext(station); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * ééç« ISAPI è¿æ¥ä¸ä¸æ |
| | | */ |
| | | @Getter |
| | | public class IsapiStationContext { |
| | | |
| | | private final String host; |
| | | private final int port; |
| | | private final boolean https; |
| | | private final String username; |
| | | private final String password; |
| | | |
| | | public IsapiStationContext(CollectionStation station) { |
| | | this.host = station.getIp(); |
| | | this.port = station.getPort() != null ? station.getPort() : 80; |
| | | this.https = station.getUseHttps() != null && station.getUseHttps() == 1; |
| | | this.username = station.getUsername(); |
| | | this.password = station.getPassword(); |
| | | } |
| | | |
| | | public IsapiStationContext(String host, int port, boolean https, String username, String password) { |
| | | this.host = host; |
| | | this.port = port; |
| | | this.https = https; |
| | | this.username = username; |
| | | this.password = password; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi; |
| | | |
| | | import com.doumee.core.haikang.isapi.model.DeviceInfoDTO; |
| | | import com.doumee.core.haikang.isapi.model.MediaItemDTO; |
| | | import com.doumee.core.haikang.isapi.model.RecordTrackDTO; |
| | | import com.doumee.core.haikang.isapi.model.SearchPageResult; |
| | | import com.doumee.core.haikang.isapi.model.StorageInfoDTO; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.w3c.dom.Document; |
| | | import org.w3c.dom.Element; |
| | | import org.w3c.dom.Node; |
| | | import org.w3c.dom.NodeList; |
| | | |
| | | import javax.xml.parsers.DocumentBuilderFactory; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.net.URLDecoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | | /** |
| | | * ISAPI XML ååºè§£æ |
| | | */ |
| | | public class IsapiXmlParser { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(IsapiXmlParser.class); |
| | | |
| | | private IsapiXmlParser() { |
| | | } |
| | | |
| | | public static DeviceInfoDTO parseDeviceInfo(String xml) { |
| | | if (StringUtils.isBlank(xml)) { |
| | | return null; |
| | | } |
| | | DeviceInfoDTO dto = new DeviceInfoDTO(); |
| | | dto.setDeviceName(extractTag(xml, "deviceName")); |
| | | dto.setDeviceId(extractTag(xml, "deviceID")); |
| | | dto.setModel(extractTag(xml, "model")); |
| | | dto.setSerialNumber(extractTag(xml, "serialNumber")); |
| | | dto.setFirmwareVersion(extractTag(xml, "firmwareVersion")); |
| | | dto.setDeviceType(extractTag(xml, "deviceType")); |
| | | return dto; |
| | | } |
| | | |
| | | public static StorageInfoDTO parseStorage(String xml) { |
| | | if (StringUtils.isBlank(xml)) { |
| | | return null; |
| | | } |
| | | StorageInfoDTO dto = new StorageInfoDTO(); |
| | | long total = 0; |
| | | long free = 0; |
| | | try { |
| | | Document doc = parseDocument(xml); |
| | | NodeList hddNodes = doc.getElementsByTagName("hdd"); |
| | | for (int i = 0; i < hddNodes.getLength(); i++) { |
| | | Element hdd = (Element) hddNodes.item(i); |
| | | total += parseLong(getChildText(hdd, "capacity")); |
| | | free += parseLong(getChildText(hdd, "freeSpace")); |
| | | } |
| | | } catch (Exception e) { |
| | | total = parseLong(extractTag(xml, "capacity")); |
| | | free = parseLong(extractTag(xml, "freeSpace")); |
| | | } |
| | | if (total > 0) { |
| | | dto.setTotalSpaceGb(total); |
| | | dto.setFreeSpaceGb(free); |
| | | } |
| | | return dto; |
| | | } |
| | | |
| | | public static List<RecordTrackDTO> parseRecordTracks(String xml) { |
| | | List<RecordTrackDTO> list = new ArrayList<>(); |
| | | if (StringUtils.isBlank(xml)) { |
| | | return list; |
| | | } |
| | | try { |
| | | Document doc = parseDocument(xml); |
| | | NodeList trackNodes = doc.getElementsByTagName("Track"); |
| | | if (trackNodes.getLength() == 0) { |
| | | trackNodes = doc.getElementsByTagName("track"); |
| | | } |
| | | for (int i = 0; i < trackNodes.getLength(); i++) { |
| | | Element track = (Element) trackNodes.item(i); |
| | | String id = getChildText(track, "id"); |
| | | if (StringUtils.isBlank(id)) { |
| | | id = getChildText(track, "trackID"); |
| | | } |
| | | if (StringUtils.isBlank(id)) { |
| | | continue; |
| | | } |
| | | RecordTrackDTO dto = new RecordTrackDTO(); |
| | | dto.setId(id.trim()); |
| | | dto.setChannel(getChildText(track, "channel")); |
| | | dto.setStreamType(resolveStreamType(id)); |
| | | list.add(dto); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | if (list.isEmpty()) { |
| | | Matcher m = Pattern.compile("<id>(\\d+)</id>").matcher(xml); |
| | | while (m.find()) { |
| | | RecordTrackDTO dto = new RecordTrackDTO(); |
| | | dto.setId(m.group(1)); |
| | | dto.setStreamType(resolveStreamType(dto.getId())); |
| | | list.add(dto); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | public static SearchPageResult parseSearchPage(String xml) { |
| | | SearchPageResult page = new SearchPageResult(); |
| | | if (StringUtils.isBlank(xml)) { |
| | | return page; |
| | | } |
| | | String normalized = normalizeXml(xml); |
| | | if (normalized.contains("statusCode") && normalized.contains("<statusCode>")) { |
| | | String statusCode = firstNonBlank(extractTagIgnoreCase(normalized, "statusCode"), extractTag(normalized, "statusCode")); |
| | | if (StringUtils.isNotBlank(statusCode) && !"1".equals(statusCode.trim()) && !"0".equals(statusCode.trim())) { |
| | | log.warn("ISAPI search è¿åé误 statusCode={} statusString={} subStatusCode={}", |
| | | statusCode, |
| | | firstNonBlank(extractTagIgnoreCase(normalized, "statusString"), extractTag(normalized, "statusString")), |
| | | firstNonBlank(extractTagIgnoreCase(normalized, "subStatusCode"), extractTag(normalized, "subStatusCode"))); |
| | | } |
| | | } |
| | | page.setResponseStatusStrg(firstNonBlank( |
| | | extractTagIgnoreCase(normalized, "responseStatusStrg"), |
| | | extractTag(normalized, "responseStatusStrg"))); |
| | | page.setNumOfMatches((int) parseLong(firstNonBlank( |
| | | extractTagIgnoreCase(normalized, "numOfMatches"), |
| | | extractTag(normalized, "numOfMatches"), |
| | | extractTagIgnoreCase(normalized, "totalMatches"), |
| | | extractTag(normalized, "totalMatches")))); |
| | | page.setHasMore(IsapiConstants.SEARCH_STATUS_MORE.equalsIgnoreCase(page.getResponseStatusStrg())); |
| | | page.setItems(parseSearchResult(normalized)); |
| | | if (page.getItems().isEmpty() && page.getNumOfMatches() > 0) { |
| | | log.warn("ISAPI search numOfMatches={} ä½è§£æ items 为空", page.getNumOfMatches()); |
| | | } |
| | | return page; |
| | | } |
| | | |
| | | public static List<MediaItemDTO> parseSearchResult(String xml) { |
| | | List<MediaItemDTO> list = new ArrayList<>(); |
| | | if (StringUtils.isBlank(xml)) { |
| | | return list; |
| | | } |
| | | String normalized = normalizeXml(xml); |
| | | if (normalized.startsWith("{")) { |
| | | return parseSearchResultJson(normalized); |
| | | } |
| | | try { |
| | | Document doc = parseDocument(normalized); |
| | | collectSearchMatchItems(doc.getDocumentElement(), list); |
| | | } catch (Exception e) { |
| | | log.warn("ISAPI search DOMè§£æå¤±è´¥ï¼å°è¯æ£å: {}", e.getMessage()); |
| | | } |
| | | if (list.isEmpty()) { |
| | | collectSearchMatchItemsByRegex(normalized, list); |
| | | } |
| | | if (list.isEmpty() && (normalized.toLowerCase().contains("searchmatchitem") |
| | | || normalized.toLowerCase().contains("matchelement"))) { |
| | | log.warn("ISAPI search å«å¹é
项ä½è§£æä¸ºç©ºï¼response={}", |
| | | StringUtils.abbreviate(normalized, 500)); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | private static final String[] MATCH_ITEM_TAGS = {"searchMatchItem", "matchElement", "matchItem"}; |
| | | |
| | | private static void collectSearchMatchItems(Element root, List<MediaItemDTO> list) { |
| | | if (root == null) { |
| | | return; |
| | | } |
| | | for (String tag : MATCH_ITEM_TAGS) { |
| | | NodeList nodes = getElementsByLocalName(root, tag); |
| | | for (int i = 0; i < nodes.getLength(); i++) { |
| | | Node node = nodes.item(i); |
| | | if (node.getNodeType() != Node.ELEMENT_NODE) { |
| | | continue; |
| | | } |
| | | addParsedMedia(list, parseMatchItem((Element) node)); |
| | | } |
| | | if (!list.isEmpty()) { |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void collectSearchMatchItemsByRegex(String xml, List<MediaItemDTO> list) { |
| | | for (String tag : MATCH_ITEM_TAGS) { |
| | | Matcher matcher = Pattern.compile( |
| | | "(?is)<(?:\\w+:)?" + tag + "\\b[^>]*>(.*?)</(?:\\w+:)?" + tag + "\\s*>").matcher(xml); |
| | | while (matcher.find()) { |
| | | addParsedMedia(list, parseMatchItemFromBlock(matcher.group(1))); |
| | | } |
| | | if (!list.isEmpty()) { |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void addParsedMedia(List<MediaItemDTO> list, MediaItemDTO media) { |
| | | if (media == null) { |
| | | return; |
| | | } |
| | | if (StringUtils.isBlank(media.getFileIndex())) { |
| | | String fallback = buildFileIndexFallback(media); |
| | | if (StringUtils.isNotBlank(fallback)) { |
| | | media.setFileIndex(fallback); |
| | | if (StringUtils.isBlank(media.getFileName())) { |
| | | media.setFileName(fallback); |
| | | } |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(media.getFileIndex()) || StringUtils.isNotBlank(media.getPlaybackUri())) { |
| | | list.add(media); |
| | | } |
| | | } |
| | | |
| | | private static List<MediaItemDTO> parseSearchResultJson(String json) { |
| | | List<MediaItemDTO> list = new ArrayList<>(); |
| | | try { |
| | | com.alibaba.fastjson.JSONObject root = com.alibaba.fastjson.JSONObject.parseObject(json); |
| | | com.alibaba.fastjson.JSONObject searchResult = root.getJSONObject("CMSearchResult"); |
| | | if (searchResult != null) { |
| | | root = searchResult; |
| | | } |
| | | com.alibaba.fastjson.JSONArray matchList = extractJsonMatchList(root); |
| | | if (matchList == null || matchList.isEmpty()) { |
| | | return list; |
| | | } |
| | | for (int i = 0; i < matchList.size(); i++) { |
| | | com.alibaba.fastjson.JSONObject item = matchList.getJSONObject(i); |
| | | if (item == null) { |
| | | continue; |
| | | } |
| | | com.alibaba.fastjson.JSONObject desc = item.getJSONObject("MediaSegmentDescriptor"); |
| | | if (desc == null) { |
| | | desc = item.getJSONObject("mediaSegmentDescriptor"); |
| | | } |
| | | if (desc == null) { |
| | | desc = item.getJSONObject("recordSegmentDescriptor"); |
| | | } |
| | | if (desc == null) { |
| | | desc = item; |
| | | } |
| | | MediaItemDTO dto = new MediaItemDTO(); |
| | | dto.setTrackId(firstNonBlank(item.getString("trackID"), item.getString("trackId"))); |
| | | com.alibaba.fastjson.JSONObject timeSpan = item.getJSONObject("timeSpan"); |
| | | dto.setStartTime(parseIsapiTime(firstNonBlank(item.getString("startTime"), |
| | | timeSpan != null ? timeSpan.getString("startTime") : null))); |
| | | dto.setEndTime(parseIsapiTime(firstNonBlank(item.getString("endTime"), |
| | | timeSpan != null ? timeSpan.getString("endTime") : null))); |
| | | dto.setPlaybackUri(firstNonBlank(desc.getString("playbackURI"), desc.getString("playbackUri"), |
| | | desc.getString("downloadURI"), desc.getString("fileUrl"))); |
| | | dto.setContentType(firstNonBlank(desc.getString("contentType"), desc.getString("contenType"))); |
| | | dto.setFileSize(desc.getLong("size")); |
| | | dto.setFileName(firstNonBlank(desc.getString("name"), desc.getString("fileName"), |
| | | desc.getString("mediaID"), item.getString("sourceID"))); |
| | | dto.setUserName(firstNonBlank(desc.getString("policeName"), item.getString("policeName"))); |
| | | dto.setRecorderSn(firstNonBlank(desc.getString("bodyCameraShortSN"), |
| | | desc.getString("recorderCode"), desc.getString("bodyCameraShortSN"))); |
| | | if (StringUtils.isBlank(dto.getFileName())) { |
| | | dto.setFileName(extractNameFromUri(dto.getPlaybackUri())); |
| | | } |
| | | applyFileIdentity(dto, desc.getString("mediaID")); |
| | | dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); |
| | | addParsedMedia(list, dto); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("ISAPI search JSONè§£æå¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | private static com.alibaba.fastjson.JSONArray extractJsonMatchList(com.alibaba.fastjson.JSONObject root) { |
| | | com.alibaba.fastjson.JSONArray matchList = root.getJSONArray("MatchList"); |
| | | if (matchList == null) { |
| | | matchList = root.getJSONArray("matchList"); |
| | | } |
| | | if (matchList != null) { |
| | | return matchList; |
| | | } |
| | | com.alibaba.fastjson.JSONObject matchObj = root.getJSONObject("MatchList"); |
| | | if (matchObj == null) { |
| | | matchObj = root.getJSONObject("matchList"); |
| | | } |
| | | if (matchObj == null) { |
| | | return null; |
| | | } |
| | | com.alibaba.fastjson.JSONArray items = matchObj.getJSONArray("SearchMatchItem"); |
| | | if (items == null) { |
| | | items = matchObj.getJSONArray("searchMatchItem"); |
| | | } |
| | | if (items == null) { |
| | | items = matchObj.getJSONArray("matchElement"); |
| | | } |
| | | if (items == null) { |
| | | items = matchObj.getJSONArray("MatchElement"); |
| | | } |
| | | if (items != null) { |
| | | return items; |
| | | } |
| | | com.alibaba.fastjson.JSONObject single = matchObj.getJSONObject("SearchMatchItem"); |
| | | if (single == null) { |
| | | single = matchObj.getJSONObject("searchMatchItem"); |
| | | } |
| | | if (single == null) { |
| | | single = matchObj.getJSONObject("matchElement"); |
| | | } |
| | | if (single == null) { |
| | | single = matchObj.getJSONObject("MatchElement"); |
| | | } |
| | | if (single != null) { |
| | | com.alibaba.fastjson.JSONArray arr = new com.alibaba.fastjson.JSONArray(); |
| | | arr.add(single); |
| | | return arr; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static MediaItemDTO parseMatchItemFromBlock(String block) { |
| | | MediaItemDTO dto = new MediaItemDTO(); |
| | | dto.setTrackId(extractTagIgnoreCase(block, "trackID")); |
| | | dto.setStartTime(parseIsapiTime(firstNonBlank( |
| | | extractTagIgnoreCase(block, "startTime"), |
| | | extractNestedTagIgnoreCase(block, "timeSpan", "startTime")))); |
| | | dto.setEndTime(parseIsapiTime(firstNonBlank( |
| | | extractTagIgnoreCase(block, "endTime"), |
| | | extractNestedTagIgnoreCase(block, "timeSpan", "endTime")))); |
| | | String descBlock = firstNonBlank( |
| | | extractBlockIgnoreCase(block, "mediaSegmentDescriptor"), |
| | | extractBlockIgnoreCase(block, "recordSegmentDescriptor"), |
| | | block); |
| | | dto.setPlaybackUri(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "playbackURI"), |
| | | extractTagIgnoreCase(descBlock, "playbackUri"), |
| | | extractTagIgnoreCase(descBlock, "downloadURI"), |
| | | extractTagIgnoreCase(descBlock, "fileUrl"))); |
| | | dto.setContentType(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "contentType"), |
| | | extractTagIgnoreCase(descBlock, "contenType"))); |
| | | dto.setFileSize(parseLong(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "size"), |
| | | extractSizeFromUri(dto.getPlaybackUri())))); |
| | | String mediaId = extractTagIgnoreCase(descBlock, "mediaID"); |
| | | dto.setFileName(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "name"), |
| | | extractTagIgnoreCase(descBlock, "fileName"), |
| | | mediaId, |
| | | extractTagIgnoreCase(block, "sourceID"))); |
| | | dto.setUserName(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "policeName"), |
| | | extractTagIgnoreCase(block, "policeName"))); |
| | | dto.setRecorderSn(firstNonBlank( |
| | | extractTagIgnoreCase(descBlock, "bodyCameraShortSN"), |
| | | extractTagIgnoreCase(descBlock, "recorderCode"), |
| | | extractTagIgnoreCase(block, "bodyCameraShortSN"), |
| | | extractTagIgnoreCase(block, "recorderCode"), |
| | | extractTagIgnoreCase(descBlock, "shortSerialNumber"))); |
| | | if (StringUtils.isBlank(dto.getFileName())) { |
| | | dto.setFileName(extractNameFromUri(dto.getPlaybackUri())); |
| | | } |
| | | applyFileIdentity(dto, mediaId); |
| | | dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); |
| | | return dto; |
| | | } |
| | | |
| | | private static void applyFileIdentity(MediaItemDTO dto, String mediaId) { |
| | | if (StringUtils.isBlank(dto.getFileIndex())) { |
| | | dto.setFileIndex(firstNonBlank( |
| | | mediaId, |
| | | StringUtils.isNoneBlank(dto.getTrackId(), dto.getFileName()) |
| | | ? dto.getTrackId() + "_" + dto.getFileName() : null, |
| | | dto.getFileName())); |
| | | } |
| | | } |
| | | |
| | | private static String resolveContentType(Element desc) { |
| | | return firstNonBlank(getChildText(desc, "contentType"), getChildText(desc, "contenType")); |
| | | } |
| | | |
| | | public static String parseSecurityToken(String json) { |
| | | if (StringUtils.isBlank(json)) { |
| | | return null; |
| | | } |
| | | Matcher m = Pattern.compile("\"value\"\\s*:\\s*\"([^\"]+)\"").matcher(json); |
| | | if (m.find()) { |
| | | return m.group(1); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static MediaItemDTO parseMatchItem(Element item) { |
| | | MediaItemDTO dto = new MediaItemDTO(); |
| | | dto.setTrackId(firstNonBlank( |
| | | getChildText(item, "trackID"), |
| | | getChildText(item, "trackId"), |
| | | getChildText(item, "trackid"))); |
| | | |
| | | Element timeSpan = findFirstChildElement(item, "timeSpan"); |
| | | if (timeSpan != null) { |
| | | dto.setStartTime(parseIsapiTime(getChildText(timeSpan, "startTime"))); |
| | | dto.setEndTime(parseIsapiTime(getChildText(timeSpan, "endTime"))); |
| | | } else { |
| | | dto.setStartTime(parseIsapiTime(getChildText(item, "startTime"))); |
| | | dto.setEndTime(parseIsapiTime(getChildText(item, "endTime"))); |
| | | } |
| | | |
| | | Element desc = findDescriptorElement(item); |
| | | |
| | | dto.setPlaybackUri(firstNonBlank( |
| | | getChildText(desc, "playbackURI"), |
| | | getChildText(desc, "playbackUri"), |
| | | getChildText(desc, "downloadURI"), |
| | | getChildText(desc, "fileUrl"), |
| | | getChildText(item, "playbackURI"), |
| | | getChildText(item, "downloadURI"))); |
| | | dto.setContentType(resolveContentType(desc)); |
| | | dto.setFileSize(parseLong(firstNonBlank( |
| | | getChildText(desc, "size"), |
| | | getChildText(desc, "fileSize"), |
| | | extractSizeFromUri(dto.getPlaybackUri())))); |
| | | String mediaId = getChildText(desc, "mediaID"); |
| | | String name = firstNonBlank( |
| | | getChildText(desc, "name"), |
| | | getChildText(desc, "fileName"), |
| | | getChildText(item, "name"), |
| | | getChildText(item, "fileName"), |
| | | mediaId, |
| | | getChildText(item, "sourceID")); |
| | | if (StringUtils.isBlank(name)) { |
| | | name = extractNameFromUri(dto.getPlaybackUri()); |
| | | } |
| | | dto.setFileName(name); |
| | | applyFileIdentity(dto, mediaId); |
| | | dto.setUserName(firstNonBlank( |
| | | getChildText(desc, "policeName"), |
| | | getChildText(item, "policeName"), |
| | | getChildText(desc, "userName"))); |
| | | dto.setRecorderSn(firstNonBlank( |
| | | getChildText(desc, "bodyCameraShortSN"), |
| | | getChildText(desc, "recorderCode"), |
| | | getChildText(item, "bodyCameraShortSN"), |
| | | getChildText(item, "recorderCode"), |
| | | getChildText(desc, "shortSerialNumber"), |
| | | getChildText(item, "shortSerialNumber"))); |
| | | dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName())); |
| | | return dto; |
| | | } |
| | | |
| | | private static Element findDescriptorElement(Element item) { |
| | | NodeList descriptors = getElementsByLocalName(item, "mediaSegmentDescriptor"); |
| | | if (descriptors.getLength() == 0) { |
| | | descriptors = getElementsByLocalName(item, "recordSegmentDescriptor"); |
| | | } |
| | | if (descriptors.getLength() == 0) { |
| | | descriptors = getElementsByLocalName(item, "segmentDescriptor"); |
| | | } |
| | | return descriptors.getLength() > 0 ? (Element) descriptors.item(0) : item; |
| | | } |
| | | |
| | | private static Element findFirstChildElement(Element parent, String localName) { |
| | | if (parent == null) { |
| | | return null; |
| | | } |
| | | NodeList nodes = parent.getChildNodes(); |
| | | for (int i = 0; i < nodes.getLength(); i++) { |
| | | Node node = nodes.item(i); |
| | | if (node.getNodeType() != Node.ELEMENT_NODE) { |
| | | continue; |
| | | } |
| | | Element el = (Element) node; |
| | | String name = el.getLocalName() != null ? el.getLocalName() : el.getNodeName(); |
| | | if (localName.equalsIgnoreCase(name)) { |
| | | return el; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static int resolveStreamType(String trackId) { |
| | | if (StringUtils.isBlank(trackId) || trackId.length() < 3) { |
| | | return 0; |
| | | } |
| | | char last = trackId.charAt(trackId.length() - 1); |
| | | if (last == '3') { |
| | | return 2; |
| | | } |
| | | if (last == '2') { |
| | | return 1; |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private static int resolveMediaType(String contentType, String trackId, String fileName) { |
| | | if (StringUtils.isNotBlank(contentType)) { |
| | | String lower = contentType.toLowerCase(); |
| | | if (lower.contains("picture") || lower.contains("image") || lower.equals("metadata")) { |
| | | return 1; |
| | | } |
| | | if (lower.contains("audio")) { |
| | | return 2; |
| | | } |
| | | if (lower.contains("video")) { |
| | | return 0; |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(fileName)) { |
| | | String lower = fileName.toLowerCase(); |
| | | if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png") |
| | | || lower.endsWith(".bmp")) { |
| | | return 1; |
| | | } |
| | | if (lower.endsWith(".mp3") || lower.endsWith(".wav") || lower.endsWith(".aac")) { |
| | | return 2; |
| | | } |
| | | if (lower.endsWith(".mp4") || lower.endsWith(".avi") || lower.endsWith(".mov") |
| | | || lower.endsWith(".mkv")) { |
| | | return 0; |
| | | } |
| | | } |
| | | if (StringUtils.isNotBlank(trackId) && trackId.endsWith("3")) { |
| | | return 1; |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private static int resolveMediaType(String contentType, String trackId) { |
| | | return resolveMediaType(contentType, trackId, null); |
| | | } |
| | | |
| | | private static String extractNameFromUri(String uri) { |
| | | if (StringUtils.isBlank(uri)) { |
| | | return null; |
| | | } |
| | | String normalized = uri.replace("&", "&"); |
| | | Matcher m = Pattern.compile("[?&](?:name|filename|fileName)=([^&\\s]+)", Pattern.CASE_INSENSITIVE).matcher(normalized); |
| | | if (m.find()) { |
| | | try { |
| | | return URLDecoder.decode(m.group(1), StandardCharsets.UTF_8.name()); |
| | | } catch (Exception e) { |
| | | return m.group(1); |
| | | } |
| | | } |
| | | int slash = normalized.lastIndexOf('/'); |
| | | if (slash >= 0 && slash < normalized.length() - 1) { |
| | | String tail = normalized.substring(slash + 1); |
| | | int q = tail.indexOf('?'); |
| | | if (q > 0) { |
| | | tail = tail.substring(0, q); |
| | | } |
| | | if (StringUtils.isNotBlank(tail) && !tail.equals("tracks")) { |
| | | return tail; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String buildFileIndexFallback(MediaItemDTO dto) { |
| | | if (StringUtils.isNotBlank(dto.getPlaybackUri())) { |
| | | return String.valueOf(Math.abs(dto.getPlaybackUri().hashCode())); |
| | | } |
| | | if (dto.getStartTime() != null) { |
| | | return StringUtils.defaultString(dto.getTrackId(), "track") + "_" + dto.getStartTime().getTime(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String getDirectChildText(Element parent, String tag) { |
| | | if (parent == null) { |
| | | return null; |
| | | } |
| | | NodeList nodes = parent.getChildNodes(); |
| | | for (int i = 0; i < nodes.getLength(); i++) { |
| | | Node node = nodes.item(i); |
| | | if (node.getNodeType() == Node.ELEMENT_NODE && tag.equalsIgnoreCase(node.getNodeName())) { |
| | | return node.getTextContent(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static NodeList getElementsByLocalName(Element root, String localName) { |
| | | NodeList nodes = root.getElementsByTagNameNS("*", localName); |
| | | if (nodes.getLength() > 0) { |
| | | return nodes; |
| | | } |
| | | nodes = root.getElementsByTagName(localName); |
| | | if (nodes.getLength() > 0) { |
| | | return nodes; |
| | | } |
| | | return findElementsByLocalNameIgnoreCase(root, localName); |
| | | } |
| | | |
| | | private static NodeList findElementsByLocalNameIgnoreCase(Element root, String localName) { |
| | | java.util.List<Element> matched = new ArrayList<>(); |
| | | collectElementsByLocalNameIgnoreCase(root, localName.toLowerCase(), matched); |
| | | return new NodeListWrapper(matched); |
| | | } |
| | | |
| | | private static void collectElementsByLocalNameIgnoreCase(Element element, String localNameLower, |
| | | java.util.List<Element> matched) { |
| | | if (localNameLower.equals(element.getLocalName() != null |
| | | ? element.getLocalName().toLowerCase() |
| | | : element.getNodeName().toLowerCase())) { |
| | | matched.add(element); |
| | | } |
| | | NodeList children = element.getChildNodes(); |
| | | for (int i = 0; i < children.getLength(); i++) { |
| | | Node child = children.item(i); |
| | | if (child.getNodeType() == Node.ELEMENT_NODE) { |
| | | collectElementsByLocalNameIgnoreCase((Element) child, localNameLower, matched); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static String normalizeXml(String xml) { |
| | | String text = stripBom(StringUtils.defaultString(xml).trim()); |
| | | if (text.startsWith("\"") && text.endsWith("\"")) { |
| | | text = text.substring(1, text.length() - 1); |
| | | } |
| | | if (text.startsWith("<") || text.startsWith("<")) { |
| | | text = text.replace("<", "<").replace(">", ">").replace(""", "\""); |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private static String extractSizeFromUri(String uri) { |
| | | if (StringUtils.isBlank(uri)) { |
| | | return null; |
| | | } |
| | | Matcher matcher = Pattern.compile("[?&;]size=([0-9]+)", Pattern.CASE_INSENSITIVE) |
| | | .matcher(uri.replace("&", "&")); |
| | | return matcher.find() ? matcher.group(1) : null; |
| | | } |
| | | |
| | | private static String stripBom(String text) { |
| | | if (text != null && text.startsWith("\uFEFF")) { |
| | | return text.substring(1); |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private static String extractTagIgnoreCase(String xml, String tag) { |
| | | Pattern pattern = Pattern.compile( |
| | | "(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>([^<]*)</(?:\\w+:)?" + Pattern.quote(tag) + "\\s*>"); |
| | | Matcher matcher = pattern.matcher(xml); |
| | | return matcher.find() ? matcher.group(1).trim() : null; |
| | | } |
| | | |
| | | private static String extractNestedTagIgnoreCase(String xml, String parentTag, String childTag) { |
| | | String parentBlock = extractBlockIgnoreCase(xml, parentTag); |
| | | if (StringUtils.isBlank(parentBlock)) { |
| | | return null; |
| | | } |
| | | return extractTagIgnoreCase(parentBlock, childTag); |
| | | } |
| | | |
| | | private static String extractBlockIgnoreCase(String xml, String tag) { |
| | | Pattern pattern = Pattern.compile( |
| | | "(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>(.*?)</(?:\\w+:)?" + Pattern.quote(tag) + "\\s*>"); |
| | | Matcher matcher = pattern.matcher(xml); |
| | | return matcher.find() ? matcher.group(1) : null; |
| | | } |
| | | |
| | | /** å
¼å®¹å½åç©ºé´ XML ç NodeList */ |
| | | private static class NodeListWrapper implements NodeList { |
| | | private final java.util.List<Element> elements; |
| | | |
| | | NodeListWrapper(java.util.List<Element> elements) { |
| | | this.elements = elements; |
| | | } |
| | | |
| | | @Override |
| | | public Node item(int index) { |
| | | return elements.get(index); |
| | | } |
| | | |
| | | @Override |
| | | public int getLength() { |
| | | return elements.size(); |
| | | } |
| | | } |
| | | |
| | | private static Document parseDocument(String xml) throws Exception { |
| | | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| | | factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
| | | factory.setNamespaceAware(true); |
| | | return factory.newDocumentBuilder() |
| | | .parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); |
| | | } |
| | | |
| | | private static String getChildText(Element parent, String tag) { |
| | | if (parent == null) { |
| | | return null; |
| | | } |
| | | NodeList nodes = getElementsByLocalName(parent, tag); |
| | | if (nodes.getLength() == 0) { |
| | | return null; |
| | | } |
| | | return nodes.item(0).getTextContent(); |
| | | } |
| | | |
| | | private static String extractTag(String xml, String tag) { |
| | | Matcher m = Pattern.compile("<" + tag + ">([^<]*)</" + tag + ">").matcher(xml); |
| | | if (m.find()) { |
| | | return m.group(1).trim(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static long parseLong(String val) { |
| | | if (StringUtils.isBlank(val)) { |
| | | return 0; |
| | | } |
| | | try { |
| | | return Long.parseLong(val.trim()); |
| | | } catch (NumberFormatException e) { |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | private static Date parseIsapiTime(String timeStr) { |
| | | if (StringUtils.isBlank(timeStr)) { |
| | | return null; |
| | | } |
| | | String[] patterns = { |
| | | "yyyy-MM-dd'T'HH:mm:ss'Z'", |
| | | "yyyy-MM-dd'T'HH:mm:ssXXX", |
| | | "yyyy-MM-dd'T'HH:mm:ss", |
| | | "yyyyMMdd'T'HHmmss'Z'", |
| | | "yyyyMMdd'T'HHmmss" |
| | | }; |
| | | for (String pattern : patterns) { |
| | | try { |
| | | SimpleDateFormat sdf = new SimpleDateFormat(pattern); |
| | | if (pattern.endsWith("'Z'")) { |
| | | sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); |
| | | } |
| | | return sdf.parse(timeStr.trim()); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String firstNonBlank(String... values) { |
| | | for (String v : values) { |
| | | if (StringUtils.isNotBlank(v)) { |
| | | return v; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class DeviceInfoDTO { |
| | | private String deviceName; |
| | | private String deviceId; |
| | | private String model; |
| | | private String serialNumber; |
| | | private String firmwareVersion; |
| | | private String deviceType; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class DockDeviceDTO { |
| | | private String deviceId; |
| | | private String deviceName; |
| | | private String shortSerialNumber; |
| | | private String accessDeviceId; |
| | | private Boolean networkedDevice; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class DockStationBasicInfoDTO { |
| | | private String dockStationId; |
| | | private String dockStationType; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | public class MediaItemDTO { |
| | | private String fileIndex; |
| | | private String fileName; |
| | | private String playbackUri; |
| | | private String contentType; |
| | | private int mediaType; |
| | | private Long fileSize; |
| | | private Date startTime; |
| | | private Date endTime; |
| | | private String recorderSn; |
| | | private String userName; |
| | | /** ISAPI trackIDï¼å¦ 101/103 */ |
| | | private String trackId; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class RecordTrackDTO { |
| | | /** track IDï¼å¦ 101=éé1ä¸»ç æµï¼103=éé1æå¾ */ |
| | | private String id; |
| | | private String channel; |
| | | /** 0=video 1=substream 2=picture */ |
| | | private int streamType; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | public class SearchPageResult { |
| | | private List<MediaItemDTO> items = new ArrayList<>(); |
| | | /** OK / MORE / NO MATCHES */ |
| | | private String responseStatusStrg; |
| | | private int numOfMatches; |
| | | private boolean hasMore; |
| | | |
| | | public boolean isMore() { |
| | | return hasMore || "MORE".equalsIgnoreCase(responseStatusStrg); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.core.haikang.isapi.model; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class StorageInfoDTO { |
| | | /** æ»å®¹é GB */ |
| | | private Long totalSpaceGb; |
| | | /** å©ä½å®¹é GB */ |
| | | private Long freeSpaceGb; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.admin.request; |
| | | |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | public class CollectionMediaSyncRequest { |
| | | |
| | | @ApiModelProperty(value = "ééç«ID") |
| | | private Integer stationId; |
| | | |
| | | @ApiModelProperty(value = "å¼å§æ¶é´") |
| | | private Date startTime; |
| | | |
| | | @ApiModelProperty(value = "ç»ææ¶é´") |
| | | private Date endTime; |
| | | |
| | | @ApiModelProperty(value = "æ¹éä¸è½½æ°ééå¶") |
| | | private Integer limit; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business; |
| | | |
| | | import com.doumee.dao.business.model.CollectionDockDevice; |
| | | import com.github.yulichang.base.MPJBaseMapper; |
| | | |
| | | public interface CollectionDockDeviceMapper extends MPJBaseMapper<CollectionDockDevice> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business; |
| | | |
| | | import com.doumee.dao.business.model.CollectionMedia; |
| | | import com.github.yulichang.base.MPJBaseMapper; |
| | | |
| | | public interface CollectionMediaMapper extends MPJBaseMapper<CollectionMedia> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business; |
| | | |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import com.github.yulichang.base.MPJBaseMapper; |
| | | |
| | | public interface CollectionStationMapper extends MPJBaseMapper<CollectionStation> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * ééç«æ§½ä½æ§æ³è®°å½ä»ª |
| | | */ |
| | | @Data |
| | | @ApiModel("ééç«æ§æ³è®¾å¤") |
| | | @TableName("`collection_dock_device`") |
| | | public class CollectionDockDevice { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | @ApiModelProperty(value = "ééç«ID") |
| | | private Integer stationId; |
| | | |
| | | @TableField(exist = false) |
| | | @ApiModelProperty(value = "ééç«åç§°") |
| | | private String stationName; |
| | | |
| | | private String deviceId; |
| | | private String deviceName; |
| | | private String shortSerialNumber; |
| | | private String accessDeviceId; |
| | | private Integer networkedDevice; |
| | | private Date lastSyncTime; |
| | | private Date createDate; |
| | | private Integer isdeleted; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * ééç«åªä½æä»¶ |
| | | */ |
| | | @Data |
| | | @ApiModel("ééç«åªä½æä»¶") |
| | | @TableName("`collection_media`") |
| | | public class CollectionMedia { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Integer id; |
| | | |
| | | @ApiModelProperty(value = "ééç«ID") |
| | | private Integer stationId; |
| | | |
| | | @ApiModelProperty(value = "设å¤ä¾§æä»¶å¯ä¸æ è¯") |
| | | private String fileIndex; |
| | | |
| | | @ApiModelProperty(value = "ISAPI trackID") |
| | | private String trackId; |
| | | |
| | | private String fileName; |
| | | private String playbackUri; |
| | | |
| | | @ApiModelProperty(value = "0è§é¢ 1å¾ç 2é³é¢") |
| | | private Integer mediaType; |
| | | |
| | | private String contentType; |
| | | private Long fileSize; |
| | | private Date startTime; |
| | | private Date endTime; |
| | | private String recorderSn; |
| | | private String userName; |
| | | private String filePathLocal; |
| | | |
| | | @TableField(exist = false) |
| | | @ApiModelProperty(value = "FTP宿´è®¿é®å°å") |
| | | private String fileUrlFull; |
| | | |
| | | @ApiModelProperty(value = "0å¾
ä¸è½½ 1å·²ä¸è½½ 2失败 3ä¸è½½ä¸") |
| | | private Integer downloadStatus; |
| | | |
| | | private Date downloadTime; |
| | | private Date createDate; |
| | | private Integer isdeleted; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.dao.business.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.doumee.core.annotation.excel.ExcelColumn; |
| | | import com.doumee.service.business.third.model.LoginUserModel; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * 海康æ¡é¢ééç«è®¾å¤ |
| | | */ |
| | | @Data |
| | | @ApiModel("ééç«è®¾å¤") |
| | | @TableName("`collection_station`") |
| | | public class CollectionStation extends LoginUserModel { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | @ApiModelProperty(value = "主é®") |
| | | private Integer id; |
| | | |
| | | private String creator; |
| | | private Date createDate; |
| | | private String edirot; |
| | | private Date editDate; |
| | | private Integer isdeleted; |
| | | private String remark; |
| | | |
| | | @ApiModelProperty(value = "ééç«åç§°") |
| | | @ExcelColumn(name = "ééç«åç§°") |
| | | private String name; |
| | | |
| | | @ApiModelProperty(value = "设å¤åºåå·") |
| | | private String serialNo; |
| | | |
| | | @ApiModelProperty(value = "设å¤IP") |
| | | private String ip; |
| | | |
| | | @ApiModelProperty(value = "设å¤ç«¯å£") |
| | | private Integer port; |
| | | |
| | | @ApiModelProperty(value = "æ¯å¦HTTPS 0å¦1æ¯") |
| | | private Integer useHttps; |
| | | |
| | | @ApiModelProperty(value = "ç»å½ç¨æ·å") |
| | | private String username; |
| | | |
| | | @ApiModelProperty(value = "ç»å½å¯ç ") |
| | | private String password; |
| | | |
| | | @ApiModelProperty(value = "设å¤åå·") |
| | | private String model; |
| | | |
| | | @ApiModelProperty(value = "å¨çº¿ç¶æ 0离线 1å¨çº¿") |
| | | private Integer online; |
| | | |
| | | @ApiModelProperty(value = "æ»åå¨ç©ºé´(KB)") |
| | | private Long totalSpace; |
| | | |
| | | @ApiModelProperty(value = "å©ä½åå¨ç©ºé´(KB)") |
| | | private Long freeSpace; |
| | | |
| | | @ApiModelProperty(value = "è½¯ä»¶çæ¬") |
| | | private String softwareVersion; |
| | | |
| | | @ApiModelProperty(value = "æè¿åæ¥æ¶é´") |
| | | private Date lastSyncTime; |
| | | |
| | | @ApiModelProperty(value = "ç¶æ 0ç¦ç¨ 1å¯ç¨") |
| | | private Integer status; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.service.business; |
| | | |
| | | import com.doumee.dao.admin.request.CollectionMediaSyncRequest; |
| | | import com.doumee.dao.business.model.CollectionMedia; |
| | | import com.doumee.service.business.third.model.PageData; |
| | | import com.doumee.service.business.third.model.PageWrap; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | |
| | | public interface CollectionMediaSyncService { |
| | | |
| | | String syncMediaList(CollectionMediaSyncRequest request); |
| | | |
| | | String downloadMedia(Integer mediaId); |
| | | |
| | | String batchDownload(CollectionMediaSyncRequest request); |
| | | |
| | | PageData<CollectionMedia> findPage(PageWrap<CollectionMedia> pageWrap); |
| | | |
| | | void previewMedia(Integer mediaId, HttpServletRequest request, HttpServletResponse response); |
| | | |
| | | void downloadMediaFile(Integer mediaId, HttpServletRequest request, HttpServletResponse response); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.service.business; |
| | | |
| | | import com.doumee.dao.business.model.CollectionDockDevice; |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import com.doumee.service.business.third.model.LoginUserInfo; |
| | | import com.doumee.service.business.third.model.PageData; |
| | | import com.doumee.service.business.third.model.PageWrap; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface CollectionStationService { |
| | | |
| | | Integer create(CollectionStation station); |
| | | |
| | | void updateById(CollectionStation station); |
| | | |
| | | void deleteById(Integer id, LoginUserInfo user); |
| | | |
| | | void deleteByIdInBatch(List<Integer> ids, LoginUserInfo user); |
| | | |
| | | PageData<CollectionStation> findPage(PageWrap<CollectionStation> pageWrap); |
| | | |
| | | List<CollectionStation> findList(CollectionStation query); |
| | | |
| | | CollectionStation findById(Integer id); |
| | | |
| | | String syncAllStations(); |
| | | |
| | | String syncStationStatus(Integer stationId); |
| | | |
| | | String probeIsapi(Integer stationId); |
| | | |
| | | List<CollectionDockDevice> findDockDevices(Integer stationId); |
| | | |
| | | PageData<CollectionDockDevice> findDockDevicePage(PageWrap<CollectionDockDevice> pageWrap); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.service.business.impl.collection; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.doumee.biz.system.SystemDictDataBiz; |
| | | import com.doumee.core.constants.ResponseStatus; |
| | | import com.doumee.core.exception.BusinessException; |
| | | import com.doumee.core.haikang.isapi.IsapiClient; |
| | | import com.doumee.core.haikang.isapi.IsapiConstants; |
| | | import com.doumee.core.haikang.isapi.model.MediaItemDTO; |
| | | import com.doumee.core.utils.Constants; |
| | | import com.doumee.core.utils.DateUtil; |
| | | import com.doumee.core.utils.FtpUtil; |
| | | import com.doumee.core.utils.VideoTranscodeUtil; |
| | | import com.doumee.dao.admin.request.CollectionMediaSyncRequest; |
| | | import com.doumee.dao.business.CollectionMediaMapper; |
| | | import com.doumee.dao.business.CollectionStationMapper; |
| | | import com.doumee.dao.business.model.CollectionMedia; |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | import com.doumee.service.business.CollectionMediaSyncService; |
| | | import com.doumee.service.business.third.model.PageData; |
| | | import com.doumee.service.business.third.model.PageWrap; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.concurrent.Executor; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | public class CollectionMediaSyncServiceImpl implements CollectionMediaSyncService { |
| | | |
| | | @Autowired |
| | | private CollectionMediaMapper collectionMediaMapper; |
| | | @Autowired |
| | | private CollectionStationMapper collectionStationMapper; |
| | | @Autowired |
| | | private SystemDictDataBiz systemDictDataBiz; |
| | | @Resource(name = "asyncExecutor") |
| | | private Executor asyncExecutor; |
| | | |
| | | private final IsapiClient isapiClient = new IsapiClient(); |
| | | private static FtpUtil ftp; |
| | | |
| | | @Override |
| | | public String syncMediaList(CollectionMediaSyncRequest request) { |
| | | if (Constants.DEALING_HK_COLLECTION_MEDIA) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "åªä½åæ¥ä»»å¡æ£å¨æ§è¡ï¼è¯·ç¨å"); |
| | | } |
| | | Constants.DEALING_HK_COLLECTION_MEDIA = true; |
| | | try { |
| | | Date endTime = request.getEndTime() != null ? request.getEndTime() : new Date(); |
| | | Date startTime = request.getStartTime(); |
| | | if (startTime == null) { |
| | | Calendar cal = Calendar.getInstance(); |
| | | cal.setTime(endTime); |
| | | cal.add(Calendar.DAY_OF_MONTH, -7); |
| | | startTime = cal.getTime(); |
| | | } |
| | | String trackId = getTrackId(); |
| | | |
| | | int totalNew = 0; |
| | | if (request.getStationId() != null) { |
| | | totalNew += syncStationMedia(request.getStationId(), startTime, endTime, trackId); |
| | | } else { |
| | | List<CollectionStation> stations = collectionStationMapper.selectList(new QueryWrapper<CollectionStation>().lambda() |
| | | .eq(CollectionStation::getIsdeleted, Constants.ZERO) |
| | | .eq(CollectionStation::getStatus, Constants.ONE)); |
| | | for (CollectionStation station : stations) { |
| | | try { |
| | | totalNew += syncStationMedia(station.getId(), startTime, endTime, trackId); |
| | | } catch (Exception e) { |
| | | log.error("ééç«åªä½ç´¢å¼åæ¥å¤±è´¥ stationId={}: {}", station.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | return "åæ¥å®æï¼æ°å¢ç´¢å¼ã" + totalNew + "ãæ¡"; |
| | | } finally { |
| | | Constants.DEALING_HK_COLLECTION_MEDIA = false; |
| | | } |
| | | } |
| | | |
| | | private int syncStationMedia(Integer stationId, Date startTime, Date endTime, String trackId) { |
| | | CollectionStation station = collectionStationMapper.selectById(stationId); |
| | | if (station == null || Constants.equalsInteger(station.getIsdeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | List<MediaItemDTO> items = isapiClient.searchMediaAll(station, startTime, endTime, trackId, IsapiConstants.MAX_PAGE_RESULTS); |
| | | log.info("ééç«åªä½æ£ç´¢ stationId={} ip={} range={}~{} track={} found={}", |
| | | stationId, station.getIp(), startTime, endTime, trackId, items.size()); |
| | | int count = 0; |
| | | Date now = new Date(); |
| | | for (MediaItemDTO item : items) { |
| | | if (StringUtils.isBlank(item.getFileIndex())) { |
| | | continue; |
| | | } |
| | | Long exists = collectionMediaMapper.selectCount(new QueryWrapper<CollectionMedia>().lambda() |
| | | .eq(CollectionMedia::getStationId, stationId) |
| | | .eq(CollectionMedia::getFileIndex, item.getFileIndex()) |
| | | .eq(CollectionMedia::getIsdeleted, Constants.ZERO)); |
| | | if (exists != null && exists > 0) { |
| | | continue; |
| | | } |
| | | CollectionMedia media = new CollectionMedia(); |
| | | media.setStationId(stationId); |
| | | media.setFileIndex(item.getFileIndex()); |
| | | media.setTrackId(item.getTrackId()); |
| | | media.setFileName(item.getFileName()); |
| | | media.setPlaybackUri(item.getPlaybackUri()); |
| | | media.setMediaType(item.getMediaType()); |
| | | media.setContentType(item.getContentType()); |
| | | media.setFileSize(item.getFileSize()); |
| | | media.setStartTime(item.getStartTime()); |
| | | media.setEndTime(item.getEndTime()); |
| | | media.setRecorderSn(item.getRecorderSn()); |
| | | media.setUserName(item.getUserName()); |
| | | media.setDownloadStatus(Constants.ZERO); |
| | | media.setCreateDate(now); |
| | | media.setIsdeleted(Constants.ZERO); |
| | | collectionMediaMapper.insert(media); |
| | | count++; |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | @Override |
| | | public String downloadMedia(Integer mediaId) { |
| | | CollectionMedia media = collectionMediaMapper.selectById(mediaId); |
| | | if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | if (Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "æä»¶å·²ä¸è½½ï¼æ ééå¤ä¸è½½"); |
| | | } |
| | | if (Constants.equalsInteger(media.getDownloadStatus(), Constants.COLLECTION_MEDIA_DOWNLOADING)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "æä»¶æ£å¨ä¸è½½ä¸ï¼è¯·ç¨åå·æ°"); |
| | | } |
| | | CollectionStation station = collectionStationMapper.selectById(media.getStationId()); |
| | | if (station == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | CollectionMedia downloading = new CollectionMedia(); |
| | | downloading.setDownloadStatus(Constants.COLLECTION_MEDIA_DOWNLOADING); |
| | | int updated = collectionMediaMapper.update(downloading, new QueryWrapper<CollectionMedia>().lambda() |
| | | .eq(CollectionMedia::getId, mediaId) |
| | | .in(CollectionMedia::getDownloadStatus, Constants.ZERO, 2)); |
| | | if (updated <= 0) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "æ æ³æäº¤ä¸è½½ï¼è¯·å·æ°åéè¯"); |
| | | } |
| | | asyncExecutor.execute(() -> executeDownloadAsync(mediaId)); |
| | | return "å·²æäº¤ä¸è½½ä»»å¡ï¼è¯·ç¨åå·æ°æ¥çç¶æ"; |
| | | } |
| | | |
| | | private void executeDownloadAsync(Integer mediaId) { |
| | | try { |
| | | CollectionMedia media = collectionMediaMapper.selectById(mediaId); |
| | | if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) { |
| | | return; |
| | | } |
| | | CollectionStation station = collectionStationMapper.selectById(media.getStationId()); |
| | | if (station == null) { |
| | | markDownloadFailed(mediaId); |
| | | return; |
| | | } |
| | | String path = downloadToFtp(station, media); |
| | | if (StringUtils.isBlank(path)) { |
| | | markDownloadFailed(mediaId); |
| | | log.error("弿¥ä¸è½½å¤±è´¥ mediaId={}", mediaId); |
| | | return; |
| | | } |
| | | CollectionMedia update = new CollectionMedia(); |
| | | update.setId(mediaId); |
| | | update.setFilePathLocal(path); |
| | | update.setDownloadStatus(Constants.ONE); |
| | | update.setDownloadTime(new Date()); |
| | | collectionMediaMapper.updateById(update); |
| | | log.info("弿¥ä¸è½½æå mediaId={} path={}", mediaId, path); |
| | | } catch (Exception e) { |
| | | markDownloadFailed(mediaId); |
| | | log.error("弿¥ä¸è½½å¼å¸¸ mediaId={}: {}", mediaId, e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | private void markDownloadFailed(Integer mediaId) { |
| | | CollectionMedia fail = new CollectionMedia(); |
| | | fail.setId(mediaId); |
| | | fail.setDownloadStatus(2); |
| | | collectionMediaMapper.updateById(fail); |
| | | } |
| | | |
| | | @Override |
| | | public String batchDownload(CollectionMediaSyncRequest request) { |
| | | int limit = request.getLimit() != null ? request.getLimit() : getBatchSize(); |
| | | QueryWrapper<CollectionMedia> wrapper = new QueryWrapper<>(); |
| | | wrapper.lambda() |
| | | .eq(CollectionMedia::getIsdeleted, Constants.ZERO) |
| | | .eq(CollectionMedia::getDownloadStatus, Constants.ZERO) |
| | | .eq(request.getStationId() != null, CollectionMedia::getStationId, request.getStationId()) |
| | | .orderByAsc(CollectionMedia::getId) |
| | | .last("limit " + limit); |
| | | List<CollectionMedia> list = collectionMediaMapper.selectList(wrapper); |
| | | int submitted = 0; |
| | | int skip = 0; |
| | | for (CollectionMedia media : list) { |
| | | try { |
| | | downloadMedia(media.getId()); |
| | | submitted++; |
| | | } catch (BusinessException e) { |
| | | skip++; |
| | | log.warn("æ¹éä¸è½½è·³è¿ mediaId={}: {}", media.getId(), e.getMessage()); |
| | | } catch (Exception e) { |
| | | skip++; |
| | | log.error("æ¹éä¸è½½æäº¤å¤±è´¥ mediaId={}: {}", media.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | return "å·²æäº¤ä¸è½½ã" + submitted + "ãæ¡ï¼è·³è¿ã" + skip + "ãæ¡"; |
| | | } |
| | | |
| | | @Override |
| | | public PageData<CollectionMedia> findPage(PageWrap<CollectionMedia> pageWrap) { |
| | | CollectionMedia model = pageWrap.getModel() != null ? pageWrap.getModel() : new CollectionMedia(); |
| | | QueryWrapper<CollectionMedia> wrapper = new QueryWrapper<>(); |
| | | wrapper.lambda() |
| | | .eq(CollectionMedia::getIsdeleted, Constants.ZERO) |
| | | .eq(model.getStationId() != null, CollectionMedia::getStationId, model.getStationId()) |
| | | .eq(model.getDownloadStatus() != null, CollectionMedia::getDownloadStatus, model.getDownloadStatus()) |
| | | .eq(model.getMediaType() != null, CollectionMedia::getMediaType, model.getMediaType()) |
| | | .orderByDesc(CollectionMedia::getId); |
| | | IPage<CollectionMedia> page = collectionMediaMapper.selectPage( |
| | | new Page<>(pageWrap.getPage(), pageWrap.getCapacity()), wrapper); |
| | | page.getRecords().forEach(this::fillMediaAccessUrl); |
| | | return PageData.from(page); |
| | | } |
| | | |
| | | @Override |
| | | public void previewMedia(Integer mediaId, HttpServletRequest request, HttpServletResponse response) { |
| | | streamMediaFile(mediaId, request, response, false); |
| | | } |
| | | |
| | | @Override |
| | | public void downloadMediaFile(Integer mediaId, HttpServletRequest request, HttpServletResponse response) { |
| | | streamMediaFile(mediaId, request, response, true); |
| | | } |
| | | |
| | | private void streamMediaFile(Integer mediaId, HttpServletRequest request, HttpServletResponse response, boolean attachment) { |
| | | CollectionMedia media = collectionMediaMapper.selectById(mediaId); |
| | | if (media == null || Constants.equalsInteger(media.getIsdeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | if (!Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE) || StringUtils.isBlank(media.getFilePathLocal())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "æä»¶å°æªä¸è½½ï¼æ æ³è®¿é®"); |
| | | } |
| | | String remotePath = getMediaFolder() + media.getFilePathLocal(); |
| | | FtpUtil ftpClient = null; |
| | | try { |
| | | ftpClient = createFtpClient(); |
| | | if (!ftpClient.connect()) { |
| | | throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "FTPè¿æ¥å¤±è´¥"); |
| | | } |
| | | String contentType = resolvePreviewContentType(media); |
| | | String fileName = StringUtils.defaultIfBlank(media.getFileName(), "media" + resolveExt(media)); |
| | | long fileSize = ftpClient.getRemoteFileSize(remotePath); |
| | | if (fileSize <= 0 && media.getFileSize() != null && media.getFileSize() > 0) { |
| | | fileSize = media.getFileSize(); |
| | | } |
| | | if (fileSize <= 0) { |
| | | log.warn("åªä½æä»¶æ æ³è·åå¤§å° mediaId={} remotePath={}", mediaId, remotePath); |
| | | } |
| | | |
| | | response.setContentType(contentType); |
| | | String disposition = (attachment ? "attachment" : "inline") + ";filename=" |
| | | + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); |
| | | response.setHeader("Content-Disposition", disposition); |
| | | if (attachment) { |
| | | response.setHeader("eva-download-filename", URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())); |
| | | } |
| | | response.setHeader("Accept-Ranges", "bytes"); |
| | | response.setHeader("eva-opera-type", attachment ? "download" : "preview"); |
| | | response.setHeader("Cache-Control", "private, max-age=3600"); |
| | | |
| | | RangeSpec range = fileSize > 0 |
| | | ? parseRangeHeader(request != null ? request.getHeader("Range") : null, fileSize) : null; |
| | | OutputStream out = response.getOutputStream(); |
| | | boolean streamed; |
| | | if (range != null) { |
| | | response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); |
| | | response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + fileSize); |
| | | response.setHeader("Content-Length", String.valueOf(range.length())); |
| | | streamed = ftpClient.streamRemoteFileRange(remotePath, out, range.start, range.length()); |
| | | } else { |
| | | if (fileSize > 0) { |
| | | response.setHeader("Content-Length", String.valueOf(fileSize)); |
| | | } |
| | | streamed = ftpClient.streamRemoteFile(remotePath, out); |
| | | } |
| | | if (!streamed) { |
| | | log.warn("åªä½æä»¶ FTP 读å失败 mediaId={} remotePath={}", mediaId, remotePath); |
| | | if (!response.isCommitted()) { |
| | | response.resetBuffer(); |
| | | response.setStatus(HttpServletResponse.SC_NOT_FOUND); |
| | | response.setContentType("text/plain;charset=UTF-8"); |
| | | out.write("æä»¶è¯»å失败".getBytes(StandardCharsets.UTF_8)); |
| | | } |
| | | return; |
| | | } |
| | | out.flush(); |
| | | } catch (BusinessException e) { |
| | | throw e; |
| | | } catch (Exception e) { |
| | | log.error("åªä½æä»¶è¾åºå¤±è´¥ mediaId={}: {}", mediaId, e.getMessage()); |
| | | if (!response.isCommitted()) { |
| | | try { |
| | | response.resetBuffer(); |
| | | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| | | response.setContentType("text/plain;charset=UTF-8"); |
| | | response.getOutputStream().write("æä»¶è¯»å失败".getBytes(StandardCharsets.UTF_8)); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | } finally { |
| | | try { |
| | | if (ftpClient != null) { |
| | | ftpClient.disconnect(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void fillMediaAccessUrl(CollectionMedia media) { |
| | | if (!Constants.equalsInteger(media.getDownloadStatus(), Constants.ONE) || StringUtils.isBlank(media.getFilePathLocal())) { |
| | | return; |
| | | } |
| | | try { |
| | | String prefix = systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_RESOURCE_PATH).getCode(); |
| | | media.setFileUrlFull(prefix + getMediaFolder() + media.getFilePathLocal()); |
| | | } catch (Exception e) { |
| | | log.warn("æå»ºåªä½è®¿é®URL失败 mediaId={}: {}", media.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private FtpUtil createFtpClient() throws IOException { |
| | | return new FtpUtil( |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_HOST).getCode(), |
| | | Integer.parseInt(systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PORT).getCode()), |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_USERNAME).getCode(), |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PWD).getCode()); |
| | | } |
| | | |
| | | private String resolvePreviewContentType(CollectionMedia media) { |
| | | String name = StringUtils.defaultString(media.getFileName()).toLowerCase(); |
| | | if (name.endsWith(".jpg") || name.endsWith(".jpeg")) { |
| | | return "image/jpeg"; |
| | | } |
| | | if (name.endsWith(".png")) { |
| | | return "image/png"; |
| | | } |
| | | if (name.endsWith(".gif")) { |
| | | return "image/gif"; |
| | | } |
| | | if (name.endsWith(".bmp")) { |
| | | return "image/bmp"; |
| | | } |
| | | if (name.endsWith(".mp3")) { |
| | | return "audio/mpeg"; |
| | | } |
| | | if (name.endsWith(".mp4") || name.endsWith(".m4v")) { |
| | | return "video/mp4"; |
| | | } |
| | | if (name.endsWith(".wav")) { |
| | | return "audio/wav"; |
| | | } |
| | | if (name.endsWith(".txt") || name.endsWith(".log")) { |
| | | return "text/plain;charset=UTF-8"; |
| | | } |
| | | if (media.getMediaType() != null && media.getMediaType() == 1) { |
| | | return "image/jpeg"; |
| | | } |
| | | if (media.getMediaType() != null && media.getMediaType() == 2) { |
| | | return "audio/mpeg"; |
| | | } |
| | | return "video/mp4"; |
| | | } |
| | | |
| | | private String downloadToFtp(CollectionStation station, CollectionMedia media) { |
| | | InputStream is = null; |
| | | File tempSource = null; |
| | | File tempTarget = null; |
| | | try { |
| | | is = isapiClient.downloadMedia(station, media.getPlaybackUri(), media.getFileIndex(), |
| | | media.getFileName(), media.getTrackId(), media.getStartTime(), media.getEndTime(), |
| | | media.getFileSize(), media.getMediaType()); |
| | | if (is == null) { |
| | | log.error("ISAPIä¸è½½æ ååº mediaId={} mediaID={} fileName={}", |
| | | media.getId(), media.getFileIndex(), media.getFileName()); |
| | | return null; |
| | | } |
| | | tempSource = File.createTempFile("hk_media_src_", resolveTempSuffix(media)); |
| | | Files.copy(is, tempSource.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| | | is.close(); |
| | | is = null; |
| | | |
| | | if (!validateDownloadFile(tempSource, media)) { |
| | | log.error("ISAPIä¸è½½å
å®¹æ æ mediaId={} fileName={} size={}", media.getId(), media.getFileName(), tempSource.length()); |
| | | return null; |
| | | } |
| | | |
| | | File uploadFile = tempSource; |
| | | String ext = resolveUploadExt(media, tempSource); |
| | | if (shouldTranscodeMp4(media, tempSource)) { |
| | | tempTarget = File.createTempFile("hk_media_out_", ".mp4"); |
| | | log.info("å¼å§ MP4 è§é¢è½¬ç mediaId={} fileName={} size={}", |
| | | media.getId(), media.getFileName(), tempSource.length()); |
| | | if (VideoTranscodeUtil.transcodeToBrowserMp4(getFfmpegPath(), tempSource, tempTarget)) { |
| | | uploadFile = tempTarget; |
| | | ext = ".mp4"; |
| | | } else { |
| | | log.warn("MP4 è§é¢è½¬ç æªæåï¼ä¸ä¼ åå§æä»¶ mediaId={} fileName={} size={}", |
| | | media.getId(), media.getFileName(), tempSource.length()); |
| | | uploadFile = tempSource; |
| | | ext = resolveUploadExt(media, tempSource); |
| | | } |
| | | } |
| | | |
| | | if (ftp == null) { |
| | | ftp = new FtpUtil( |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_HOST).getCode(), |
| | | Integer.parseInt(systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PORT).getCode()), |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_USERNAME).getCode(), |
| | | systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_PWD).getCode()); |
| | | } else { |
| | | ftp.connect(); |
| | | } |
| | | String folder = getMediaFolder(); |
| | | String date = DateUtil.getNowShortDate(); |
| | | String fName = date + "/" + UUID.randomUUID() + ext; |
| | | String fileName = folder + fName; |
| | | try (InputStream uploadStream = new FileInputStream(uploadFile)) { |
| | | boolean uploaded = ftp.uploadInputstream(uploadStream, fileName); |
| | | if (uploaded) { |
| | | log.info("ééç«åªä½ä¸ä¼ FTPæå stationId={} file={} size={}", station.getId(), fName, uploadFile.length()); |
| | | return fName; |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("ééç«åªä½ä¸è½½ä¸ä¼ 失败 mediaId={} fileName={}: {}", media.getId(), media.getFileName(), e.getMessage(), e); |
| | | } finally { |
| | | if (is != null) { |
| | | try { |
| | | is.close(); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | deleteQuietly(tempSource); |
| | | deleteQuietly(tempTarget); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String resolveTempSuffix(CollectionMedia media) { |
| | | if (StringUtils.isNotBlank(media.getFileName()) && media.getFileName().contains(".")) { |
| | | return media.getFileName().substring(media.getFileName().lastIndexOf('.')).toLowerCase(); |
| | | } |
| | | return resolveExt(media); |
| | | } |
| | | |
| | | /** ä»
æ©å±å为 .mp4 çè§é¢èµ° FFmpeg 转ç ï¼æµ·åº·å¸¸è§ .mp4 å
为 MPEG-PSï¼ä»å¨æ¤å¤çï¼ */ |
| | | private boolean shouldTranscodeMp4(CollectionMedia media, File sourceFile) { |
| | | return isVideoMedia(media) && isMp4FileName(media.getFileName()); |
| | | } |
| | | |
| | | private static boolean isMp4FileName(String fileName) { |
| | | if (StringUtils.isBlank(fileName)) { |
| | | return false; |
| | | } |
| | | String lower = fileName.toLowerCase(); |
| | | return lower.endsWith(".mp4"); |
| | | } |
| | | |
| | | private String resolveUploadExt(CollectionMedia media, File sourceFile) { |
| | | if (StringUtils.isNotBlank(media.getFileName()) && media.getFileName().contains(".")) { |
| | | return media.getFileName().substring(media.getFileName().lastIndexOf('.')).toLowerCase(); |
| | | } |
| | | return resolveExt(media); |
| | | } |
| | | |
| | | private void deleteQuietly(File file) { |
| | | if (file != null && file.exists() && !file.delete()) { |
| | | log.warn("ä¸´æ¶æä»¶å é¤å¤±è´¥: {}", file.getAbsolutePath()); |
| | | } |
| | | } |
| | | |
| | | private String getFfmpegPath() { |
| | | try { |
| | | return systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_FFMPEG_PATH).getCode(); |
| | | } catch (Exception e) { |
| | | return "ffmpeg"; |
| | | } |
| | | } |
| | | |
| | | private boolean validateDownloadFile(File file, CollectionMedia media) throws IOException { |
| | | if (file.length() <= 0) { |
| | | log.warn("ISAPIä¸è½½å
容为空 mediaId={}", media.getId()); |
| | | return false; |
| | | } |
| | | byte[] head = new byte[4096]; |
| | | int n; |
| | | try (InputStream in = new FileInputStream(file)) { |
| | | n = in.read(head); |
| | | } |
| | | if (n <= 0) { |
| | | log.warn("ISAPIä¸è½½å
容为空 mediaId={}", media.getId()); |
| | | return false; |
| | | } |
| | | if (isErrorPayload(head, n)) { |
| | | log.error("ISAPIä¸è½½è¿åé误 mediaId={} snippet={}", media.getId(), |
| | | new String(head, 0, Math.min(n, 200), StandardCharsets.UTF_8)); |
| | | return false; |
| | | } |
| | | if (isVideoMedia(media) && isMpegPs(head, n)) { |
| | | log.info("æ£æµå°MPEG-PSè§é¢æµ mediaId={}ï¼é MP4ï¼ä¸è½¬ç ï¼", media.getId()); |
| | | } |
| | | if (isVideoMedia(media) && !isLikelyPlayableVideo(head, n) && !isMp4FileName(media.getFileName())) { |
| | | log.info("ä¸è½½å
容é MP4 è§é¢ mediaId={} fileName={}ï¼ä¸è½¬ç ï¼", media.getId(), media.getFileName()); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private String getMediaFolder() { |
| | | try { |
| | | return systemDictDataBiz.queryByCode(Constants.FTP, Constants.COLLECTION_MEDIA_FOLDER).getCode(); |
| | | } catch (Exception e) { |
| | | return "/collection_media/"; |
| | | } |
| | | } |
| | | |
| | | private String resolveExt(CollectionMedia media) { |
| | | if (StringUtils.isNotBlank(media.getFileName())) { |
| | | String lower = media.getFileName().toLowerCase(); |
| | | if (lower.endsWith(".txt") || lower.endsWith(".log")) { |
| | | return lower.substring(lower.lastIndexOf('.')); |
| | | } |
| | | if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png")) { |
| | | return lower.substring(lower.lastIndexOf('.')); |
| | | } |
| | | if (lower.endsWith(".mp3") || lower.endsWith(".wav")) { |
| | | return lower.substring(lower.lastIndexOf('.')); |
| | | } |
| | | if (lower.contains(".")) { |
| | | return lower.substring(lower.lastIndexOf('.')); |
| | | } |
| | | } |
| | | if (media.getMediaType() != null && media.getMediaType() == 1) { |
| | | return ".jpg"; |
| | | } |
| | | if (media.getMediaType() != null && media.getMediaType() == 2) { |
| | | return ".mp3"; |
| | | } |
| | | return ".dat"; |
| | | } |
| | | |
| | | private String getTrackId() { |
| | | try { |
| | | String val = systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_SEARCH_TRACK_ID).getCode(); |
| | | if (StringUtils.isBlank(val) || "auto".equalsIgnoreCase(val.trim()) || "*".equals(val.trim())) { |
| | | return null; |
| | | } |
| | | return val.trim(); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private int getBatchSize() { |
| | | try { |
| | | return Integer.parseInt(systemDictDataBiz.queryByCode(Constants.CS_PARAM, Constants.CS_DOWNLOAD_BATCH_SIZE).getCode()); |
| | | } catch (Exception e) { |
| | | return 10; |
| | | } |
| | | } |
| | | |
| | | private static boolean isVideoMedia(CollectionMedia media) { |
| | | if (media.getMediaType() != null) { |
| | | return media.getMediaType() == 0; |
| | | } |
| | | String name = StringUtils.defaultString(media.getFileName()).toLowerCase(); |
| | | return name.endsWith(".mp4") || name.endsWith(".mov") || name.endsWith(".avi") |
| | | || name.endsWith(".mkv") || name.endsWith(".m4v"); |
| | | } |
| | | |
| | | private static boolean isErrorPayload(byte[] head, int n) { |
| | | String snippet = new String(head, 0, Math.min(n, 64), StandardCharsets.UTF_8).trim(); |
| | | return snippet.startsWith("<?xml") || snippet.startsWith("{") |
| | | || (snippet.startsWith("<") && snippet.contains("ResponseStatus")); |
| | | } |
| | | |
| | | private static boolean isMpegPs(byte[] head, int n) { |
| | | return n >= 4 && head[0] == 0 && head[1] == 0 && head[2] == 1 && (head[3] & 0xFF) == 0xBA; |
| | | } |
| | | |
| | | private static boolean isLikelyPlayableVideo(byte[] head, int n) { |
| | | if (n >= 8 && head[4] == 'f' && head[5] == 't' && head[6] == 'y' && head[7] == 'p') { |
| | | return true; |
| | | } |
| | | return n >= 12 && head[0] == 'R' && head[1] == 'I' && head[2] == 'F' && head[3] == 'F' |
| | | && head[8] == 'A' && head[9] == 'V' && head[10] == 'I'; |
| | | } |
| | | |
| | | private static RangeSpec parseRangeHeader(String rangeHeader, long fileSize) { |
| | | if (StringUtils.isBlank(rangeHeader) || fileSize <= 0 || !rangeHeader.startsWith("bytes=")) { |
| | | return null; |
| | | } |
| | | String spec = rangeHeader.substring("bytes=".length()).trim(); |
| | | int dash = spec.indexOf('-'); |
| | | if (dash < 0) { |
| | | return null; |
| | | } |
| | | try { |
| | | long start = Long.parseLong(spec.substring(0, dash)); |
| | | String endPart = spec.substring(dash + 1).trim(); |
| | | long end = endPart.isEmpty() ? fileSize - 1 : Long.parseLong(endPart); |
| | | if (start < 0 || end < start || start >= fileSize) { |
| | | return null; |
| | | } |
| | | if (end >= fileSize) { |
| | | end = fileSize - 1; |
| | | } |
| | | return new RangeSpec(start, end); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static final class RangeSpec { |
| | | private final long start; |
| | | private final long end; |
| | | |
| | | private RangeSpec(long start, long end) { |
| | | this.start = start; |
| | | this.end = end; |
| | | } |
| | | |
| | | private long length() { |
| | | return end - start + 1; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.doumee.service.business.impl.collection; |
| | | |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.doumee.core.constants.ResponseStatus; |
| | | |
| | | import com.doumee.core.exception.BusinessException; |
| | | |
| | | import com.doumee.core.haikang.isapi.IsapiClient; |
| | | |
| | | import com.doumee.core.haikang.isapi.IsapiConstants; |
| | | |
| | | import com.doumee.core.haikang.isapi.model.DeviceInfoDTO; |
| | | |
| | | import com.doumee.core.haikang.isapi.model.DockDeviceDTO; |
| | | |
| | | import com.doumee.core.haikang.isapi.model.DockStationBasicInfoDTO; |
| | | |
| | | import com.doumee.core.haikang.isapi.model.StorageInfoDTO; |
| | | |
| | | import com.doumee.core.utils.Constants; |
| | | |
| | | import com.doumee.core.utils.IsapiHttpUtil; |
| | | |
| | | import com.doumee.dao.business.CollectionDockDeviceMapper; |
| | | |
| | | import com.doumee.dao.business.CollectionStationMapper; |
| | | |
| | | import com.doumee.dao.business.model.CollectionDockDevice; |
| | | |
| | | import com.doumee.dao.business.model.CollectionStation; |
| | | |
| | | import com.doumee.service.business.CollectionStationService; |
| | | |
| | | import com.doumee.service.business.third.model.LoginUserInfo; |
| | | |
| | | import com.doumee.service.business.third.model.PageData; |
| | | |
| | | import com.doumee.service.business.third.model.PageWrap; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | |
| | | |
| | | import java.util.Date; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | |
| | | |
| | | @Service |
| | | |
| | | @Slf4j |
| | | |
| | | public class CollectionStationServiceImpl implements CollectionStationService { |
| | | |
| | | |
| | | |
| | | @Autowired |
| | | |
| | | private CollectionStationMapper collectionStationMapper; |
| | | |
| | | @Autowired |
| | | |
| | | private CollectionDockDeviceMapper collectionDockDeviceMapper; |
| | | |
| | | private final IsapiClient isapiClient = new IsapiClient(); |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | |
| | | public Integer create(CollectionStation station) { |
| | | |
| | | validateStation(station); |
| | | |
| | | Date now = new Date(); |
| | | |
| | | station.setCreateDate(now); |
| | | |
| | | station.setEditDate(now); |
| | | |
| | | station.setIsdeleted(Constants.ZERO); |
| | | |
| | | if (station.getStatus() == null) { |
| | | |
| | | station.setStatus(Constants.ONE); |
| | | |
| | | } |
| | | |
| | | if (station.getPort() == null) { |
| | | |
| | | station.setPort(defaultPort(station)); |
| | | |
| | | } |
| | | |
| | | if (station.getUseHttps() == null) { |
| | | |
| | | station.setUseHttps(Constants.ZERO); |
| | | |
| | | } |
| | | |
| | | if (StringUtils.isBlank(station.getModel())) { |
| | | |
| | | station.setModel("UD39625B"); |
| | | |
| | | } |
| | | |
| | | if (station.getLoginUserInfo() != null) { |
| | | |
| | | station.setCreator(station.getLoginUserInfo().getUsername()); |
| | | |
| | | station.setEdirot(station.getLoginUserInfo().getUsername()); |
| | | |
| | | } |
| | | |
| | | collectionStationMapper.insert(station); |
| | | |
| | | return station.getId(); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | |
| | | public void updateById(CollectionStation station) { |
| | | |
| | | if (station.getId() == null) { |
| | | |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST); |
| | | |
| | | } |
| | | |
| | | station.setEditDate(new Date()); |
| | | |
| | | if (station.getLoginUserInfo() != null) { |
| | | |
| | | station.setEdirot(station.getLoginUserInfo().getUsername()); |
| | | |
| | | } |
| | | |
| | | collectionStationMapper.updateById(station); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | |
| | | public void deleteById(Integer id, LoginUserInfo user) { |
| | | |
| | | CollectionStation station = new CollectionStation(); |
| | | |
| | | station.setId(id); |
| | | |
| | | station.setIsdeleted(Constants.ONE); |
| | | |
| | | station.setEditDate(new Date()); |
| | | |
| | | if (user != null) { |
| | | |
| | | station.setEdirot(user.getUsername()); |
| | | |
| | | } |
| | | |
| | | collectionStationMapper.updateById(station); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | |
| | | public void deleteByIdInBatch(List<Integer> ids, LoginUserInfo user) { |
| | | |
| | | if (ids == null) { |
| | | |
| | | return; |
| | | |
| | | } |
| | | |
| | | for (Integer id : ids) { |
| | | |
| | | deleteById(id, user); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public PageData<CollectionStation> findPage(PageWrap<CollectionStation> pageWrap) { |
| | | |
| | | CollectionStation model = pageWrap.getModel() != null ? pageWrap.getModel() : new CollectionStation(); |
| | | |
| | | QueryWrapper<CollectionStation> wrapper = buildQueryWrapper(model); |
| | | |
| | | IPage<CollectionStation> page = collectionStationMapper.selectPage( |
| | | new Page<>(pageWrap.getPage(), pageWrap.getCapacity()), wrapper); |
| | | return PageData.from(page); |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public List<CollectionStation> findList(CollectionStation query) { |
| | | |
| | | return collectionStationMapper.selectList(buildQueryWrapper(query != null ? query : new CollectionStation())); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public CollectionStation findById(Integer id) { |
| | | |
| | | CollectionStation station = collectionStationMapper.selectById(id); |
| | | |
| | | if (station == null || Constants.equalsInteger(station.getIsdeleted(), Constants.ONE)) { |
| | | |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | |
| | | } |
| | | |
| | | return station; |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public String syncAllStations() { |
| | | |
| | | if (Constants.DEALING_HK_COLLECTION_STATION) { |
| | | |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "ééç«åæ¥ä»»å¡æ£å¨æ§è¡ï¼è¯·ç¨å"); |
| | | |
| | | } |
| | | |
| | | Constants.DEALING_HK_COLLECTION_STATION = true; |
| | | |
| | | try { |
| | | |
| | | List<CollectionStation> list = collectionStationMapper.selectList(new QueryWrapper<CollectionStation>().lambda() |
| | | |
| | | .eq(CollectionStation::getIsdeleted, Constants.ZERO) |
| | | |
| | | .eq(CollectionStation::getStatus, Constants.ONE)); |
| | | |
| | | int success = 0; |
| | | |
| | | int fail = 0; |
| | | |
| | | for (CollectionStation station : list) { |
| | | |
| | | try { |
| | | |
| | | syncStationStatusInternal(station); |
| | | |
| | | success++; |
| | | |
| | | } catch (Exception e) { |
| | | |
| | | fail++; |
| | | |
| | | log.error("ééç«åæ¥å¤±è´¥ id={}: {}", station.getId(), e.getMessage()); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | return "åæ¥å®æï¼æåã" + success + "ãå°ï¼å¤±è´¥ã" + fail + "ãå°"; |
| | | |
| | | } finally { |
| | | |
| | | Constants.DEALING_HK_COLLECTION_STATION = false; |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public String syncStationStatus(Integer stationId) { |
| | | |
| | | CollectionStation station = findById(stationId); |
| | | |
| | | syncStationStatusInternal(station); |
| | | |
| | | return "忥æå"; |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public String probeIsapi(Integer stationId) { |
| | | |
| | | CollectionStation station = findById(stationId); |
| | | |
| | | boolean https = station.getUseHttps() != null && station.getUseHttps() == 1; |
| | | |
| | | String deviceInfo = IsapiHttpUtil.doGet(station.getIp(), station.getPort(), https, |
| | | |
| | | station.getUsername(), station.getPassword(), IsapiConstants.DEVICE_INFO); |
| | | |
| | | String storage = IsapiHttpUtil.doGet(station.getIp(), station.getPort(), https, |
| | | |
| | | station.getUsername(), station.getPassword(), IsapiConstants.STORAGE); |
| | | |
| | | String tracks = IsapiHttpUtil.doGet(station.getIp(), station.getPort(), https, |
| | | |
| | | station.getUsername(), station.getPassword(), IsapiConstants.RECORD_TRACKS); |
| | | |
| | | String dockBasic = IsapiHttpUtil.doGet(station.getIp(), station.getPort(), https, |
| | | |
| | | station.getUsername(), station.getPassword(), IsapiConstants.DOCK_BASIC_INFO); |
| | | |
| | | String dockDevices = IsapiHttpUtil.doGet(station.getIp(), station.getPort(), https, |
| | | |
| | | station.getUsername(), station.getPassword(), IsapiConstants.DOCK_DEVICE_MANAGEMENT); |
| | | |
| | | return "deviceInfo:\n" + StringUtils.defaultString(deviceInfo) |
| | | |
| | | + "\n\nstorage:\n" + StringUtils.defaultString(storage) |
| | | |
| | | + "\n\nrecord/tracks:\n" + StringUtils.defaultString(tracks) |
| | | |
| | | + "\n\ndockStation/basicInfo:\n" + StringUtils.defaultString(dockBasic) |
| | | |
| | | + "\n\ndockStation/deviceManagement:\n" + StringUtils.defaultString(dockDevices); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | |
| | | public List<CollectionDockDevice> findDockDevices(Integer stationId) { |
| | | |
| | | findById(stationId); |
| | | |
| | | return collectionDockDeviceMapper.selectList(new QueryWrapper<CollectionDockDevice>().lambda() |
| | | |
| | | .eq(CollectionDockDevice::getStationId, stationId) |
| | | |
| | | .eq(CollectionDockDevice::getIsdeleted, Constants.ZERO) |
| | | |
| | | .orderByAsc(CollectionDockDevice::getId)); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public PageData<CollectionDockDevice> findDockDevicePage(PageWrap<CollectionDockDevice> pageWrap) { |
| | | CollectionDockDevice model = pageWrap.getModel() != null ? pageWrap.getModel() : new CollectionDockDevice(); |
| | | QueryWrapper<CollectionDockDevice> wrapper = new QueryWrapper<>(); |
| | | wrapper.lambda() |
| | | .eq(CollectionDockDevice::getIsdeleted, Constants.ZERO) |
| | | .eq(model.getStationId() != null, CollectionDockDevice::getStationId, model.getStationId()) |
| | | .like(StringUtils.isNotBlank(model.getDeviceName()), CollectionDockDevice::getDeviceName, model.getDeviceName()) |
| | | .like(StringUtils.isNotBlank(model.getShortSerialNumber()), CollectionDockDevice::getShortSerialNumber, model.getShortSerialNumber()) |
| | | .orderByDesc(CollectionDockDevice::getId); |
| | | IPage<CollectionDockDevice> page = collectionDockDeviceMapper.selectPage( |
| | | new Page<>(pageWrap.getPage(), pageWrap.getCapacity()), wrapper); |
| | | fillStationName(page.getRecords()); |
| | | return PageData.from(page); |
| | | } |
| | | |
| | | private void fillStationName(List<CollectionDockDevice> records) { |
| | | if (records == null || records.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Integer> stationIds = records.stream() |
| | | .map(CollectionDockDevice::getStationId) |
| | | .filter(id -> id != null) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (stationIds.isEmpty()) { |
| | | return; |
| | | } |
| | | List<CollectionStation> stations = collectionStationMapper.selectBatchIds(stationIds); |
| | | Map<Integer, String> nameMap = stations.stream() |
| | | .collect(Collectors.toMap(CollectionStation::getId, CollectionStation::getName, (a, b) -> a)); |
| | | records.forEach(row -> row.setStationName(nameMap.get(row.getStationId()))); |
| | | } |
| | | |
| | | |
| | | |
| | | private void syncStationStatusInternal(CollectionStation station) { |
| | | |
| | | Date now = new Date(); |
| | | |
| | | CollectionStation update = new CollectionStation(); |
| | | |
| | | update.setId(station.getId()); |
| | | |
| | | update.setLastSyncTime(now); |
| | | |
| | | update.setEditDate(now); |
| | | |
| | | |
| | | |
| | | boolean online = isapiClient.isOnline(station); |
| | | |
| | | update.setOnline(online ? Constants.ONE : Constants.ZERO); |
| | | |
| | | |
| | | |
| | | if (online) { |
| | | |
| | | DeviceInfoDTO deviceInfo = isapiClient.getDeviceInfo(station); |
| | | |
| | | if (deviceInfo != null) { |
| | | |
| | | if (StringUtils.isNotBlank(deviceInfo.getSerialNumber())) { |
| | | |
| | | update.setSerialNo(deviceInfo.getSerialNumber()); |
| | | |
| | | } |
| | | |
| | | if (StringUtils.isNotBlank(deviceInfo.getFirmwareVersion())) { |
| | | |
| | | update.setSoftwareVersion(deviceInfo.getFirmwareVersion()); |
| | | |
| | | } |
| | | |
| | | if (StringUtils.isBlank(station.getName()) && StringUtils.isNotBlank(deviceInfo.getDeviceName())) { |
| | | |
| | | update.setName(deviceInfo.getDeviceName()); |
| | | |
| | | } |
| | | |
| | | if (StringUtils.isNotBlank(deviceInfo.getModel())) { |
| | | |
| | | update.setModel(deviceInfo.getModel()); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | DockStationBasicInfoDTO basicInfo = isapiClient.getDockBasicInfo(station); |
| | | |
| | | if (basicInfo != null && StringUtils.isNotBlank(basicInfo.getDockStationId()) |
| | | |
| | | && StringUtils.isBlank(station.getSerialNo())) { |
| | | |
| | | update.setSerialNo(basicInfo.getDockStationId()); |
| | | |
| | | } |
| | | |
| | | StorageInfoDTO storage = isapiClient.getStorageInfo(station); |
| | | |
| | | if (storage != null) { |
| | | |
| | | update.setTotalSpace(storage.getTotalSpaceGb()); |
| | | |
| | | update.setFreeSpace(storage.getFreeSpaceGb()); |
| | | |
| | | } |
| | | |
| | | syncDockDevices(station.getId(), now); |
| | | |
| | | } |
| | | |
| | | collectionStationMapper.updateById(update); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | private void syncDockDevices(Integer stationId, Date now) { |
| | | |
| | | CollectionStation station = collectionStationMapper.selectById(stationId); |
| | | |
| | | if (station == null) { |
| | | |
| | | return; |
| | | |
| | | } |
| | | |
| | | List<DockDeviceDTO> devices = isapiClient.getDockDevices(station); |
| | | |
| | | for (DockDeviceDTO src : devices) { |
| | | |
| | | if (StringUtils.isBlank(src.getDeviceId())) { |
| | | |
| | | continue; |
| | | |
| | | } |
| | | |
| | | CollectionDockDevice existed = collectionDockDeviceMapper.selectOne(new QueryWrapper<CollectionDockDevice>().lambda() |
| | | |
| | | .eq(CollectionDockDevice::getStationId, stationId) |
| | | |
| | | .eq(CollectionDockDevice::getDeviceId, src.getDeviceId()) |
| | | |
| | | .eq(CollectionDockDevice::getIsdeleted, Constants.ZERO)); |
| | | |
| | | if (existed == null) { |
| | | |
| | | CollectionDockDevice row = new CollectionDockDevice(); |
| | | |
| | | row.setStationId(stationId); |
| | | |
| | | row.setDeviceId(src.getDeviceId()); |
| | | |
| | | row.setDeviceName(src.getDeviceName()); |
| | | |
| | | row.setShortSerialNumber(src.getShortSerialNumber()); |
| | | |
| | | row.setAccessDeviceId(src.getAccessDeviceId()); |
| | | |
| | | row.setNetworkedDevice(Boolean.TRUE.equals(src.getNetworkedDevice()) ? Constants.ONE : Constants.ZERO); |
| | | |
| | | row.setLastSyncTime(now); |
| | | |
| | | row.setCreateDate(now); |
| | | |
| | | row.setIsdeleted(Constants.ZERO); |
| | | |
| | | collectionDockDeviceMapper.insert(row); |
| | | |
| | | } else { |
| | | |
| | | CollectionDockDevice row = new CollectionDockDevice(); |
| | | |
| | | row.setId(existed.getId()); |
| | | |
| | | row.setDeviceName(src.getDeviceName()); |
| | | |
| | | row.setShortSerialNumber(src.getShortSerialNumber()); |
| | | |
| | | row.setAccessDeviceId(src.getAccessDeviceId()); |
| | | |
| | | row.setNetworkedDevice(Boolean.TRUE.equals(src.getNetworkedDevice()) ? Constants.ONE : Constants.ZERO); |
| | | |
| | | row.setLastSyncTime(now); |
| | | |
| | | collectionDockDeviceMapper.updateById(row); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | private QueryWrapper<CollectionStation> buildQueryWrapper(CollectionStation model) { |
| | | |
| | | QueryWrapper<CollectionStation> wrapper = new QueryWrapper<>(); |
| | | |
| | | wrapper.lambda() |
| | | |
| | | .eq(CollectionStation::getIsdeleted, Constants.ZERO) |
| | | |
| | | .like(StringUtils.isNotBlank(model.getName()), CollectionStation::getName, model.getName()) |
| | | |
| | | .like(StringUtils.isNotBlank(model.getIp()), CollectionStation::getIp, model.getIp()) |
| | | |
| | | .eq(model.getOnline() != null, CollectionStation::getOnline, model.getOnline()) |
| | | |
| | | .eq(model.getStatus() != null, CollectionStation::getStatus, model.getStatus()) |
| | | |
| | | .orderByDesc(CollectionStation::getId); |
| | | |
| | | return wrapper; |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | private void validateStation(CollectionStation station) { |
| | | |
| | | if (StringUtils.isBlank(station.getIp()) || StringUtils.isBlank(station.getUsername()) |
| | | |
| | | || StringUtils.isBlank(station.getPassword())) { |
| | | |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "IPãç¨æ·åãå¯ç ä¸è½ä¸ºç©º"); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | private int defaultPort(CollectionStation station) { |
| | | |
| | | if (station.getUseHttps() != null && station.getUseHttps() == 1) { |
| | | |
| | | return 443; |
| | | |
| | | } |
| | | |
| | | return 80; |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | ########################åæ¥æ°æ®æ¨¡å¼ ######################## |
| | | data-sync: |
| | | org-user-data-origin: 3 #ç»ç»æ°æ® 0èªå»º 2以海康为主 1åæERPç³»ç» 3ç®éäº 4éé |
| | | visitor-data-origin: 2 #è®¿å®¢æ°æ® 0èªå»º 2以海康为主 1åæERPç³»ç» 2ç®éäº |
| | | org-user-data-origin: 0 #ç»ç»æ°æ® 0èªå»º 2以海康为主 1åæERPç³»ç» |
| | | visitor-data-origin: 0 #è®¿å®¢æ°æ® 0èªå»º 2以海康为主 1åæERPç³»ç» |
| | | need-deal-img: true #æ¯å¦éè¦å¤çå¾çæ°æ® |
| | | |
| | | |