From c33436500a16709d10a444d2906d47b103973e94 Mon Sep 17 00:00:00 2001 From: lhy Date: Sun, 25 Jan 2026 18:39:09 +0800 Subject: [PATCH] .2174177451707596:c0ba60c71ba14c5a02716963e3cb79f4_6975df8be4f68bfdcb300b56.6975e00ce4f68bfdcb300b5a.6975e00b2e282319d9096ddc:Trae CN.T(2026/1/25 17:19:08) --- blog-api/pom.xml | 6 ++ .../controller/admin/VisitLogController.java | 66 +++++++++++++++++ .../top/naccl/model/vo/VisitLogExcel.java | 53 ++++++++++++++ .../main/resources/application-dev.properties | 5 +- blog-cms/src/views/log/VisitLog.vue | 53 +++++++++++++- blog-view/vue.config.js | 72 +++++++++---------- 6 files changed, 213 insertions(+), 42 deletions(-) create mode 100644 blog-api/src/main/java/top/naccl/model/vo/VisitLogExcel.java 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