import React, { useState, useEffect } from 'react'; import { DatePicker, Table, Row, Col, Button, Select, Input, notification, message, Spin, Tooltip, Pagination, } from 'antd'; import { SwapRightOutlined, SyncOutlined, CaretUpFilled, CaretDownFilled } from '@ant-design/icons'; import { Chart, Interval, Line, Axis } from 'bizcharts'; import { post, PUBLISH_SERVICE } from '@/services/index'; import moment from 'moment'; import 'moment/dist/locale/zh-cn'; import locale from 'antd/es/date-picker/locale/zh_CN'; import styles from './index.less'; const { Option } = Select; const { Search } = Input; const { RangePicker } = DatePicker; const ServiceLog = () => { const [loading, setLoading] = useState(false); // 源数据 const [dataTable, setDataTable] = useState([]); // 源数据 const [visitedCount, setVisitedCount] = useState([]); // 访问量,统计数据 const [pathCount, setPathCount] = useState([]); // 接口调用次数,统计数据 const [reponseTime, setReponseTime] = useState([]); // 接口调用时长,统计数据 const [timeInterval, setTimeInterval] = useState('4'); // 时间间隔,1/2/3/4(每分钟/5分钟/小时/天),默认每小时 const [startTime, setStartTime] = useState(moment().startOf('month')); // 默认值,当天0点00:00:00 const [endTime, setEndTime] = useState( moment(new Date(), 'YYYY-MM-DD HH:mm:ss'), // 默认值,当前时间 ); const [allTime, setAllTime] = useState([ moment().startOf('month'), moment(new Date(), 'YYYY-MM-DD HH:mm:ss'), ]); const [logType, setLogType] = useState('9999'); // 请求参数,日志类型,默认是正常,0:成功 -1:异常 9999:全部 const [searchWord, setSearchWord] = useState(''); // 关键字 const [showSearchStyle, setShowSearchStyle] = useState(false); // 是否显示模糊查询样式 const [total, setTotal] = useState(); const [pageSize, setPageSize] = useState(20); const [currentPage, setCurrentPage] = useState(1); const [show1, setShow1] = useState('block'); const [show2, setShow2] = useState('none'); const [keepType, setKeepType] = useState(''); const [keepOrder, setKeepOrder] = useState(''); // 计算时间间隔(分钟) const start = new Date(allTime[0]).getTime(); const end = new Date(allTime[1]).getTime(); const minuteInterval = (end - start) / (60 * 1000); // 相隔多少分钟 const countInterval = () => { if (minuteInterval > 0 && minuteInterval <= 30) { setTimeInterval('1'); } else if (minuteInterval > 30 && minuteInterval <= 120) { setTimeInterval('2'); } else if (minuteInterval > 120 && minuteInterval <= 60 * 24) { setTimeInterval('3'); } else { setTimeInterval('4'); } }; // 计算时间间隔(月份),禁止跨月份查询 const startMonth = new Date(allTime[0]).getMonth(); const endMonth = new Date(allTime[1]).getMonth(); const startYear = new Date(allTime[0]).getYear(); const endYear = new Date(allTime[1]).getYear(); if (minuteInterval <= 0) { console.log(start); console.log(end); console.log(allTime[0]); console.log(allTime[1]); console.log(minuteInterval); notification.error({ message: '时间设置有误', description: '起始时间应该早于结束时间', }); } else if ((startMonth !== endMonth && startYear === endYear) || startYear !== endYear) { notification.info({ message: '时间设置提示', description: '不允许跨月份查询', }); } const columns = [ { title: '接口名称', dataIndex: 'Path', key: 'Path', fixed: 'left', width: 300, render: item => searchStyle(item), }, { dataIndex: 'CallTime', key: 'CallTime', width: 160, title: ( <div> 录入时间 <Tooltip title="点击降序"> <CaretUpFilled style={{ display: show1, color: '#1890ff', marginTop: '-13px', marginLeft: '60%', }} onClick={() => shengxu(1)} /> </Tooltip> <Tooltip title="点击升序"> <CaretDownFilled style={{ display: show2, color: '#1890ff', marginTop: '-13px', marginLeft: '60%', }} onClick={() => jiangxu(1)} /> </Tooltip> </div> ), }, // { // title: 'IP', // dataIndex: 'DownstreamRequest', // key: 'DownstreamRequest', // width: 120, // }, { title: '返回状态', dataIndex: 'Result', key: 'Result', width: 80, }, { title: '异常信息', dataIndex: 'ErrorMsg', key: 'ErrorMsg', onCell: () => ({ style: { maxWidth: 300, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', cursor: 'pointer', }, }), render: record => ( <Tooltip placement="topLeft" title={record}> {record} </Tooltip> ), }, { title: '请求方法', dataIndex: 'Method', key: 'Method', width: 100, }, { dataIndex: 'ConsumerTime', key: 'ConsumerTime', width: 100, title: ( <div> 耗时/ms <Tooltip title="点击降序"> <CaretUpFilled style={{ display: show1, color: '#1890ff', marginTop: '-13px', marginLeft: '60%', }} onClick={() => shengxu(2)} /> </Tooltip> <Tooltip title="点击升序"> <CaretDownFilled style={{ display: show2, color: '#1890ff', marginTop: '-13px', marginLeft: '60%', }} onClick={() => jiangxu(2)} /> </Tooltip> </div> ), }, { title: '返回体大小/byte', dataIndex: 'ResponseSize', key: 'ResponseSize', width: 140, }, ]; const shengxu = e => { setKeepType(e); setKeepOrder('desc'); console.log(e); setShow1('none'); setShow2('block'); setPageSize(20); setCurrentPage(1); setLoading(true); post(`${PUBLISH_SERVICE}/LogCenter/GetOMSLog`, { // 获取日志表数据时PageSize设置为200,其他接口默认值20 PageIndex: 1, PageSize: 20, DateFrom: allTime[0].format('YYYY-MM-DD HH:mm:ss'), DateTo: allTime[1].format('YYYY-MM-DD HH:mm:ss'), IP: '', Module: searchWord, LogType: +logType, Description: '', LoginName: '', UserName: '', StaticsType: +timeInterval, sort: e == 1 ? 'callTime' : 'consumerTime', order: 'desc', }) .then(res => { if (res.code === 0) { if (!res.data) { setDataTable([]); } else { const result = res.data.list; setTotal(res.data.TotalCount); setDataTable( result.map((item, index) => { item.key = index; return item; }), ); } } else { notification.error({ message: '数据获取失败', description: res.msg, }); } setLoading(false); }) .catch(err => { message.error(err); setLoading(false); }); }; const jiangxu = e => { setKeepType(e); setKeepOrder('asc'); setShow2('none'); setShow1('block'); setPageSize(20); setCurrentPage(1); setLoading(true); post(`${PUBLISH_SERVICE}/LogCenter/GetOMSLog`, { // 获取日志表数据时PageSize设置为200,其他接口默认值20 PageIndex: 1, PageSize: 20, DateFrom: allTime[0].format('YYYY-MM-DD HH:mm:ss'), DateTo: allTime[1].format('YYYY-MM-DD HH:mm:ss'), IP: '', Module: searchWord, LogType: +logType, Description: '', LoginName: '', UserName: '', StaticsType: +timeInterval, sort: e == 1 ? 'callTime' : 'consumerTime', order: 'asc', }) .then(res => { if (res.code === 0) { if (!res.data) { setDataTable([]); } else { const result = res.data.list; setTotal(res.data.TotalCount); setDataTable( result.map((item, index) => { item.key = index; return item; }), ); } } else { notification.error({ message: '数据获取失败', description: res.msg, }); } setLoading(false); }) .catch(err => { message.error(err); setLoading(false); }); }; // 模糊查询匹配的样式 const searchStyle = val => { let n; if (showSearchStyle) { n = val.replace(new RegExp(searchWord, 'g'), `<span style='color:red'>${searchWord}</span>`); } else { n = val; } return <div dangerouslySetInnerHTML={{ __html: n }} />; }; // 在起止时间任意一个变化后获取数据,且起止时间应该早于结束时间,且不允许跨月查询 useEffect(() => { if (allTime && end - start > 0 && startMonth === endMonth && startYear === endYear) { countInterval(); // 根据起止时间计算时间间隔 } }, [allTime]); useEffect(() => { if (allTime && end - start > 0 && startMonth === endMonth && startYear === endYear) { setLoading(true); getData('/TrafficStatistics', setVisitedCount); // 访问量统计 } }, [allTime, logType, timeInterval]); useEffect(() => { if (allTime && end - start > 0 && startMonth === endMonth && startYear === endYear) { setLoading(true); getData('/TopCountList', setPathCount); // 接口调用频次统计 getData('/TopConsumeList', setReponseTime); // 接口平均耗时统计 getData('/GetOMSLog', setDataTable); // 接口调用记录 } }, [allTime, logType]); // 封装接口请求,参数url/设置方法set const getData = (url, set) => { post(`${PUBLISH_SERVICE}/LogCenter${url}`, { // 获取日志表数据时PageSize设置为200,其他接口默认值20 PageIndex: 1, PageSize: 20, DateFrom: allTime[0].format('YYYY-MM-DD HH:mm:ss'), DateTo: allTime[1].format('YYYY-MM-DD HH:mm:ss'), IP: '', Module: url === '/GetOMSLog' ? searchWord : '', LogType: +logType, Description: '', LoginName: '', UserName: '', StaticsType: +timeInterval, }) .then(res => { if (res.code === 0) { if (!res.data) { set([]); } else { const result = url === '/GetOMSLog' ? res.data.list : res.data; if (url === '/GetOMSLog') { setTotal(res.data.TotalCount); } set( result.map((item, index) => { item.key = index; if (url === '/TrafficStatistics') { item.StartTime = item.StartTime.replace(' ', '-'); } return item; }), ); } setKeepType(''); setKeepOrder(''); setPageSize(20); setCurrentPage(1); } else { notification.error({ message: '数据获取失败', description: res.msg, }); } setLoading(false); }) .catch(err => { message.error(err); setLoading(false); }); }; const getData1 = (url, set, page, size) => { console.log(keepType); console.log(keepOrder); let data = {}; if (keepType) { data = { PageIndex: page, PageSize: size, DateFrom: allTime[0].format('YYYY-MM-DD HH:mm:ss'), DateTo: allTime[1].format('YYYY-MM-DD HH:mm:ss'), IP: '', Module: searchWord, LogType: +logType, Description: '', LoginName: '', UserName: '', StaticsType: +timeInterval, sort: keepType == 1 ? 'callTime' : 'consumerTime', order: keepOrder, }; } else { data = { PageIndex: page, PageSize: size, DateFrom: allTime[0].format('YYYY-MM-DD HH:mm:ss'), DateTo: allTime[1].format('YYYY-MM-DD HH:mm:ss'), IP: '', Module: searchWord, LogType: +logType, Description: '', LoginName: '', UserName: '', StaticsType: +timeInterval, }; } post(`${PUBLISH_SERVICE}/LogCenter${url}`, data) .then(res => { if (res.code === 0) { if (!res.data) { set([]); } else { const result = res.data.list; setTotal(res.data.TotalCount); set( result.map((item, index) => { item.key = index; return item; }), ); } } else { notification.error({ message: '数据获取失败', description: res.msg, }); } setLoading(false); }) .catch(err => { message.error(err); setLoading(false); }); }; // DatePicker改变点击确定时 const changeStartTime = time => { console.log(time); setStartTime(time); }; const changeEndTime = time => { console.log(time); setEndTime(time); }; const changeTime = time => { console.log(time); setAllTime(time); }; // 近1/6/12/24小时,同时设置对应的时间间隔 const setTime = (time, value) => { setTimeInterval(value); setStartTime(moment(new Date(new Date().getTime() - time * 60 * 1000), 'YYYY-MM-DD HH:mm:ss')); setEndTime(moment(new Date(), 'YYYY-MM-DD HH:mm:ss')); setAllTime([ moment(new Date(new Date().getTime() - time * 60 * 1000), 'YYYY-MM-DD HH:mm:ss'), moment(new Date(), 'YYYY-MM-DD HH:mm:ss'), ]); }; // 设置返回状态 const changeStatus = value => { setLogType(value); }; // 设置时间间隔 const selectChange = value => { setTimeInterval(value); }; // 获取搜索框的值 const handleSearch = e => { setSearchWord(e.target.value); }; const handleReset = () => { setLogType('9999'); setTimeInterval('4'); setStartTime(moment().startOf('month')); setEndTime(moment(new Date(), 'YYYY-MM-DD HH:mm:ss')); setAllTime([moment().startOf('month'), moment(new Date(), 'YYYY-MM-DD HH:mm:ss')]); setSearchWord(''); setShowSearchStyle(false); }; const dayTime = () => { let date = new Date(); return date.getDate() - 1; }; // 监听分页 const paginationChange = (page, pageSizes) => { getData1('/GetOMSLog', setDataTable, page, pageSizes); setCurrentPage(page); setPageSize(pageSizes); }; return ( <> <div className={styles.serviceLog}> <Row className={styles.head}> <Col span={24}> <span style={{ marginLeft: '27px' }}>时间:</span> <RangePicker locale={locale} showTime format="YYYY-MM-DD HH:mm:ss" onChange={changeTime} value={allTime} allowClear={false} /> {/* <DatePicker locale={locale} showTime format="YYYY-MM-DD HH:mm:ss" placeholder="起始时间" value={startTime} onChange={changeStartTime} allowClear={false} /> <SwapRightOutlined style={{ verticalAlign: '0.125em' }} /> <DatePicker locale={locale} showTime format="YYYY-MM-DD HH:mm:ss" placeholder="结束时间" value={endTime} onChange={changeEndTime} style={{ marginRight: '10px' }} allowClear={false} /> */} <Button onClick={() => setTime(15, '1')} style={{ marginLeft: '10px' }}> 近15分钟 </Button> <Button onClick={() => setTime(60, '2')}>近1小时</Button> <Button onClick={() => setTime(12 * 60, '3')}>近12小时</Button> <Button onClick={() => setTime(24 * 60, '3')}>近1天</Button> <Button onClick={() => setTime(24 * 7 * 60, '4')}>近1周</Button> <Button onClick={() => setTime(24 * dayTime() * 60, '4')}>本月</Button> <br /> <br /> <span>返回状态:</span> <Select defaultValue="全部" value={logType} onChange={changeStatus} className={styles.sel} > <Option value="9999">全部</Option> <Option value="0">正常</Option> <Option value="-1">异常</Option> </Select> <Search allowClear style={{ width: 250, marginLeft: '20px' }} placeholder="请输入接口名称" onSearch={() => { getData('/GetOMSLog', setDataTable); setShowSearchStyle(true); }} onChange={e => handleSearch(e)} enterButton value={searchWord} /> {/* <Button icon={<SyncOutlined className={styles.icon} />} onClick={handleReset} style={{ marginLeft: '25px', verticalAlign: 'middle', marginTop: '-3px', }} > 重置 </Button> */} </Col> </Row> <Spin spinning={loading} tip="loading"> <Row style={{ background: 'white' }}> <Col span={8} style={{ paddingTop: '8px', paddingLeft: '5px' }}> <span style={{ fontSize: '14px', fontWeight: '600' }}>访问量统计</span> <span style={{ float: 'right' }}> <span>间隔:</span> <Select defaultValue="每小时" size="small" value={timeInterval} onChange={selectChange} className={styles.sel} > <Option value="1">每分钟</Option> <Option value="2">每5分钟</Option> <Option value="3">每小时</Option> <Option value="4">每天</Option> </Select> </span> </Col> <Col span={7} offset={1} style={{ paddingTop: '8px' }}> <span style={{ fontSize: '14px', fontWeight: '600' }}>接口调用频次统计</span> </Col> <Col span={7} offset={1} style={{ paddingTop: '8px' }}> <span style={{ fontSize: '14px', fontWeight: '600' }}>接口平均耗时统计</span> </Col> </Row> <Row className={styles.chart}> <Col span={8}> <Chart height={300} autoFit data={visitedCount} interactions={['active-region']} padding="auto" renderer="svg" scale={{ Count: { alias: '计数' }, StartTime: { alias: '访问开始时间' }, }} > <Axis name="StartTime" label="" title={{ offset: 20 }} className={styles.fontColor} /> <Axis name="Count" title /> <Line shape="smooth" position="StartTime*Count" /> <Tooltip shared lock /> </Chart> </Col> <Col span={7} offset={1}> <Chart height={300} autoFit data={pathCount} interactions={['active-region']} padding="auto" renderer="svg" scale={{ Count: { alias: '计数' }, Path: { alias: '接口调用频次' }, }} > <Axis name="Path" label="" title={{ offset: 20 }} className={styles.fontColor} /> <Axis name="Count" title /> <Interval position="Path*Count" /> <Tooltip shared lock /> </Chart> </Col> <Col span={7} offset={1}> <Chart height={300} autoFit data={reponseTime} interactions={['active-region']} padding="auto" renderer="svg" scale={{ AvgTime: { alias: '响应时长/ms' }, Path: { alias: '接口平均响应时长' }, }} > <Axis name="Path" label="" title={{ offset: 20 }} /> <Axis name="AvgTime" title /> <Interval position="Path*AvgTime" /> <Tooltip shared lock /> </Chart> </Col> </Row> <div className={styles.table}> <Table size="small" bordered columns={columns} dataSource={dataTable} scroll={{ x: 'max-content', y: 'calc(100vh - 615px)' }} pagination={false} /> </div> <Pagination style={{ display: 'flex', justifyContent: 'end', padding: '10px' }} total={total} showTotal={(aa, range) => `第${range[0]}-${range[1]} 条/共 ${total} 条`} defaultPageSize={pageSize} defaultCurrent={1} pageSizeOptions={[10, 20, 40, 100]} current={currentPage} onChange={paginationChange} size="small" showQuickJumper /> </Spin> </div> </> ); }; export default ServiceLog;