diff --git a/blog-api/pom.xml b/blog-api/pom.xml
index aed43a72..707ec48a 100644
--- a/blog-api/pom.xml
+++ b/blog-api/pom.xml
@@ -100,6 +100,12 @@
commonmark-ext-task-list-items
0.18.1
+
+
+ com.alibaba
+ easyexcel
+ 2.2.10
+
org.springframework.boot
diff --git a/blog-api/src/main/java/top/naccl/controller/admin/VisitLogController.java b/blog-api/src/main/java/top/naccl/controller/admin/VisitLogController.java
index afe31056..25b224e3 100644
--- a/blog-api/src/main/java/top/naccl/controller/admin/VisitLogController.java
+++ b/blog-api/src/main/java/top/naccl/controller/admin/VisitLogController.java
@@ -1,8 +1,10 @@
package top.naccl.controller.admin;
+import com.alibaba.excel.EasyExcel;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -11,8 +13,16 @@
import org.springframework.web.bind.annotation.RestController;
import top.naccl.entity.VisitLog;
import top.naccl.model.vo.Result;
+import top.naccl.model.vo.VisitLogExcel;
import top.naccl.service.VisitLogService;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
/**
* @Description: 访问日志后台管理
* @Author: Naccl
@@ -61,4 +71,60 @@ public Result delete(@RequestParam Long id) {
visitLogService.deleteVisitLogById(id);
return Result.ok("删除成功");
}
+
+ /**
+ * 导出访问日志
+ *
+ * @param uuid 按访客标识码模糊查询
+ * @param date 按访问时间查询
+ * @param response HTTP响应
+ * @throws IOException IO异常
+ */
+ @GetMapping("/visitLog/export")
+ public void exportVisitLog(@RequestParam(defaultValue = "") String uuid,
+ @RequestParam(defaultValue = "") String[] date,
+ HttpServletResponse response) throws IOException {
+ String startDate = null;
+ String endDate = null;
+ if (date.length == 2) {
+ startDate = date[0];
+ endDate = date[1];
+ } else {
+ // 默认导出最近7天
+ Date now = new Date();
+ endDate = DateFormatUtils.format(now, "yyyy-MM-dd HH:mm:ss");
+ startDate = DateFormatUtils.format(new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000), "yyyy-MM-dd HH:mm:ss");
+ }
+
+ List visitLogList = visitLogService.getVisitLogListByUUIDAndDate(StringUtils.trim(uuid), startDate, endDate);
+
+ // 转换为Excel模型
+ List excelList = new ArrayList<>();
+ for (int i = 0; i < visitLogList.size(); i++) {
+ VisitLog visitLog = visitLogList.get(i);
+ VisitLogExcel excel = new VisitLogExcel();
+ excel.setSerialNumber(i + 1);
+ excel.setUuid(visitLog.getUuid());
+ excel.setBehavior(visitLog.getBehavior());
+ excel.setContent(visitLog.getContent());
+ excel.setIp(visitLog.getIp());
+ excel.setIpSource(visitLog.getIpSource());
+ excel.setOs(visitLog.getOs());
+ excel.setBrowser(visitLog.getBrowser());
+ excel.setCreateTime(DateFormatUtils.format(visitLog.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
+ excelList.add(excel);
+ }
+
+ // 设置响应头
+ String fileName = "访问日志_" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmss") + ".xlsx";
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ response.setCharacterEncoding("utf-8");
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+ String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
+ response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName);
+
+ // 写入Excel
+ EasyExcel.write(response.getOutputStream(), VisitLogExcel.class).sheet("访问日志").doWrite(excelList);
+ }
}
diff --git a/blog-api/src/main/java/top/naccl/model/vo/VisitLogExcel.java b/blog-api/src/main/java/top/naccl/model/vo/VisitLogExcel.java
new file mode 100644
index 00000000..01199553
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/vo/VisitLogExcel.java
@@ -0,0 +1,53 @@
+package top.naccl.model.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * @Description: 访问日志Excel导出模型
+ * @Author: Naccl
+ * @Date: 2025-01-25
+ */
+@Getter
+@Setter
+public class VisitLogExcel {
+ @ExcelProperty(value = "序号", index = 0)
+ @ColumnWidth(10)
+ private Integer serialNumber;
+
+ @ExcelProperty(value = "访客标识", index = 1)
+ @ColumnWidth(32)
+ private String uuid;
+
+ @ExcelProperty(value = "访问行为", index = 2)
+ @ColumnWidth(20)
+ private String behavior;
+
+ @ExcelProperty(value = "访问内容", index = 3)
+ @ColumnWidth(50)
+ private String content;
+
+ @ExcelProperty(value = "IP", index = 4)
+ @ColumnWidth(15)
+ private String ip;
+
+ @ExcelProperty(value = "IP来源", index = 5)
+ @ColumnWidth(20)
+ private String ipSource;
+
+ @ExcelProperty(value = "操作系统", index = 6)
+ @ColumnWidth(20)
+ private String os;
+
+ @ExcelProperty(value = "浏览器", index = 7)
+ @ColumnWidth(20)
+ private String browser;
+
+ @ExcelProperty(value = "访问时间", index = 8)
+ @ColumnWidth(20)
+ private String createTime;
+}
\ No newline at end of file
diff --git a/blog-api/src/main/resources/application-dev.properties b/blog-api/src/main/resources/application-dev.properties
index 67cee1ac..4ca5ab8a 100644
--- a/blog-api/src/main/resources/application-dev.properties
+++ b/blog-api/src/main/resources/application-dev.properties
@@ -15,8 +15,9 @@ spring.datasource.url=jdbc:mysql://localhost:3306/nblog?useUnicode=true&characte
spring.datasource.username=root
spring.datasource.password=root
# Redis连接信息
-spring.redis.host=192.168.17.132
-spring.redis.password=123456
+#spring.redis.host=192.168.17.132
+spring.redis.host=127.0.0.1
+#spring.redis.password=123456
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=10000ms
diff --git a/blog-cms/src/views/log/VisitLog.vue b/blog-cms/src/views/log/VisitLog.vue
index b64e59df..8bef901f 100644
--- a/blog-cms/src/views/log/VisitLog.vue
+++ b/blog-cms/src/views/log/VisitLog.vue
@@ -13,6 +13,9 @@
搜索
+
+ 导出
+
@@ -70,9 +73,9 @@
diff --git a/blog-view/vue.config.js b/blog-view/vue.config.js
index 37018708..e3dc0329 100644
--- a/blog-view/vue.config.js
+++ b/blog-view/vue.config.js
@@ -1,38 +1,36 @@
module.exports = {
- configureWebpack: {
- resolve: {
- alias: {
- 'assets': '@/assets',
- 'common': '@/common',
- 'components': '@/components',
- 'api': '@/api',
- 'views': '@/views',
- 'plugins': '@/plugins'
- }
- }
- },
- optimization: {
- splitChunks: {
- cacheGroups: {
- vendor: {
- test: /[\\/]node_modules[\\/]/,
- name(module) {
- // get the name. E.g. node_modules/packageName/not/this/part.js
- // or node_modules/packageName
- const packageName = module.context.match(
- /[\\/]node_modules[\\/](.*?)([\\/]|$)/
- )[1];
- // npm package names are URL-safe, but some servers don't like @ symbols
- return `npm.${packageName.replace("@", "")}`;
- },
- chunks: "all",
- enforce: true,
- priority: 10,
- minSize: 50000, // 50KB
- maxSize: 200000,
- reuseExistingChunk: true,
- },
- },
- },
- },
-}
\ No newline at end of file
+ configureWebpack: {
+ resolve: {
+ alias: {
+ 'assets': '@/assets',
+ 'common': '@/common',
+ 'components': '@/components',
+ 'api': '@/api',
+ 'views': '@/views',
+ 'plugins': '@/plugins'
+ }
+ },
+ // 把 optimization 放到 configureWebpack 里面
+ optimization: {
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ test: /[\\/]node_modules[\\/]/,
+ name(module) {
+ const packageName = module.context.match(
+ /[\\/]node_modules[\\/](.*?)([\\/]|$)/
+ )[1];
+ return `npm.${packageName.replace("@", "")}`;
+ },
+ chunks: "all",
+ enforce: true,
+ priority: 10,
+ minSize: 50000,
+ maxSize: 200000,
+ reuseExistingChunk: true,
+ },
+ },
+ }
+ }
+ }
+ }
\ No newline at end of file