| | |
| | | @Autowired |
| | | private InfluxDBClient influxDBClient; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 写入数据 |
| | | * |
| | |
| | | |
| | | Job getJobByBarcodeAndJobSts(String barcode, Integer jobSts); |
| | | |
| | | Job getJobByBarcode(String barcode); |
| | | Job getJobByBarcode(String barcode, Integer jobSts); |
| | | |
| | | Job getJobByJobNo(Integer jobNo); |
| | | |
| | |
| | | if (de != null) { |
| | | de.setSqlData(devp); |
| | | baseMapper.updateById(de); |
| | | }else { |
| | | baseMapper.insert(devp); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Job getJobByBarcode(String barcode) { |
| | | return baseMapper.getJobByBarcode(barcode); |
| | | public Job getJobByBarcode(String barcode, Integer jobSts) { |
| | | return baseMapper.getJobByBarcodeAndJobSts(barcode,jobSts); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | String s= "{\"msg\":\"Success\",\"code\":200,\"data\":{\"locNo\":\"A102400201\",\"batchNo\":\"TK2603104428\",\"taskNo\":\"TK2603104428\"}}"; |
| | | //System.out.println(JSON.parseObject(s).getString("data")); |
| | | ApplyInRepsonseDto applyInRepsonseDto = JSONObject.parseObject(JSON.parseObject(s).getString("data"), ApplyInRepsonseDto.class); |
| | | System.out.println(applyInRepsonseDto); |
| | | } |
| | | } |
| | |
| | | |
| | | do { |
| | | workNo = workNo >= eNo ? sNo : workNo + 1; |
| | | } while (jobService.getJobByJobNo(workNo) == null); |
| | | } while (jobService.getJobByJobNo(workNo) != null); |
| | | |
| | | if (workNo > 0) { |
| | | wrkLastno.setWrkNo(workNo); |
| | |
| | | } |
| | | // 9991是空板,9992是满板 |
| | | if (staProtocol.getWorkNo() >= 9991 && staProtocol.getWorkNo() <= 9992) { |
| | | Job job = jobService.getJobByBarcode(barcode); |
| | | Job job = jobService.getJobByBarcode(barcode ,ConveyorStateType.INBOUND.getStatus()); |
| | | // 申请入库 |
| | | if (job == null || (job != null && job.getJobSts() == ConveyorStateType.CLEARSIGNAL.getStatus())) { |
| | | ApplyInRepsonseDto locOfWms = wmsMainService.getLocOfWms(applyIn(barcode, inSta.getStaNo() + "", staProtocol)); |
| | | if (locOfWms != null) { |
| | | staProtocol.setWorkNo(job.getJobNo()); |
| | | Integer workNo = getWorkNo(); |
| | | staProtocol.setWorkNo(workNo); |
| | | staProtocol.setStaNo(inSta.getTargetSta()); |
| | | if (MessageQueue.offer(SlaveType.Devp, devp.getId(), new Task(TaskType.WRITE, staProtocol))) { |
| | | if (!jobService.insert(initJob(locOfWms, barcode, inSta.getTargetSta() + ""))) { |
| | | if (!jobService.insert(initJob(locOfWms, barcode, workNo,inSta.getTargetSta() + ""))) { |
| | | throw new CoolException("更新输送线任务失败," + " - " + staProtocol.getWorkNo()); |
| | | } |
| | | log.info("入库前进:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta()); |
| | | }else { |
| | | log.info("下发失败:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta()); |
| | | } |
| | | } else { |
| | | log.info("WMS未返回库位信息,条码:{},站点:{}", barcode, inSta.getStaNo()); |
| | |
| | | } |
| | | } |
| | | |
| | | private Job initJob(ApplyInRepsonseDto locOfWms, String barcode, String staNo) { |
| | | |
| | | private Job initJob(ApplyInRepsonseDto locOfWms, String barcode,Integer workNo, String staNo) { |
| | | Job job = new Job(); |
| | | job.setLoc(locOfWms.getLocNo()); |
| | | job.setTaskNo(locOfWms.getTaskNo()); |
| | | job.setBatchNo(locOfWms.getBatchNo()); |
| | | job.setBarcode(barcode); |
| | | job.setStaNo(staNo); |
| | | job.setJobNo(getWorkNo()); |
| | | job.setJobNo(workNo); |
| | | job.setJobSts(ConveyorStateType.INBOUND.getStatus()); |
| | | job.setWmsTime(new Date()); |
| | | return job; |
| | |
| | | return; |
| | | } |
| | | if (staProtocol.getWorkNo() > 0 && staProtocol.isAutoing()) { |
| | | Job jobByWorkNo = jobService.getJobByJobNo(staProtocol.getWorkNo()); |
| | | Job jobByWorkNo = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.OUTBOUND.getStatus()); |
| | | if (jobByWorkNo != null && jobByWorkNo.getJobSts() == ConveyorStateType.OUTBOUND.getStatus()) { |
| | | staProtocol.setWorkNo(9992); |
| | | staProtocol.setStaNo(1005); |
| | |
| | | continue; |
| | | } |
| | | if (staProtocol.isAutoing()) { |
| | | Job job = jobService.getJobByJobNo(staProtocol.getWorkNo()); |
| | | if (job != null && job.getJobSts() == ConveyorStateType.INBOUND.getStatus()) { |
| | | Job job = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.INBOUND.getStatus()); |
| | | if (job != null ) { |
| | | if (ctuMainService.sendTask(process(job))) { |
| | | job.setJobSts(ConveyorStateType.SENDTASK.getStatus()); |
| | | job.setRcsTime(new Date()); |
| | |
| | | datasource: |
| | | driver-class-name: com.mysql.jdbc.Driver |
| | | #url: jdbc:mysql://127.0.0.1:3306/rcs_ctu_stable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai |
| | | url: jdbc:mysql://192.168.133.173:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai |
| | | url: jdbc:mysql://127.0.0.1:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai |
| | | username: root |
| | | password: xltys1995 |
| | | mvc: |
| | |
| | | |
| | | logging: |
| | | file: |
| | | path: stock/out/cv/logs |
| | | path: /stock/out/cv/logs |
| | | |
| | | wms: |
| | | url: 10.10.10.220:8081 |
| | |
| | | select * |
| | | from cv_job |
| | | where barcode = #{barcode} |
| | | and job_sts = #{jobSts} |
| | | order by id desc limit 1 |
| | | </select> |
| | | |
| | |
| | | </div> |
| | | <div class="modal-footer"> |
| | | <button type="button" class="btn btn-primary" id="save">保存</button> |
| | | <button type="button" class="btn btn-secondary" id="clear">清除</button> |
| | | <button type="button" class="btn btn-secondary" id="cancel">取消</button> |
| | | </div> |
| | | </div> |
| | |
| | | }); |
| | | }); |
| | | |
| | | // 清除任务号和目标站点 |
| | | $(document).on('click', '#clear', function () { |
| | | $('#workNo').val(0); |
| | | $('#staNo').val(0); |
| | | }); |
| | | |
| | | // 取消站点信息修改 |
| | | $(document).on('click', '#cancel', function () { |
| | | closeModal(); |
| New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>机器人数据监控</title> |
| | | <!-- 引入 layui-vue 样式 --> |
| | | <link href="https://cdn.jsdelivr.net/npm/layui-vue@2.1.0/dist/index.css" rel="stylesheet"> |
| | | <!-- 引入 Vue 3 --> |
| | | <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.min.js"></script> |
| | | <!-- 引入 layui-vue --> |
| | | <script src="https://cdn.jsdelivr.net/npm/layui-vue@2.1.0/dist/index.js"></script> |
| | | <!-- 引入 Chart.js --> |
| | | <script src="/static/js/chart.umd.min.js"></script> |
| | | <!-- 引入 Font Awesome --> |
| | | <link href="/static/css/font-awesome.min.css" rel="stylesheet"> |
| | | <style> |
| | | body { |
| | | font-family: 'Inter', sans-serif; |
| | | background-color: #f5f7fa; |
| | | } |
| | | .layui-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .layui-card-header { |
| | | font-weight: 600; |
| | | } |
| | | .status-online { |
| | | color: #10b981; |
| | | } |
| | | .status-offline { |
| | | color: #ef4444; |
| | | } |
| | | .chart-container { |
| | | height: 300px; |
| | | } |
| | | .realtime-container { |
| | | height: 160px; |
| | | overflow-y: auto; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div id="app"> |
| | | <!-- 顶部导航栏 --> |
| | | <lay-header height="60px" bg-color="#fff" shadow> |
| | | <template #left> |
| | | <div class="flex items-center space-x-2"> |
| | | <i class="fa fa-android text-2xl" style="color: #3b82f6"></i> |
| | | <h1 class="text-xl font-bold" style="color: #1e293b">机器人数据监控</h1> |
| | | </div> |
| | | </template> |
| | | <template #right> |
| | | <div class="flex items-center space-x-4"> |
| | | <lay-input placeholder="搜索机器人..." prefix-icon="search" style="width: 200px"></lay-input> |
| | | <lay-button type="primary" @click="refreshData"> |
| | | <i class="fa fa-refresh mr-1"></i> 刷新 |
| | | </lay-button> |
| | | </div> |
| | | </template> |
| | | </lay-header> |
| | | |
| | | <!-- 主内容区 --> |
| | | <lay-container style="padding: 20px"> |
| | | <!-- 状态概览 --> |
| | | <lay-row :gutter="16"> |
| | | <lay-col :span="6"> |
| | | <lay-card shadow> |
| | | <div class="flex items-center justify-between"> |
| | | <div> |
| | | <p style="color: #64748b; font-size: 14px">总机器人数</p> |
| | | <h3 style="font-size: 24px; font-weight: bold; color: #1e293b">24</h3> |
| | | </div> |
| | | <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #dbeafe; display: flex; align-items: center; justify-content: center"> |
| | | <i class="fa fa-microchip text-xl" style="color: #3b82f6"></i> |
| | | </div> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | <lay-col :span="6"> |
| | | <lay-card shadow> |
| | | <div class="flex items-center justify-between"> |
| | | <div> |
| | | <p style="color: #64748b; font-size: 14px">在线机器人</p> |
| | | <h3 style="font-size: 24px; font-weight: bold; color: #10b981">18</h3> |
| | | </div> |
| | | <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #d1fae5; display: flex; align-items: center; justify-content: center"> |
| | | <i class="fa fa-check-circle text-xl" style="color: #10b981"></i> |
| | | </div> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | <lay-col :span="6"> |
| | | <lay-card shadow> |
| | | <div class="flex items-center justify-between"> |
| | | <div> |
| | | <p style="color: #64748b; font-size: 14px">离线机器人</p> |
| | | <h3 style="font-size: 24px; font-weight: bold; color: #ef4444">6</h3> |
| | | </div> |
| | | <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fee2e2; display: flex; align-items: center; justify-content: center"> |
| | | <i class="fa fa-exclamation-circle text-xl" style="color: #ef4444"></i> |
| | | </div> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | <lay-col :span="6"> |
| | | <lay-card shadow> |
| | | <div class="flex items-center justify-between"> |
| | | <div> |
| | | <p style="color: #64748b; font-size: 14px">今日数据量</p> |
| | | <h3 style="font-size: 24px; font-weight: bold; color: #f59e0b">1.2k</h3> |
| | | </div> |
| | | <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fef3c7; display: flex; align-items: center; justify-content: center"> |
| | | <i class="fa fa-database text-xl" style="color: #f59e0b"></i> |
| | | </div> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | </lay-row> |
| | | |
| | | <!-- 数据图表 --> |
| | | <lay-row :gutter="16" style="margin-top: 16px"> |
| | | <lay-col :span="12"> |
| | | <lay-card shadow> |
| | | <template #header> |
| | | <div class="flex justify-between items-center"> |
| | | <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">上行数据趋势</h2> |
| | | <div class="flex space-x-2"> |
| | | <lay-button size="sm" type="primary">小时</lay-button> |
| | | <lay-button size="sm">天</lay-button> |
| | | <lay-button size="sm">周</lay-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <canvas ref="upDataChart"></canvas> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | <lay-col :span="12"> |
| | | <lay-card shadow> |
| | | <template #header> |
| | | <div class="flex justify-between items-center"> |
| | | <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">下行数据趋势</h2> |
| | | <div class="flex space-x-2"> |
| | | <lay-button size="sm" type="primary">小时</lay-button> |
| | | <lay-button size="sm">天</lay-button> |
| | | <lay-button size="sm">周</lay-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <canvas ref="downDataChart"></canvas> |
| | | </div> |
| | | </lay-card> |
| | | </lay-col> |
| | | </lay-row> |
| | | |
| | | <!-- 设备数据表格 --> |
| | | <lay-card shadow style="margin-top: 16px"> |
| | | <template #header> |
| | | <div class="flex justify-between items-center"> |
| | | <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">机器人数据列表</h2> |
| | | <div class="flex space-x-2"> |
| | | <lay-button size="sm"> |
| | | <i class="fa fa-filter mr-1"></i> 筛选 |
| | | </lay-button> |
| | | <lay-button size="sm"> |
| | | <i class="fa fa-download mr-1"></i> 导出 |
| | | </lay-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <lay-table :data="devices" :height="400"> |
| | | <lay-table-column prop="id" label="设备ID" width="120"></lay-table-column> |
| | | <lay-table-column prop="name" label="设备名称" width="150"></lay-table-column> |
| | | <lay-table-column prop="status" label="状态" width="100"> |
| | | <template #default="{ row }"> |
| | | <lay-badge v-if="row.status === 'online'" type="success">在线</lay-badge> |
| | | <lay-badge v-else type="danger">离线</lay-badge> |
| | | </template> |
| | | </lay-table-column> |
| | | <lay-table-column prop="upData" label="上行数据" width="120"></lay-table-column> |
| | | <lay-table-column prop="downData" label="下行数据" width="120"></lay-table-column> |
| | | <lay-table-column prop="lastComm" label="最后通信" width="150"></lay-table-column> |
| | | <lay-table-column label="操作" width="150"> |
| | | <template #default="{ row }"> |
| | | <lay-button size="sm" type="primary" style="margin-right: 8px"> |
| | | <i class="fa fa-eye"></i> |
| | | </lay-button> |
| | | <lay-button size="sm" type="warning" style="margin-right: 8px"> |
| | | <i class="fa fa-edit"></i> |
| | | </lay-button> |
| | | <lay-button size="sm" type="danger"> |
| | | <i class="fa fa-trash"></i> |
| | | </lay-button> |
| | | </template> |
| | | </lay-table-column> |
| | | </lay-table> |
| | | <div class="flex justify-between items-center mt-4"> |
| | | <p style="color: #64748b; font-size: 14px">显示 1-10 条,共 24 条</p> |
| | | <lay-pagination |
| | | v-model:current="currentPage" |
| | | v-model:limit="pageSize" |
| | | :total="total" |
| | | :limits="[10, 20, 50, 100]" |
| | | layout="prev, pager, next, jumper, sizes, total" |
| | | ></lay-pagination> |
| | | </div> |
| | | </lay-card> |
| | | |
| | | <!-- 实时数据更新 --> |
| | | <lay-card shadow style="margin-top: 16px"> |
| | | <template #header> |
| | | <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">实时数据更新</h2> |
| | | </template> |
| | | <div class="realtime-container p-2 border border-gray-200 rounded-lg"> |
| | | <div v-for="(item, index) in realtimeData" :key="index" class="py-1 border-b border-gray-100"> |
| | | <span style="color: #64748b; font-size: 12px">{{ item.timestamp }}</span> |
| | | <span style="color: #1e293b; margin-left: 10px">{{ item.message }}</span> |
| | | </div> |
| | | </div> |
| | | </lay-card> |
| | | </lay-container> |
| | | |
| | | <!-- 页脚 --> |
| | | <lay-footer height="60px" bg-color="#fff" shadow> |
| | | <div class="text-center" style="color: #64748b; font-size: 14px"> |
| | | © 2026 机器人数据监控系统 | 版本 1.0.0 |
| | | </div> |
| | | </lay-footer> |
| | | </div> |
| | | |
| | | <script> |
| | | const { createApp, ref, onMounted } = Vue; |
| | | const app = createApp({ |
| | | components: { |
| | | LayHeader: layui.LayHeader, |
| | | LayContainer: layui.LayContainer, |
| | | LayRow: layui.LayRow, |
| | | LayCol: layui.LayCol, |
| | | LayCard: layui.LayCard, |
| | | LayInput: layui.LayInput, |
| | | LayButton: layui.LayButton, |
| | | LayTable: layui.LayTable, |
| | | LayTableColumn: layui.LayTableColumn, |
| | | LayBadge: layui.LayBadge, |
| | | LayPagination: layui.LayPagination, |
| | | LayFooter: layui.LayFooter |
| | | }, |
| | | setup() { |
| | | // 模拟机器人数据 |
| | | const devices = ref([ |
| | | { id: 'ROB-001', name: '配送机器人1号', status: 'online', upData: '2.4KB', downData: '0.8KB', lastComm: '2分钟前' }, |
| | | { id: 'ROB-002', name: '配送机器人2号', status: 'online', upData: '1.8KB', downData: '0.5KB', lastComm: '5分钟前' }, |
| | | { id: 'ROB-003', name: '巡检机器人1号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '2小时前' }, |
| | | { id: 'ROB-004', name: '配送机器人3号', status: 'online', upData: '3.2KB', downData: '1.2KB', lastComm: '1分钟前' }, |
| | | { id: 'ROB-005', name: '巡检机器人2号', status: 'online', upData: '1.5KB', downData: '0.6KB', lastComm: '3分钟前' }, |
| | | { id: 'ROB-006', name: '配送机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '5小时前' }, |
| | | { id: 'ROB-007', name: '巡检机器人3号', status: 'online', upData: '2.1KB', downData: '0.9KB', lastComm: '4分钟前' }, |
| | | { id: 'ROB-008', name: '配送机器人5号', status: 'online', upData: '2.8KB', downData: '1.1KB', lastComm: '2分钟前' }, |
| | | { id: 'ROB-009', name: '巡检机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '1天前' }, |
| | | { id: 'ROB-010', name: '配送机器人6号', status: 'online', upData: '1.9KB', downData: '0.7KB', lastComm: '6分钟前' } |
| | | ]); |
| | | |
| | | // 分页数据 |
| | | const currentPage = ref(1); |
| | | const pageSize = ref(10); |
| | | const total = ref(24); |
| | | |
| | | // 实时数据 |
| | | const realtimeData = ref([]); |
| | | |
| | | // 图表引用 |
| | | const upDataChart = ref(null); |
| | | const downDataChart = ref(null); |
| | | |
| | | // 刷新数据 |
| | | const refreshData = () => { |
| | | console.log('刷新数据'); |
| | | // 这里可以添加实际的刷新逻辑 |
| | | }; |
| | | |
| | | // 初始化图表 |
| | | const initCharts = () => { |
| | | // 上行数据图表 |
| | | const upCtx = upDataChart.value.getContext('2d'); |
| | | new Chart(upCtx, { |
| | | type: 'line', |
| | | data: { |
| | | labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'], |
| | | datasets: [{ |
| | | label: '上行数据 (KB)', |
| | | data: [12, 19, 15, 25, 22, 30, 28, 35], |
| | | borderColor: '#3b82f6', |
| | | backgroundColor: 'rgba(59, 130, 246, 0.1)', |
| | | tension: 0.4, |
| | | fill: true |
| | | }] |
| | | }, |
| | | options: { |
| | | responsive: true, |
| | | maintainAspectRatio: false, |
| | | plugins: { |
| | | legend: { |
| | | display: false |
| | | } |
| | | }, |
| | | scales: { |
| | | y: { |
| | | beginAtZero: true |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // 下行数据图表 |
| | | const downCtx = downDataChart.value.getContext('2d'); |
| | | new Chart(downCtx, { |
| | | type: 'line', |
| | | data: { |
| | | labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'], |
| | | datasets: [{ |
| | | label: '下行数据 (KB)', |
| | | data: [5, 8, 6, 12, 10, 15, 13, 18], |
| | | borderColor: '#10b981', |
| | | backgroundColor: 'rgba(16, 185, 129, 0.1)', |
| | | tension: 0.4, |
| | | fill: true |
| | | }] |
| | | }, |
| | | options: { |
| | | responsive: true, |
| | | maintainAspectRatio: false, |
| | | plugins: { |
| | | legend: { |
| | | display: false |
| | | } |
| | | }, |
| | | scales: { |
| | | y: { |
| | | beginAtZero: true |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 模拟实时数据更新 |
| | | const simulateRealtimeData = () => { |
| | | const messages = [ |
| | | 'ROB-001 配送机器人1号: 运行中,位置: A1区', |
| | | 'ROB-002 配送机器人2号: 待机中,位置: B2区', |
| | | 'ROB-004 配送机器人3号: 充电中,电量: 85%', |
| | | 'ROB-005 巡检机器人2号: 巡检中,已完成3/5任务', |
| | | 'ROB-007 巡检机器人3号: 待机中,位置: C3区', |
| | | 'ROB-008 配送机器人5号: 运行中,位置: D4区' |
| | | ]; |
| | | |
| | | setInterval(() => { |
| | | const message = messages[Math.floor(Math.random() * messages.length)]; |
| | | const timestamp = new Date().toLocaleTimeString(); |
| | | realtimeData.value.unshift({ timestamp, message }); |
| | | // 限制显示条数 |
| | | if (realtimeData.value.length > 20) { |
| | | realtimeData.value.pop(); |
| | | } |
| | | }, 2000); |
| | | }; |
| | | |
| | | // 页面加载完成后初始化 |
| | | onMounted(() => { |
| | | initCharts(); |
| | | simulateRealtimeData(); |
| | | }); |
| | | |
| | | return { |
| | | devices, |
| | | currentPage, |
| | | pageSize, |
| | | total, |
| | | realtimeData, |
| | | upDataChart, |
| | | downDataChart, |
| | | refreshData |
| | | }; |
| | | } |
| | | }); |
| | | app.mount('#app'); |
| | | </script> |
| | | </body> |
| | | </html> |
| | |
| | | </parent> |
| | | |
| | | <artifactId>zy-acs-hex</artifactId> |
| | | <version>1.0.0</version> |
| | | <packaging>war</packaging> |
| | | |
| | | <properties> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | |
| | | <artifactId>fastjson</artifactId> |
| | | <version>${fastjson.version}</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | <version>4.12.0</version> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | <build> |
| | |
| | | |
| | | import org.springframework.boot.SpringApplication; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; |
| | | import org.springframework.context.annotation.ComponentScan; |
| | | import org.springframework.scheduling.annotation.EnableScheduling; |
| | | |
| | | @EnableScheduling |
| | | @ComponentScan(basePackages = {"com.zy.component", "com.zy.acs"}) |
| | | @SpringBootApplication |
| | | public class HexApplication { |
| | | public class HexApplication extends SpringBootServletInitializer { |
| | | |
| | | public static void main(String[] args) { |
| | | SpringApplication.run(HexApplication.class, args); |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| New file |
| | |
| | | package com.zy.acs.hex.config; |
| | | |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.servlet.AsyncHandlerInterceptor; |
| | | import org.springframework.web.servlet.config.annotation.CorsRegistry; |
| | | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
| | | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
| | | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * WebMvc配置, 拦截器、资源映射等都在此配置 |
| | | * |
| | | * @author vincent |
| | | * @since 2019-06-12 10:11:16 |
| | | */ |
| | | @Configuration |
| | | public class WebMvcConfig implements WebMvcConfigurer { |
| | | |
| | | /** |
| | | * token通过header传递的名称 |
| | | */ |
| | | public static final String TOKEN_HEADER_NAME = "Authorization"; |
| | | |
| | | |
| | | @Override |
| | | public void addInterceptors(InterceptorRegistry registry) { |
| | | registry.addInterceptor(getAsyncHandlerInterceptor()) |
| | | .addPathPatterns("/**") |
| | | ; |
| | | } |
| | | |
| | | |
| | | @Bean |
| | | public AsyncHandlerInterceptor getAsyncHandlerInterceptor() { |
| | | return new AsyncHandlerInterceptor(){ |
| | | @Override |
| | | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
| | | cors(response); |
| | | return true; |
| | | } |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public void addResourceHandlers(ResourceHandlerRegistry registry) { |
| | | // 配置静态资源处理器 |
| | | registry.addResourceHandler("/static/**") |
| | | .addResourceLocations("/static/"); |
| | | // 配置视图文件处理器 |
| | | registry.addResourceHandler("/views/**") |
| | | .addResourceLocations("/views/"); |
| | | } |
| | | |
| | | public static void cors(HttpServletResponse response){ |
| | | // 跨域设置 |
| | | response.setHeader("Access-Control-Max-Age", "3600"); |
| | | response.setHeader("Access-Control-Allow-Origin", "*"); |
| | | response.setHeader("Access-Control-Allow-Methods", "*"); |
| | | response.setHeader("Access-Control-Allow-Headers", "*"); |
| | | response.setHeader("Access-Control-Expose-Headers", TOKEN_HEADER_NAME); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.acs.hex.controller; |
| | | |
| | | import com.zy.acs.common.domain.mq.DeviceMessage; |
| | | import com.zy.acs.framework.common.R; |
| | | import com.zy.component.influxdb.service.InfluxDBService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.ResponseBody; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | |
| | | @RestController |
| | | @Slf4j |
| | | @RequestMapping(value = "/deviceLog") |
| | | public class DeviceLogController { |
| | | |
| | | @Autowired |
| | | private InfluxDBService influxDBService; |
| | | |
| | | |
| | | /** |
| | | * 查询最新的十条数据 |
| | | * |
| | | * @return |
| | | */ |
| | | @GetMapping(value = "/query") |
| | | @ResponseBody |
| | | public R query() { |
| | | List<DeviceMessage> deviceMessages = influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class); |
| | | return R.ok(deviceMessages); |
| | | } |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.acs.hex.controller; |
| | | |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Controller; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | |
| | | import javax.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * Created by vincent on 2019-07-30 |
| | | */ |
| | | @Controller |
| | | public class RouterController { |
| | | |
| | | |
| | | |
| | | |
| | | @RequestMapping("/") |
| | | public void index(HttpServletResponse response) { |
| | | try { |
| | | response.sendRedirect( "/views/index.html"); |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @RequestMapping("/login") |
| | | public void login(HttpServletResponse response) { |
| | | try { |
| | | response.sendRedirect( "/views/login.html"); |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | package com.zy.acs.hex.controller; |
| | | |
| | | import com.zy.acs.common.domain.mq.DeviceMessage; |
| | | import com.zy.acs.hex.constant.RabbitConstant; |
| | | import com.zy.acs.hex.domain.Device; |
| | | import com.zy.component.influxdb.service.InfluxDBService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.amqp.rabbit.core.RabbitTemplate; |
| | |
| | | */ |
| | | @GetMapping(value = "/test1") |
| | | public void sendTest1() { |
| | | Device device = new Device(); |
| | | DeviceMessage device = new DeviceMessage("121212121212"); |
| | | //device.setEvent("online"); |
| | | //device.setDeviceId("123"); |
| | | device.setProtocol("212121212121212"); |
| | | String router = RabbitConstant.ROUTING_KEY_UP.replaceFirst("\\*", "123").replaceFirst("\\*", "online"); |
| | | rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE, router, device); |
| | | } |
| | |
| | | @GetMapping(value = "/query2") |
| | | @ResponseBody |
| | | public Object queryTest2() { |
| | | return influxDBService.queryPoints("select * from device order by time desc limit 10", Device.class); |
| | | return influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.acs.hex.influxdb.task; |
| | | |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.zy.acs.hex.utils.HttpGo; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import java.io.IOException; |
| | | import java.time.Duration; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | | @Component |
| | | public class InfluxDbScheduler { |
| | | |
| | | |
| | | @Value("${influxdb3.createDatabaseUrl}") |
| | | private String createDatabaseUrl; |
| | | |
| | | |
| | | @Value("${influxdb3.database}") |
| | | private String databaseName; |
| | | |
| | | @Value("${influxdb3.token}") |
| | | private String token; |
| | | |
| | | @Value("${influxdb3.retention-period}") |
| | | private String retentionPeriod; |
| | | |
| | | |
| | | private static Long timeoutSeconds = 30L; |
| | | |
| | | private HttpGo http; |
| | | |
| | | @PostConstruct |
| | | public void init() { |
| | | this.http = HttpGo.builder() |
| | | .connectTimeout(Duration.ofSeconds(timeoutSeconds)) |
| | | .readTimeout(Duration.ofSeconds(timeoutSeconds)) |
| | | .build(); |
| | | createDatabase(); |
| | | } |
| | | |
| | | |
| | | public void createDatabase() { |
| | | // headers |
| | | Map<String, String> headers = new HashMap<>(); |
| | | headers.put("Authorization", "Bearer " + token); |
| | | headers.put("Content-Type", "application/json;charset=UTF-8"); |
| | | try { |
| | | HttpGo.HttpResponse response = this.http.get(createDatabaseUrl + "?format=json", headers, null); |
| | | if (!isExist(response.body())) { |
| | | Map<String, String> parames = new HashMap<>(); |
| | | parames.put("db", databaseName); |
| | | parames.put("retention-period", retentionPeriod); |
| | | HttpGo.HttpResponse postResponse = this.http.postJson(createDatabaseUrl, headers, JSON.toJSONString(parames)); |
| | | log.info("是否创建数据库:{}", postResponse); |
| | | }else { |
| | | log.info("数据库:{}", response.body()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | |
| | | } |
| | | |
| | | private boolean isExist(String databases) { |
| | | JSONArray objects = JSON.parseArray(databases); |
| | | for (Object object : objects) { |
| | | JSONObject obj = (JSONObject) object; |
| | | if (obj.getString("iox::database").equals(databaseName)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.acs.hex.utils; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import okhttp3.*; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.SSLSocketFactory; |
| | | import javax.net.ssl.TrustManager; |
| | | import javax.net.ssl.X509TrustManager; |
| | | import java.io.IOException; |
| | | import java.nio.charset.Charset; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.SecureRandom; |
| | | import java.security.cert.X509Certificate; |
| | | import java.time.Duration; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * Minimal OkHttp wrapper: GET / POST only. |
| | | * |
| | | * - fluent API (get / postJson / postForm) |
| | | * - default singleton instance (thread-safe) |
| | | * - per-request headers + default headers |
| | | * - simple response wrapper with tookMs |
| | | * - optional trust-all SSL (ONLY for internal test) |
| | | */ |
| | | @Slf4j |
| | | public final class HttpGo { |
| | | |
| | | private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); |
| | | private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; |
| | | |
| | | private final OkHttpClient client; |
| | | private final Map<String, String> defaultHeaders; |
| | | |
| | | private HttpGo(OkHttpClient client, Map<String, String> defaultHeaders) { |
| | | this.client = Objects.requireNonNull(client, "client"); |
| | | this.defaultHeaders = defaultHeaders == null |
| | | ? Collections.emptyMap() |
| | | : Collections.unmodifiableMap(new LinkedHashMap<>(defaultHeaders)); |
| | | } |
| | | |
| | | /** Shared default instance (safe SSL by default). */ |
| | | public static HttpGo defaults() { |
| | | return Holder.DEFAULT; |
| | | } |
| | | |
| | | public static Builder builder() { |
| | | return new Builder(); |
| | | } |
| | | |
| | | // ===================== GET ===================== |
| | | |
| | | public HttpResponse get(String url) throws IOException { |
| | | return get(url, null, null); |
| | | } |
| | | |
| | | public HttpResponse get(String url, Map<String, String> queryParams) throws IOException { |
| | | return get(url, null, queryParams); |
| | | } |
| | | |
| | | public HttpResponse get(String url, Map<String, String> headers, Map<String, String> queryParams) throws IOException { |
| | | HttpUrl parsed = HttpUrl.parse(url); |
| | | if (parsed == null) throw new IllegalArgumentException("Invalid url: " + url); |
| | | |
| | | HttpUrl.Builder ub = parsed.newBuilder(); |
| | | if (queryParams != null) { |
| | | queryParams.forEach((k, v) -> { |
| | | if (k != null && v != null) ub.addQueryParameter(k, v); |
| | | }); |
| | | } |
| | | |
| | | Request.Builder rb = new Request.Builder().url(ub.build()).get(); |
| | | applyHeaders(rb, headers); |
| | | return execute(rb.build()); |
| | | } |
| | | |
| | | // ===================== POST ===================== |
| | | |
| | | /** POST JSON string payload (null/blank -> "{}"). */ |
| | | public HttpResponse postJson(String url, String json) throws IOException { |
| | | return postJson(url, null, json); |
| | | } |
| | | |
| | | /** POST JSON string payload (null/blank -> "{}"). */ |
| | | public HttpResponse postJson(String url, Map<String, String> headers, String json) throws IOException { |
| | | String payload = (json == null || json.trim().isEmpty()) ? "{}" : json; |
| | | RequestBody body = RequestBody.create(payload, JSON); |
| | | |
| | | Request.Builder rb = new Request.Builder().url(url).post(body); |
| | | applyHeaders(rb, headers); |
| | | |
| | | // ensure content-type unless caller overrides |
| | | if (rb.build().header("Content-Type") == null) { |
| | | rb.header("Content-Type", "application/json; charset=utf-8"); |
| | | } |
| | | |
| | | return execute(rb.build()); |
| | | } |
| | | |
| | | /** POST x-www-form-urlencoded fields. */ |
| | | public HttpResponse postForm(String url, Map<String, String> formFields) throws IOException { |
| | | return postForm(url, null, formFields); |
| | | } |
| | | |
| | | /** POST x-www-form-urlencoded fields. */ |
| | | public HttpResponse postForm(String url, Map<String, String> headers, Map<String, String> formFields) throws IOException { |
| | | FormBody.Builder fb = new FormBody.Builder(DEFAULT_CHARSET); |
| | | if (formFields != null) { |
| | | formFields.forEach((k, v) -> { |
| | | if (k != null && v != null) fb.add(k, v); |
| | | }); |
| | | } |
| | | |
| | | Request.Builder rb = new Request.Builder().url(url).post(fb.build()); |
| | | applyHeaders(rb, headers); |
| | | return execute(rb.build()); |
| | | } |
| | | |
| | | // ===================== Internals ===================== |
| | | |
| | | private HttpResponse execute(Request request) throws IOException { |
| | | long start = System.nanoTime(); |
| | | try (Response resp = client.newCall(request).execute()) { |
| | | long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); |
| | | |
| | | String body = null; |
| | | ResponseBody rb = resp.body(); |
| | | if (rb != null) body = rb.string(); // one-shot |
| | | |
| | | return new HttpResponse(resp.code(), resp.headers(), body, tookMs); |
| | | } catch (IOException e) { |
| | | log.error("HttpGo request failed: {} {}", request.method(), request.url(), e); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | private void applyHeaders(Request.Builder rb, Map<String, String> headers) { |
| | | // defaults first, then per-request overrides |
| | | if (defaultHeaders != null) { |
| | | defaultHeaders.forEach((k, v) -> { |
| | | if (k != null && v != null) rb.header(k, v); |
| | | }); |
| | | } |
| | | if (headers != null) { |
| | | headers.forEach((k, v) -> { |
| | | if (k != null && v != null) rb.header(k, v); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | private static final class Holder { |
| | | private static final HttpGo DEFAULT = HttpGo.builder().build(); |
| | | } |
| | | |
| | | // ===================== Response ===================== |
| | | |
| | | public static final class HttpResponse { |
| | | private final int statusCode; |
| | | private final Headers headers; |
| | | private final String body; |
| | | private final long tookMs; |
| | | |
| | | public HttpResponse(int statusCode, Headers headers, String body, long tookMs) { |
| | | this.statusCode = statusCode; |
| | | this.headers = headers == null ? new Headers.Builder().build() : headers; |
| | | this.body = body; |
| | | this.tookMs = tookMs; |
| | | } |
| | | |
| | | public int statusCode() { return statusCode; } |
| | | public Headers headers() { return headers; } |
| | | public String body() { return body; } |
| | | public long tookMs() { return tookMs; } |
| | | |
| | | public boolean is2xx() { |
| | | return statusCode >= 200 && statusCode < 300; |
| | | } |
| | | |
| | | public String header(String name) { |
| | | return headers.get(name); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "HttpResponse{status=" + statusCode + ", tookMs=" + tookMs |
| | | + ", bodyLen=" + (body == null ? 0 : body.length()) + "}"; |
| | | } |
| | | } |
| | | |
| | | // ===================== Builder ===================== |
| | | |
| | | public static final class Builder { |
| | | private Duration connectTimeout = Duration.ofSeconds(10); |
| | | private Duration readTimeout = Duration.ofSeconds(20); |
| | | private Duration writeTimeout = Duration.ofSeconds(20); |
| | | private boolean trustAllSsl = false; |
| | | |
| | | private final Map<String, String> defaultHeaders = new LinkedHashMap<>(); |
| | | |
| | | public Builder defaultHeader(String name, String value) { |
| | | if (name != null && value != null) defaultHeaders.put(name, value); |
| | | return this; |
| | | } |
| | | |
| | | public Builder connectTimeout(Duration d) { |
| | | if (d != null) connectTimeout = d; |
| | | return this; |
| | | } |
| | | |
| | | public Builder readTimeout(Duration d) { |
| | | if (d != null) readTimeout = d; |
| | | return this; |
| | | } |
| | | |
| | | public Builder writeTimeout(Duration d) { |
| | | if (d != null) writeTimeout = d; |
| | | return this; |
| | | } |
| | | |
| | | /** Trust ALL certificates. ONLY for internal testing/self-signed endpoints. */ |
| | | public Builder trustAllSsl(boolean enable) { |
| | | this.trustAllSsl = enable; |
| | | return this; |
| | | } |
| | | |
| | | public HttpGo build() { |
| | | OkHttpClient.Builder cb = new OkHttpClient.Builder() |
| | | .connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS) |
| | | .readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS) |
| | | .writeTimeout(writeTimeout.toMillis(), TimeUnit.MILLISECONDS); |
| | | |
| | | if (trustAllSsl) { |
| | | TrustAll trustAll = new TrustAll(); |
| | | cb.sslSocketFactory(trustAll.sslSocketFactory, trustAll.trustManager) |
| | | .hostnameVerifier((hostname, session) -> true); |
| | | } |
| | | |
| | | return new HttpGo(cb.build(), defaultHeaders); |
| | | } |
| | | } |
| | | |
| | | // ===================== Trust-all SSL helper ===================== |
| | | |
| | | private static final class TrustAll { |
| | | final X509TrustManager trustManager; |
| | | final SSLSocketFactory sslSocketFactory; |
| | | |
| | | TrustAll() { |
| | | try { |
| | | this.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, new TrustManager[]{trustManager}, new SecureRandom()); |
| | | this.sslSocketFactory = sslContext.getSocketFactory(); |
| | | } catch (Exception e) { |
| | | throw new IllegalStateException("Failed to init trust-all SSL", e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ======================== tools ======================== |
| | | public String buildUrl(String host, Integer port, String path) { |
| | | return buildUrl(host, port, path, false); |
| | | } |
| | | |
| | | public String buildUrl(String host, Integer port, String path, boolean ssl) { |
| | | String p = (path == null) ? "" : (path.startsWith("/") ? path : ("/" + path)); |
| | | return (ssl ? "https" : "http") + "://" + host + ":" + port + p; |
| | | } |
| | | |
| | | // ===================== Demo (main) ===================== |
| | | |
| | | public static void main(String[] args) throws Exception { |
| | | HttpGo http = HttpGo.builder() |
| | | .connectTimeout(Duration.ofSeconds(8)) |
| | | .readTimeout(Duration.ofSeconds(15)) |
| | | .defaultHeader("User-Agent", "HttpGo/1.0") |
| | | // .trustAllSsl(true) // ONLY if you really need it |
| | | .build(); |
| | | |
| | | // 1) GET with query params + per-request headers |
| | | String getUrl = "https://httpbin.org/get"; |
| | | |
| | | Map<String, String> query = new HashMap<>(); |
| | | query.put("q", "vincent"); |
| | | query.put("page", "1"); |
| | | |
| | | Map<String, String> headers = new HashMap<>(); |
| | | headers.put("X-Trace-Id", "trace-001"); |
| | | |
| | | HttpResponse r1 = http.get(getUrl, headers, query); |
| | | System.out.println("GET status=" + r1.statusCode() + ", tookMs=" + r1.tookMs()); |
| | | System.out.println(r1.body()); |
| | | |
| | | // 2) POST JSON |
| | | String postUrl = "https://httpbin.org/post"; |
| | | String json = "{\"name\":\"Vincent\",\"role\":\"engineer\"}"; |
| | | |
| | | Map<String, String> postHeaders = new HashMap<>(); |
| | | postHeaders.put("X-Trace-Id", "trace-002"); |
| | | |
| | | HttpResponse r2 = http.postJson(postUrl, postHeaders, json); |
| | | System.out.println("POST(JSON) status=" + r2.statusCode() + ", tookMs=" + r2.tookMs()); |
| | | System.out.println(r2.body()); |
| | | |
| | | // 3) POST Form |
| | | Map<String, String> form = new HashMap<>(); |
| | | form.put("username", "vincent"); |
| | | form.put("password", "123456"); |
| | | |
| | | HttpResponse r3 = http.postForm(postUrl, null, form); |
| | | System.out.println("POST(Form) status=" + r3.statusCode() + ", tookMs=" + r3.tookMs()); |
| | | System.out.println(r3.body()); |
| | | } |
| | | |
| | | } |
| | |
| | | name: rcs-hex |
| | | # RabbitMQ配置 |
| | | rabbitmq: |
| | | host: 192.168.133.173 |
| | | host: localhost |
| | | port: 5672 |
| | | username: root |
| | | password: xltys1995 |
| | |
| | | # --add-opens java.base/java.nio=ALL-UNNAMED |
| | | influxdb3: |
| | | enabled: true |
| | | url: http://192.168.133.173:8181 |
| | | #token: apiv3_Jx1SvmBMV_kikGhc4eZJQbeGmNYN7KX1GdpoR9MClkKzMxSJ0MPKM_O2Xt3o1hVyRikMmlxZ_h9zfy6ybC5Idg |
| | | url: http://localhost:8181 |
| | | token: apiv3_Jx1SvmBMV_kikGhc4eZJQbeGmNYN7KX1GdpoR9MClkKzMxSJ0MPKM_O2Xt3o1hVyRikMmlxZ_h9zfy6ybC5Idg |
| | | database: rcs |
| | | token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA |
| | | # 虚拟机的token |
| | | #token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA |
| | | retention-period: 30d |
| | | createDatabaseUrl: ${influxdb3.url}/api/v3/configure/database |
| New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>机器人日志系统</title> |
| | | <style> |
| | | * { |
| | | margin: 0; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | } |
| | | body { |
| | | font-family: Arial, sans-serif; |
| | | background-color: #f0f2f5; |
| | | } |
| | | .header { |
| | | background-color: #1890ff; |
| | | color: white; |
| | | padding: 15px 20px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .header h1 { |
| | | font-size: 20px; |
| | | } |
| | | .header button { |
| | | background-color: transparent; |
| | | color: white; |
| | | border: 1px solid white; |
| | | padding: 5px 15px; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | } |
| | | .header button:hover { |
| | | background-color: rgba(255,255,255,0.1); |
| | | } |
| | | .container { |
| | | padding: 20px; |
| | | } |
| | | .refresh-btn { |
| | | background-color: #1890ff; |
| | | color: white; |
| | | border: none; |
| | | padding: 8px 16px; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | margin-bottom: 20px; |
| | | } |
| | | .refresh-btn:hover { |
| | | background-color: #40a9ff; |
| | | } |
| | | table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | background-color: white; |
| | | box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| | | } |
| | | th, td { |
| | | padding: 12px; |
| | | text-align: left; |
| | | border-bottom: 1px solid #ddd; |
| | | } |
| | | th { |
| | | background-color: #f5f5f5; |
| | | font-weight: bold; |
| | | } |
| | | tr:hover { |
| | | background-color: #f5f5f5; |
| | | } |
| | | .loading { |
| | | text-align: center; |
| | | padding: 20px; |
| | | color: #666; |
| | | } |
| | | .error { |
| | | text-align: center; |
| | | padding: 20px; |
| | | color: red; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div class="header"> |
| | | <h1>机器人上下行日志</h1> |
| | | <button id="logoutBtn">登出</button> |
| | | </div> |
| | | <div class="container"> |
| | | <button class="refresh-btn" id="refreshBtn">刷新数据</button> |
| | | <div id="loading" class="loading">加载中...</div> |
| | | <div id="error" class="error" style="display: none;"></div> |
| | | <table id="logTable" style="display: none;"> |
| | | <thead> |
| | | <tr> |
| | | <th>时间</th> |
| | | <th>设备ID</th> |
| | | <th>消息类型</th> |
| | | <th>消息内容</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody id="logTableBody"> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | <script> |
| | | // 检查登录状态 |
| | | function checkLogin() { |
| | | if (!localStorage.getItem('loggedIn')) { |
| | | window.location.href = '/login'; |
| | | } |
| | | } |
| | | |
| | | // 登出功能 |
| | | document.getElementById('logoutBtn').addEventListener('click', function() { |
| | | localStorage.removeItem('loggedIn'); |
| | | window.location.href = '/login'; |
| | | }); |
| | | |
| | | // 加载日志数据 |
| | | function loadLogData() { |
| | | document.getElementById('loading').style.display = 'block'; |
| | | document.getElementById('error').style.display = 'none'; |
| | | document.getElementById('logTable').style.display = 'none'; |
| | | |
| | | fetch('/deviceLog/query') |
| | | .then(response => response.json()) |
| | | .then(data => { |
| | | document.getElementById('loading').style.display = 'none'; |
| | | if (data && data.length > 0) { |
| | | document.getElementById('logTable').style.display = 'table'; |
| | | const tbody = document.getElementById('logTableBody'); |
| | | tbody.innerHTML = ''; |
| | | |
| | | data.forEach(item => { |
| | | const row = document.createElement('tr'); |
| | | row.innerHTML = ` |
| | | <td>${item.time || '-'}</td> |
| | | <td>${item.deviceId || '-'}</td> |
| | | <td>${item.messageType || '-'}</td> |
| | | <td>${item.messageContent || '-'}</td> |
| | | `; |
| | | tbody.appendChild(row); |
| | | }); |
| | | } else { |
| | | document.getElementById('error').textContent = '暂无日志数据'; |
| | | document.getElementById('error').style.display = 'block'; |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | document.getElementById('loading').style.display = 'none'; |
| | | document.getElementById('error').textContent = '加载数据失败: ' + error.message; |
| | | document.getElementById('error').style.display = 'block'; |
| | | }); |
| | | } |
| | | |
| | | // 刷新按钮点击事件 |
| | | document.getElementById('refreshBtn').addEventListener('click', loadLogData); |
| | | |
| | | // 页面加载时检查登录状态并加载数据 |
| | | checkLogin(); |
| | | loadLogData(); |
| | | </script> |
| | | </body> |
| | | </html> |
| New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>登录 - 机器人日志系统</title> |
| | | <style> |
| | | * { |
| | | margin: 0; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | } |
| | | body { |
| | | font-family: Arial, sans-serif; |
| | | background-color: #f0f2f5; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100vh; |
| | | } |
| | | .login-container { |
| | | background-color: white; |
| | | padding: 40px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| | | width: 100%; |
| | | max-width: 400px; |
| | | } |
| | | h2 { |
| | | text-align: center; |
| | | margin-bottom: 30px; |
| | | color: #333; |
| | | } |
| | | .form-group { |
| | | margin-bottom: 20px; |
| | | } |
| | | label { |
| | | display: block; |
| | | margin-bottom: 8px; |
| | | color: #666; |
| | | } |
| | | input { |
| | | width: 100%; |
| | | padding: 10px; |
| | | border: 1px solid #ddd; |
| | | border-radius: 4px; |
| | | font-size: 16px; |
| | | } |
| | | button { |
| | | width: 100%; |
| | | padding: 12px; |
| | | background-color: #1890ff; |
| | | color: white; |
| | | border: none; |
| | | border-radius: 4px; |
| | | font-size: 16px; |
| | | cursor: pointer; |
| | | margin-top: 10px; |
| | | } |
| | | button:hover { |
| | | background-color: #40a9ff; |
| | | } |
| | | .error-message { |
| | | color: red; |
| | | margin-top: 10px; |
| | | text-align: center; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div class="login-container"> |
| | | <h2>机器人日志系统</h2> |
| | | <form id="loginForm"> |
| | | <div class="form-group"> |
| | | <label for="username">用户名</label> |
| | | <input type="text" id="username" name="username" required> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label for="password">密码</label> |
| | | <input type="password" id="password" name="password" required> |
| | | </div> |
| | | <button type="submit">登录</button> |
| | | <div id="errorMessage" class="error-message"></div> |
| | | </form> |
| | | </div> |
| | | <script> |
| | | document.getElementById('loginForm').addEventListener('submit', function(e) { |
| | | e.preventDefault(); |
| | | const username = document.getElementById('username').value; |
| | | const password = document.getElementById('password').value; |
| | | |
| | | // 简单的登录验证(实际项目中应该调用后端API) |
| | | if (username === 'admin' && password === 'admin123') { |
| | | // 存储登录状态 |
| | | localStorage.setItem('loggedIn', 'true'); |
| | | // 跳转到主页面 |
| | | window.location.href = '/'; |
| | | } else { |
| | | document.getElementById('errorMessage').textContent = '用户名或密码错误'; |
| | | } |
| | | }); |
| | | </script> |
| | | </body> |
| | | </html> |