This commit is contained in:
Vinjor 2025-08-07 18:16:27 +08:00
parent d028780bd3
commit 05c5161894
10 changed files with 243 additions and 175 deletions

View File

@ -466,8 +466,9 @@ public class WebController extends BaseController {
})
@PostMapping("/pageSave")
public R<String> pageSave(@ApiIgnore @RequestBody BusiPage busiPage, HttpServletRequest request) {
busiPage.setIp(CommonUtils.getIpAddr(request));
busiPage.setCreateTime(new Date());
pageService.pageSave(busiPage,request);
pageService.pageSave(busiPage);
return R.ok();
}
}

View File

@ -6,6 +6,7 @@ import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.busi.vo.BusiPageVO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -40,17 +41,12 @@ public class BusiPageController extends BaseController
private IBusiPageService busiPageService;
/**
* 查询全站网页访问次数统计列表
* 查询全站网页访问次数统计列表---排名前10
*/
@PreAuthorize("@ss.hasPermi('busi:page:list')")
@GetMapping("/list")
public AjaxResult list(BusiPage busiPage,
@RequestParam(name = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize)
{
Page<BusiPage> page = new Page<>(pageNum, pageSize);
IPage<BusiPage> list = busiPageService.queryListPage(busiPage,page);
return success(list);
public AjaxResult list(BusiPageVO busiPage){
return success(busiPageService.queryListNum(busiPage));
}
/**

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.busi.domain.BusiPage;
import com.ruoyi.busi.vo.BusiPageVO;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@ -18,4 +19,6 @@ import org.apache.ibatis.annotations.Mapper;
public interface BusiPageMapper extends BaseMapper<BusiPage>
{
IPage<BusiPage> queryListPage(@Param("entity") BusiPage entity, Page<BusiPage> page);
List<BusiPageVO> selectListCus(@Param("entity")BusiPageVO busiPageVO);
}

View File

@ -1,10 +1,13 @@
package com.ruoyi.busi.service;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.busi.domain.BusiPage;
import com.ruoyi.busi.vo.BusiPageVO;
import javax.servlet.http.HttpServletRequest;
@ -24,5 +27,14 @@ public interface IBusiPageService extends IService<BusiPage>
* @date 11:13 2025/8/7
* @param busiPage TODO
**/
void pageSave(BusiPage busiPage, HttpServletRequest request);
void pageSave(BusiPage busiPage);
/**
* 查全站访问网页前十
* @author vinjor-M
* @date 16:52 2025/8/7
* @param busiPage TODO
* @return java.util.List<com.ruoyi.busi.vo.BusiPageVO>
**/
List<BusiPageVO> queryListNum(BusiPageVO busiPage);
}

View File

@ -1,13 +1,19 @@
package com.ruoyi.busi.service.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.busi.utils.CommonUtils;
import com.ruoyi.busi.vo.BusiPageVO;
import com.ruoyi.common.utils.DateUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.constant.StrConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -44,14 +50,55 @@ public class BusiPageServiceImpl extends ServiceImpl<BusiPageMapper,BusiPage> i
* @date 11:13 2025/8/7
**/
@Override
public void pageSave(BusiPage busiPage, HttpServletRequest request) {
public void pageSave(BusiPage busiPage) {
// 2. 异步保存到数据库RuoYi已内置线程池
CompletableFuture.runAsync(() -> {
Map<String,String> ipMap = commonUtils.getIPAndCountry(request);
busiPage.setIp(ipMap.get("ip"));
Map<String,String> ipMap = commonUtils.getCountryByIp(busiPage.getIp());
busiPage.setNational(ipMap.get("national"));
busiPage.setOceania(ipMap.get("oceania"));
this.save(busiPage);
});
}
/**
* 查全站访问网页前十
*
* @param busiPage TODO
* @return java.util.List<com.ruoyi.busi.vo.BusiPageVO>
* @author vinjor-M
* @date 16:52 2025/8/7
**/
@Override
public List<BusiPageVO> queryListNum(BusiPageVO busiPage) {
if(StringUtils.isNotEmpty(busiPage.getStartDate())){
busiPage.setStartDate(busiPage.getStartDate()+ StrConstants.START_DATE);
}
if(StringUtils.isNotEmpty(busiPage.getEndDate())){
busiPage.setEndDate(busiPage.getEndDate()+StrConstants.END_DATE);
}
List<BusiPageVO> rtnList = new ArrayList<>();
List<BusiPageVO> dataList = busiPageMapper.selectListCus(busiPage);
// 1. url 分组并统计数量
Map<String, Long> countMap = dataList.stream()
.collect(Collectors.groupingBy(
// 分组字段
BusiPageVO::getUrl,
// 统计数量
Collectors.counting()
));
// 2. 按数量降序排序并取前10
List<Map.Entry<String, Long>> top10 = countMap.entrySet().stream()
// 降序排序
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
// 取前10
.limit(10)
.collect(Collectors.toList());
top10.forEach(item->{
BusiPageVO pageVO = new BusiPageVO();
pageVO.setUrl(item.getKey());
pageVO.setNum(item.getValue());
rtnList.add(pageVO);
});
return rtnList;
}
}

View File

@ -110,7 +110,7 @@ public class CommonUtils {
}
/**
* 根据IP查询所属国家和洲
* 根据请求查询IP所属国家和洲
* @author vinjor-M
* @date 15:48 2025/8/7
* @return java.util.Map<java.lang.String,java.lang.Object>
@ -128,7 +128,6 @@ public class CommonUtils {
ip = StringUtils.isNotEmpty(ip) ? ip : "未知";
nationalStr = StringUtils.isNotEmpty(nationalStr) ? nationalStr : "未知";
String national = nationalStr.split("\\|")[0];
System.out.println(ip + "-----" + nationalStr);
String oceania = "未知";
Map<String, String> nationalMap = nationalService.getNationalMap();
if (nationalMap.containsKey(national)) {
@ -139,4 +138,30 @@ public class CommonUtils {
rtnMap.put("oceania",oceania);
return rtnMap;
}
/**
* 根据IP查询所属国家和洲
* @author vinjor-M
* @date 15:48 2025/8/7
* @return java.util.Map<java.lang.String,java.lang.Object>
**/
public Map<String,String> getCountryByIp(String ip){
Map<String,String> rtnMap = new HashMap<>();
String nationalStr = "";
try {
nationalStr = CommonUtils.getAddr(ip);
} catch (Exception e) {
log.error("识别所属国家失败");
}
nationalStr = StringUtils.isNotEmpty(nationalStr) ? nationalStr : "未知";
String national = nationalStr.split("\\|")[0];
String oceania = "未知";
Map<String, String> nationalMap = nationalService.getNationalMap();
if (nationalMap.containsKey(national)) {
oceania = nationalMap.get(national);
}
rtnMap.put("national",national);
rtnMap.put("oceania",oceania);
return rtnMap;
}
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.busi.vo;
import com.ruoyi.busi.domain.BusiPage;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class BusiPageVO extends BusiPage {
/** 时间范围-开始 */
private String startDate;
/** 时间范围-结束 */
private String endDate;
/** 访问次数 */
private Long num;
/** 是否归属中国 */
private Boolean ifChina;
}

View File

@ -29,4 +29,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="entity.tenantId != null and entity.tenantId != ''"> and tenant_id = #{entity.tenantId}</if>
</where>
</select>
<select id="selectListCus" resultType="com.ruoyi.busi.vo.BusiPageVO">
SELECT
*
FROM
dl_busi_page
WHERE
tenant_id = 'main'
<if test="entity.ifChina != null and entity.ifChina == true">
AND national = '中国'
</if>
<if test="entity.ifChina != null and entity.ifChina == false">
AND national != '中国'
</if>
<if test="entity.startDate != null and entity.startDate != ''">
and create_time &gt;= #{entity.startDate}
</if>
<if test="entity.endDate != null and entity.endDate != ''">
and create_time &lt;= #{entity.endDate}
</if>
</select>
</mapper>

View File

@ -1,45 +1,28 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="网页地址" prop="url">
<el-input
v-model="queryParams.url"
placeholder="请输入网页地址"
clearable
@keyup.enter.native="handleQuery"
/>
<el-form-item label="时间范围" prop="dataRange">
<el-date-picker
v-model="queryParams.dataRange"
type="daterange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerOptions"
>
</el-date-picker>
</el-form-item>
<el-form-item label="设备类型" prop="equipment">
<el-input
v-model="queryParams.equipment"
placeholder="请输入设备类型"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="来源IP" prop="ip">
<el-input
v-model="queryParams.ip"
placeholder="请输入来源IP"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="来源国家" prop="national">
<el-input
v-model="queryParams.national"
placeholder="请输入来源国家"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="站点唯一编码" prop="tenantId">
<el-input
v-model="queryParams.tenantId"
placeholder="请输入站点唯一编码"
clearable
@keyup.enter.native="handleQuery"
/>
<el-form-item label="来自中国">
<el-select v-model="queryParams.ifChina" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -47,112 +30,21 @@
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['busi:page:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['busi:page:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['busi:page:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['busi:page:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="pageList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键" align="center" prop="id" />
<el-table v-loading="loading" :data="pageList" >
<el-table-column type="index" width="60" label="序号" align="center"/>
<el-table-column label="访问次数" align="center" prop="num" width="100" />
<el-table-column label="网页地址" align="center" prop="url" />
<el-table-column label="设备类型" align="center" prop="equipment" />
<el-table-column label="来源IP" align="center" prop="ip" />
<el-table-column label="来源国家" align="center" prop="national" />
<el-table-column label="站点唯一编码" align="center" prop="tenantId" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['busi:page:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['busi:page:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改全站网页访问次数统计对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="网页地址" prop="url">
<el-input v-model="form.url" placeholder="请输入网页地址" />
</el-form-item>
<el-form-item label="设备类型" prop="equipment">
<el-input v-model="form.equipment" placeholder="请输入设备类型" />
</el-form-item>
<el-form-item label="来源IP" prop="ip">
<el-input v-model="form.ip" placeholder="请输入来源IP" />
</el-form-item>
<el-form-item label="来源国家" prop="national">
<el-input v-model="form.national" placeholder="请输入来源国家" />
</el-form-item>
<el-form-item label="站点唯一编码" prop="tenantId">
<el-input v-model="form.tenantId" placeholder="请输入站点唯一编码" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- <pagination-->
<!-- v-show="total>0"-->
<!-- :total="total"-->
<!-- :page.sync="queryParams.pageNum"-->
<!-- :limit.sync="queryParams.pageSize"-->
<!-- @pagination="getList"-->
<!-- />-->
</div>
</template>
@ -163,34 +55,59 @@ export default {
name: "Page",
data() {
return {
options: [{
value: null,
label: '全部'
}, {
label: '是',
value: true
}, {
label: '否',
value: false
}],
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
// 访
pageList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
url: null,
equipment: null,
ip: null,
national: null,
dataRange: '',
startDate: '',
endDate: '',
ifChina:null,
tenantId: null,
},
pickerOptions: {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
picker.$emit('pick', [start, end])
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
picker.$emit('pick', [start, end])
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
picker.$emit('pick', [start, end])
}
}]
},
//
form: {},
//
@ -206,8 +123,7 @@ export default {
getList() {
this.loading = true;
listPage(this.queryParams).then(response => {
this.pageList = response.data.records;
this.total = response.data.total;
this.pageList = response.data;
this.loading = false;
});
},
@ -232,6 +148,13 @@ export default {
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
if (this.queryParams.dataRange && this.queryParams.dataRange.length > 1) {
this.queryParams.startDate = this.formatDate(this.queryParams.dataRange[0])
this.queryParams.endDate = this.formatDate(this.queryParams.dataRange[1])
}else{
this.queryParams.startDate = null
this.queryParams.endDate = null
}
this.getList();
},
/** 重置按钮操作 */
@ -296,7 +219,18 @@ export default {
this.download('busi/page/export', {
...this.queryParams
}, `page_${new Date().getTime()}.xlsx`)
}
},
/**
* 格式化时间戳
*/
formatDate(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
// 01
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
}
};
</script>

View File

@ -287,7 +287,18 @@ export default {
this.download('busi/inquiryItem/export', {
...this.queryParams
}, `inquiryItem_${new Date().getTime()}.xlsx`)
}
},
/**
* 格式化时间戳
*/
formatDate(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
// 01
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
}
};
</script>