diff --git a/.gitignore b/.gitignore
index 04aab4802..83520bb82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,6 @@ build/
### VS Code ###
.vscode/
-/blog-cms-tmp
\ No newline at end of file
+.DS_Store
+/blog-cms-tmp
+package-lock.json
diff --git a/README.md b/README.md
index f6b190455..388b27c07 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
- NBlog logo
+ NBlog logo
@@ -14,15 +14,18 @@
+
## 简介
-本着『前后端分离,人不分离』的要领,开发了此基于 Spring Boot + Vue 前后端分离博客系统
+Spring Boot + Vue「前后端分离,人不分离」博客系统
+
+自用博客,长期维护,欢迎勘误
预览地址:
前台:[https://naccl.top](https://naccl.top)
-后台:[https://admin.naccl.top](https://admin.naccl.top) 账号`Visitor`密码`123456`
+后台:[https://admin.naccl.top](https://admin.naccl.top)
@@ -30,10 +33,10 @@
1. 核心框架:[Spring Boot](https://github.com/spring-projects/spring-boot)
2. 安全框架:[Spring Security](https://github.com/spring-projects/spring-security)
-3. Token 认证:[jjwt](https://github.com/jwtk/jjwt)
-4. 持久层框架:[MyBatis](https://github.com/mybatis/spring-boot-starter)
+3. Token:[jjwt](https://github.com/jwtk/jjwt)
+4. ORM 框架:[MyBatis](https://github.com/mybatis/spring-boot-starter)
5. 分页插件:[PageHelper](https://github.com/pagehelper/Mybatis-PageHelper)
-6. NoSQL缓存:[Redis](https://github.com/redis/redis)
+6. NoSQL 缓存:[Redis](https://github.com/redis/redis)
7. Markdown 转 HTML:[commonmark-java](https://github.com/commonmark/commonmark-java)
8. 离线 IP 地址库:[ip2region](https://github.com/lionsoul2014/ip2region)
9. 定时任务:[quartz](https://github.com/quartz-scheduler/quartz)
@@ -41,17 +44,7 @@
-邮件模板参考自[Typecho-CommentToMail-Template](https://github.com/MisakaTAT/Typecho-CommentToMail-Template)
-
-基于 JDK8 开发,8以上要添加依赖:
-
-```xml
-
- javax.xml.bind
- jaxb-api
- 2.3.0
-
-```
+邮件模板参考自 [Typecho-CommentToMail-Template](https://github.com/MisakaTAT/Typecho-CommentToMail-Template)
@@ -63,11 +56,13 @@ Vue 项目基于 @vue/cli4.x 构建
JS 依赖及参考的 css:[axios](https://github.com/axios/axios)、[moment](https://github.com/moment/moment)、[nprogress](https://github.com/rstacruz/nprogress)、[v-viewer](https://github.com/fengyuanchen/viewerjs)、[prismjs](https://github.com/PrismJS/prism)、[APlayer](https://github.com/DIYgod/APlayer)、[MetingJS](https://github.com/metowolf/MetingJS)、[lodash](https://github.com/lodash/lodash)、[mavonEditor](https://github.com/hinesboy/mavonEditor)、[echarts](https://github.com/apache/echarts)、[tocbot](https://github.com/tscanlin/tocbot)、[iCSS](https://github.com/chokcoco/iCSS)
+**由 [@willWang8023](https://github.com/willWang8023) 维护的 Vue3 版本请查看 [blog-view-vue3](https://github.com/willWang8023/blog-view-vue3)**
+
### 后台 UI
-后台 CMS 部分基于 [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 二次修改后的 [my-vue-admin-template](https://github.com/Naccl/my-vue-admin-template) 模板进行开发(于2021年11月1日重构过一次,[重构 commit](https://github.com/Naccl/NBlog/commit/b33641fe34b2bed34e8237bacf67146cd64be4cf))
+后台基于 [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 二次修改后的 [my-vue-admin-template](https://github.com/Naccl/my-vue-admin-template) 模板进行开发([于2021年11月1日重构](https://github.com/Naccl/NBlog/commit/b33641fe34b2bed34e8237bacf67146cd64be4cf))
UI 框架为 [Element UI](https://github.com/ElemeFE/element)
@@ -83,10 +78,28 @@ UI 框架为 [Element UI](https://github.com/ElemeFE/element)
-## 快速开始
+## Telegram Bot 快捷操作
+
+| 桌面 | Phone | Phone |
+| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+|  |  |  |
+
+若要启用该功能,参考以下步骤:
+
+1. 向 @BotFather 申请一个 Bot,得到该 Bot 的`token`,格式如`1234567890:qwertyuiopasdfghjklzxcvbnm`
+2. 与该 Bot 私聊,随便发个消息,然后打开此链接`https://api.telegram.org/bot/getUpdates`(替换链接中的 token),在`result -> message -> chat -> id`得到`chatId`
+3. 将获取的`token`和`chatId`填入`application-dev.properties`,并启用`comment.notify.channel=tg`
+4. 由于目前仅提供 webhook 的方式获取消息更新,所以`application-dev.properties`中的`blog.api`需要填写后端 API 的地址,并且**必须是`https`(Telegram 的要求)**,也就是说如果你没有公网 IP 或内网穿透或反向代理,那么在本地环境是无法测试的,建议直接扔服务器上
+5. 国内通常情况下无法访问 TG 的 API,因此提供了两种方式
+ 1. 正向代理:配置`http.proxy.server`,通过你的代理发送请求
+ 2. 反向代理:可以直接使用我跑在 Cloudflare Workers 上的反代,默认配置即可。示例代码已放在`blog-api/cfworker-tg-api-open.js`,可自行搭建(**22.05.12 更新,近两天大陆绝大多数地区 `*.workers.dev` 域名已被墙,因此若仍想使用此反代方式访问 cf worker,需要将 Worker 绑定路由至自己的域名,详见[相关讨论](https://github.com/XIU2/CloudflareSpeedTest/issues/205)**)
+
+
+
+## 开发环境
1. 创建 MySQL 数据库`nblog`,并执行`/blog-api/nblog.sql`初始化表数据
-2. 修改配置信息`blog-api/src/main/resources/application-dev.properties`
+2. 修改配置信息`/blog-api/src/main/resources/application-dev.properties`
3. 安装 Redis 并启动
4. 启动后端服务
5. 分别在`blog-cms`和`blog-view`目录下执行`npm install`安装依赖
@@ -96,15 +109,16 @@ UI 框架为 [Element UI](https://github.com/ElemeFE/element)
## 注意事项
-最近逐渐有些小伙伴关注此项目,表示不知如何部署,或部署过程中有一些问题不知如何解决,在这里把过程中可能遇到的一些问题描述一下:
+一些常见问题:
-- 本人使用的 MySQL 版本为 8.x,5.x 版本未经测试,但确保数据库字符集为`utf8mb4`的情况下通常没有问题("站点设置"及"文章详情"等许多表字段需要`utf8mb4`格式字符集来支持emoji表情,否则在导入 sql 文件时,即使成功导入,也会有部分字段内容不完整,导致前端页面渲染数据时报错)
-- 确保 Maven 能够成功导入现版本依赖(已知 [yauaa](https://github.com/nielsbasjes/yauaa) 依赖在用更高版本替换后,以现在的写法运行会报错)
+- MySQL 确保数据库字符集为`utf8mb4`("站点设置"及"文章详情"等许多表字段需要`utf8mb4`格式字符集来支持 emoji 表情,否则在导入 sql 文件时,即使成功导入,也会有部分字段内容不完整,导致前端页面渲染数据时报错)
+- 确保 Maven 和 NPM 能够成功导入现版本依赖,请勿升级或降低依赖版本
- 数据库中默认用户名密码为`Admin`,`123456`,因为是个人博客,没打算做修改密码的页面,可在`top.naccl.util.HashUtils`下的`main`方法手动生成密码存入数据库
- 注意修改`application-dev.properties`的配置信息
- - Redis 若没有密码,留空即可
- 注意修改`token.secretKey`,否则无法保证 token 安全性
- - `spring.mail.host`及`spring.mail.port`的默认配置为阿里云邮箱,其它邮箱服务商参考关键字`spring mail 服务器`(邮箱配置用于接收评论提醒)
+ - `spring.mail.host`及`spring.mail.port`的默认配置为阿里云邮箱,其它邮箱服务商参考关键字`spring mail 服务器`(邮箱配置用于接收/发送评论提醒)
+- 如需部署,注意将`/blog-view/src/plugins/axios.js`和`/blog-cms/src/util/request.js`中的`baseUrl`修改为你的后端 API 地址
+- 大部分个性化配置可以在后台"站点设置"中修改,小部分由于考虑到首屏加载速度(如首页大图)需要修改前端源码
@@ -113,20 +127,17 @@ UI 框架为 [Element UI](https://github.com/ElemeFE/element)
- 在前台访问`/login`路径登录后,可以以博主身份(带有博主标识)回复评论,且不需要填写昵称和邮箱即可提交
- 在 Markdown 中加入`` (注意以正文形式添加,而不是代码片段)可以在文章中添加 [APlayer](https://github.com/DIYgod/APlayer) 音乐播放器,`netease`为网易云音乐,其它配置及具体用法参考 [MetingJS](https://github.com/metowolf/MetingJS)
- 提供了两种隐藏文字效果:在 Markdown 中使用`@@`包住文字,文字会被渲染成"黑幕"效果,鼠标悬浮在上面时才会显示;使用`%%`包住文字,文字会被"蓝色覆盖层"遮盖,只有鼠标选中状态才会反色显示。例如:`@@隐藏文字@@`,`%%隐藏文字%%`
-- 大部分个性化配置可以在后台"站点设置"中修改,小部分由于考虑到首屏加载速度(如首页大图)需要修改前端源码,通过 IDE 全局查找应该可以很快定位
-
-## And...
-由于一些技术是现学现用的,难免有些考虑不周,望大佬们能够指出错误
+## LICENSE
-自用博客,长期维护,欢迎勘误
+[MIT](https://github.com/Naccl/NBlog/blob/master/LICENSE)
-## 致谢
+## Thanks
-感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件 License
+感谢 [JetBrains](https://www.jetbrains.com/?from=NBlog) 提供的 Open Source License
-此项目本是学习过程中的产物,参考了许多优秀的教程和项目,由于比较零散,难以统计,如大佬能看到此,请及时与我联系,以便表示感谢
+感谢上面提到的每个开源项目
diff --git a/blog-api/.gitignore b/blog-api/.gitignore
index b77b6cca3..2ced2fc6c 100644
--- a/blog-api/.gitignore
+++ b/blog-api/.gitignore
@@ -33,4 +33,5 @@ build/
.vscode/
log
-application-prod.properties
\ No newline at end of file
+application-test.properties
+application-prod.properties
diff --git a/blog-api/cfworker-tg-api-open.js b/blog-api/cfworker-tg-api-open.js
new file mode 100644
index 000000000..232bf3dde
--- /dev/null
+++ b/blog-api/cfworker-tg-api-open.js
@@ -0,0 +1,37 @@
+addEventListener("fetch", event => {
+ let response = handleRequest(event.request)
+ return event.respondWith(response)
+})
+
+async function handleRequest(request) {
+ const { method, headers } = request
+
+ if (method === "POST" && headers.get("content-type") === "application/json") {
+ const json = await request.json()
+ const toUrl = json.to
+ console.log(toUrl)
+ const data = json.data
+ let response = await fetch(toUrl, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(data)
+ })
+ const results = await gatherResponse(response)
+ return new Response(results, {
+ headers: {
+ "content-type": "application/json;charset=UTF-8",
+ },
+ })
+ }
+ return new Response("error", { status: 403 })
+}
+
+async function gatherResponse(response) {
+ const { headers } = response
+ const contentType = headers.get("content-type") || ""
+ if (contentType.includes("application/json")) {
+ return JSON.stringify(await response.json())
+ } else {
+ return response.text()
+ }
+}
\ No newline at end of file
diff --git a/blog-api/nblog.sql b/blog-api/nblog.sql
index a92e64d6b..d877b3ce9 100644
--- a/blog-api/nblog.sql
+++ b/blog-api/nblog.sql
@@ -244,34 +244,38 @@ CREATE TABLE `site_setting` (
-- ----------------------------
-- Records of site_setting
-- ----------------------------
-INSERT INTO `site_setting` VALUES (1, 'webTitleSuffix', '网页标题后缀', ' - Naccl\'s Blog', 1);
-INSERT INTO `site_setting` VALUES (2, 'blogName', '博客名称', 'Naccl\'s Blog', 1);
+INSERT INTO `site_setting` VALUES (1, 'blogName', '博客名称', 'Naccl\'s Blog', 1);
+INSERT INTO `site_setting` VALUES (2, 'webTitleSuffix', '网页标题后缀', ' - Naccl\'s Blog', 1);
INSERT INTO `site_setting` VALUES (3, 'footerImgTitle', '页脚图片标题', '手机看本站', 1);
INSERT INTO `site_setting` VALUES (4, 'footerImgUrl', '页脚图片路径', '/img/qr.png', 1);
-INSERT INTO `site_setting` VALUES (5, 'copyright', 'Copyright', '{\"title\":\"Copyright © 2019 - 2020\",\"siteName\":\"NACCL\'S BLOG\"}', 1);
+INSERT INTO `site_setting` VALUES (5, 'copyright', 'Copyright', '{\"title\":\"Copyright © 2019 - 2022\",\"siteName\":\"NACCL\'S BLOG\"}', 1);
INSERT INTO `site_setting` VALUES (6, 'beian', 'ICP备案号', '', 1);
-INSERT INTO `site_setting` VALUES (7, 'badge', '徽标', '{\"title\":\"由 Spring Boot 强力驱动\",\"url\":\"https://spring.io/projects/spring-boot/\",\"subject\":\"Powered\",\"value\":\"Spring Boot\",\"color\":\"blue\"}', 2);
-INSERT INTO `site_setting` VALUES (8, 'badge', '徽标', '{\"title\":\"Vue.js 客户端渲染\",\"url\":\"https://cn.vuejs.org/\",\"subject\":\"SPA\",\"value\":\"Vue.js\",\"color\":\"brightgreen\"}', 2);
-INSERT INTO `site_setting` VALUES (9, 'badge', '徽标', '{\"title\":\"UI 框架 Semantic-UI\",\"url\":\"https://semantic-ui.com/\",\"subject\":\"UI\",\"value\":\"Semantic-UI\",\"color\":\"semantic-ui\"}', 2);
-INSERT INTO `site_setting` VALUES (10, 'badge', '徽标', '{\"title\":\"阿里云提供服务器及域名相关服务\",\"url\":\"https://www.aliyun.com/\",\"subject\":\"VPS & DNS\",\"value\":\"Aliyun\",\"color\":\"blueviolet\"}', 2);
-INSERT INTO `site_setting` VALUES (11, 'badge', '徽标', '{\"title\":\"jsDelivr 提供 CDN 加速服务\",\"url\":\"https://www.jsdelivr.com/\",\"subject\":\"CDN\",\"value\":\"jsDelivr\",\"color\":\"orange\"}', 2);
-INSERT INTO `site_setting` VALUES (12, 'badge', '徽标', '{\"title\":\"GitHub 提供图床\",\"url\":\"https://github.com/\",\"subject\":\"OSS\",\"value\":\"GitHub\",\"color\":\"github\"}', 2);
-INSERT INTO `site_setting` VALUES (13, 'badge', '徽标', '{\"title\":\"本站点采用 CC BY 4.0 国际许可协议进行许可\",\"url\":\"https://creativecommons.org/licenses/by/4.0/\",\"subject\":\"CC\",\"value\":\"BY 4.0\",\"color\":\"lightgray\"}', 2);
-INSERT INTO `site_setting` VALUES (14, 'avatar', '图片路径', '/img/avatar.jpg', 3);
-INSERT INTO `site_setting` VALUES (15, 'name', '昵称', 'Naccl', 3);
-INSERT INTO `site_setting` VALUES (16, 'rollText', '滚动个签', '\"云鹤当归天,天不迎我妙木仙;\",\"游龙当归海,海不迎我自来也。\"', 3);
-INSERT INTO `site_setting` VALUES (17, 'github', 'GitHub地址', 'https://github.com/Naccl', 3);
-INSERT INTO `site_setting` VALUES (18, 'qq', 'QQ链接', 'http://sighttp.qq.com/authd?IDKEY=', 3);
-INSERT INTO `site_setting` VALUES (19, 'bilibili', 'bilibili链接', 'https://space.bilibili.com/', 3);
-INSERT INTO `site_setting` VALUES (20, 'netease', '网易云音乐', 'https://music.163.com/#/user/home?id=', 3);
-INSERT INTO `site_setting` VALUES (21, 'email', 'email', 'mailto:i@naccl.top', 3);
-INSERT INTO `site_setting` VALUES (22, 'favorite', '自定义', '{\"title\":\"最喜欢的动漫 📺\",\"content\":\"异度侵入、春物语、NO GAME NO LIFE、实力至上主义的教室、辉夜大小姐、青春猪头少年不会梦到兔女郎学姐、路人女主、Re0、魔禁、超炮、俺妹、在下坂本、散华礼弥、OVERLORD、慎勇、人渣的本愿、白色相簿2、死亡笔记、DARLING in the FRANXX、鬼灭之刃\"}', 3);
-INSERT INTO `site_setting` VALUES (23, 'favorite', '自定义', '{\"title\":\"最喜欢我的女孩子们 🤤\",\"content\":\"芙兰达、土间埋、食蜂操祈、佐天泪爷、樱岛麻衣、桐崎千棘、02、亚丝娜、高坂桐乃、五更琉璃、安乐冈花火、一色彩羽、英梨梨、珈百璃、时崎狂三、可儿那由多、和泉纱雾、早坂爱\"}', 3);
-INSERT INTO `site_setting` VALUES (24, 'favorite', '自定义', '{\"title\":\"最喜欢玩的游戏 🎮\",\"content\":\"Stellaris、巫师、GTA、荒野大镖客、刺客信条、魔兽争霸、LOL、PUBG\"}', 3);
-INSERT INTO `site_setting` VALUES (25, 'reward', '赞赏码路径', '/img/reward.jpg', 1);
-INSERT INTO `site_setting` VALUES (26, 'commentAdminFlag', '博主评论标识', '咕咕', 1);
-INSERT INTO `site_setting` VALUES (27, 'friendContent', '友链页面信息', '随机排序,不分先后。欢迎交换友链~( ̄▽ ̄)~*\n\n* 昵称:Naccl\n* 一句话:游龙当归海,海不迎我自来也。\n* 网址:[https://naccl.top](https://naccl.top)\n* 头像URL:[https://naccl.top/img/avatar.jpg](https://naccl.top/img/avatar.jpg)\n\n仅凭个人喜好添加友链,请在收到我的回复邮件后再于贵站添加本站链接。原则上已添加的友链不会删除,如果你发现自己被移除了,恕不另行通知,只需和我一样做就好。\n\n', 4);
-INSERT INTO `site_setting` VALUES (28, 'friendCommentEnabled', '友链页面评论开关', '1', 4);
+INSERT INTO `site_setting` VALUES (7, 'reward', '赞赏码', '/img/reward.jpg', 1);
+INSERT INTO `site_setting` VALUES (8, 'commentAdminFlag', '博主评论标识', '咕咕', 1);
+INSERT INTO `site_setting` VALUES (9, 'playlistServer', '播放器平台', 'netease', 1);
+INSERT INTO `site_setting` VALUES (10, 'playlistId', '播放器歌单', '3071528549', 1);
+INSERT INTO `site_setting` VALUES (11, 'avatar', '头像', '/img/avatar.jpg', 2);
+INSERT INTO `site_setting` VALUES (12, 'name', '昵称', 'Naccl', 2);
+INSERT INTO `site_setting` VALUES (13, 'rollText', '滚动个签', '\"云鹤当归天,天不迎我妙木仙;\",\"游龙当归海,海不迎我自来也。\"', 2);
+INSERT INTO `site_setting` VALUES (14, 'github', 'GitHub', 'https://github.com/Naccl', 2);
+INSERT INTO `site_setting` VALUES (15, 'telegram', 'Telegram', 'https://t.me/NacclOfficial', 2);
+INSERT INTO `site_setting` VALUES (16, 'qq', 'QQ', 'http://sighttp.qq.com/authd?IDKEY=', 2);
+INSERT INTO `site_setting` VALUES (17, 'bilibili', 'bilibili', 'https://space.bilibili.com/', 2);
+INSERT INTO `site_setting` VALUES (18, 'netease', '网易云音乐', 'https://music.163.com/#/user/home?id=', 2);
+INSERT INTO `site_setting` VALUES (19, 'email', 'email', 'mailto:you@example.com', 2);
+INSERT INTO `site_setting` VALUES (20, 'favorite', '自定义', '{\"title\":\"最喜欢的动漫 📺\",\"content\":\"异度侵入、春物语、NO GAME NO LIFE、实力至上主义的教室、辉夜大小姐、青春猪头少年不会梦到兔女郎学姐、路人女主、Re0、魔禁、超炮、俺妹、在下坂本、散华礼弥、OVERLORD、慎勇、人渣的本愿、白色相簿2、死亡笔记、DARLING in the FRANXX、鬼灭之刃\"}', 2);
+INSERT INTO `site_setting` VALUES (21, 'favorite', '自定义', '{\"title\":\"最喜欢我的女孩子们 🤤\",\"content\":\"芙兰达、土间埋、食蜂操祈、佐天泪爷、樱岛麻衣、桐崎千棘、02、亚丝娜、高坂桐乃、五更琉璃、安乐冈花火、一色彩羽、英梨梨、珈百璃、时崎狂三、可儿那由多、和泉纱雾、早坂爱\"}', 2);
+INSERT INTO `site_setting` VALUES (22, 'favorite', '自定义', '{\"title\":\"最喜欢玩的游戏 🎮\",\"content\":\"Stellaris、巫师、GTA、荒野大镖客、刺客信条、魔兽争霸、LOL、PUBG\"}', 2);
+INSERT INTO `site_setting` VALUES (23, 'badge', '徽标', '{\"title\":\"本博客已开源于 GitHub\",\"url\":\"https://github.com/Naccl/NBlog\",\"subject\":\"NBlog\",\"value\":\"Open Source\",\"color\":\"brightgreen\"}', 3);
+INSERT INTO `site_setting` VALUES (24, 'badge', '徽标', '{\"title\":\"由 Spring Boot 强力驱动\",\"url\":\"https://spring.io/projects/spring-boot/\",\"subject\":\"Powered\",\"value\":\"Spring Boot\",\"color\":\"blue\"}', 3);
+INSERT INTO `site_setting` VALUES (25, 'badge', '徽标', '{\"title\":\"Vue.js 客户端渲染\",\"url\":\"https://cn.vuejs.org/\",\"subject\":\"SPA\",\"value\":\"Vue.js\",\"color\":\"brightgreen\"}', 3);
+INSERT INTO `site_setting` VALUES (26, 'badge', '徽标', '{\"title\":\"UI 框架 Semantic-UI\",\"url\":\"https://semantic-ui.com/\",\"subject\":\"UI\",\"value\":\"Semantic-UI\",\"color\":\"semantic-ui\"}', 3);
+INSERT INTO `site_setting` VALUES (27, 'badge', '徽标', '{\"title\":\"阿里云提供服务器及域名相关服务\",\"url\":\"https://www.aliyun.com/\",\"subject\":\"VPS & DNS\",\"value\":\"Aliyun\",\"color\":\"blueviolet\"}', 3);
+INSERT INTO `site_setting` VALUES (28, 'badge', '徽标', '{\"title\":\"静态资源托管于 GitHub\",\"url\":\"https://github.com/\",\"subject\":\"OSS\",\"value\":\"GitHub\",\"color\":\"github\"}', 3);
+INSERT INTO `site_setting` VALUES (29, 'badge', '徽标', '{\"title\":\"jsDelivr 加速静态资源\",\"url\":\"https://www.jsdelivr.com/\",\"subject\":\"CDN\",\"value\":\"jsDelivr\",\"color\":\"orange\"}', 3);
+INSERT INTO `site_setting` VALUES (30, 'badge', '徽标', '{\"color\":\"lightgray\",\"subject\":\"CC\",\"title\":\"本站点采用 CC BY 4.0 国际许可协议进行许可\",\"url\":\"https://creativecommons.org/licenses/by/4.0/\",\"value\":\"BY 4.0\"}', 3);
+INSERT INTO `site_setting` VALUES (31, 'friendContent', '友链页面信息', '随机排序,不分先后。欢迎交换友链~( ̄▽ ̄)~*\n\n* 昵称:Naccl\n* 一句话:游龙当归海,海不迎我自来也。\n* 网址:[https://naccl.top](https://naccl.top)\n* 头像URL:[https://naccl.top/img/avatar.jpg](https://naccl.top/img/avatar.jpg)\n\n仅凭个人喜好添加友链,请在收到我的回复邮件后再于贵站添加本站链接。原则上已添加的友链不会删除,如果你发现自己被移除了,恕不另行通知,只需和我一样做就好。\n\n', 4);
+INSERT INTO `site_setting` VALUES (32, 'friendCommentEnabled', '友链页面评论开关', '1', 4);
-- ----------------------------
-- Table structure for tag
@@ -356,7 +360,8 @@ CREATE TABLE `visitor` (
`last_time` datetime(0) NOT NULL COMMENT '最后访问时间',
`pv` int(0) NULL DEFAULT NULL COMMENT '访问页数统计',
`user_agent` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'user-agent用户代理',
- PRIMARY KEY (`id`) USING BTREE
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `idx_uuid` (`uuid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
diff --git a/blog-api/pom.xml b/blog-api/pom.xml
index 0d77193b2..aed43a72c 100644
--- a/blog-api/pom.xml
+++ b/blog-api/pom.xml
@@ -20,6 +20,7 @@
1.8
1.8
UTF-8
+ 2.17.2
@@ -38,6 +39,11 @@
org.springframework.boot
spring-boot-starter-aop
+
+
+ org.springframework.retry
+ spring-retry
+
org.mybatis.spring.boot
@@ -70,29 +76,29 @@
- com.atlassian.commonmark
+ org.commonmark
commonmark
- 0.15.2
+ 0.18.1
- com.atlassian.commonmark
+ org.commonmark
commonmark-ext-heading-anchor
- 0.15.2
+ 0.18.1
- com.atlassian.commonmark
+ org.commonmark
commonmark-ext-gfm-tables
- 0.15.2
+ 0.18.1
- com.atlassian.commonmark
+ org.commonmark
commonmark-ext-gfm-strikethrough
- 0.15.2
+ 0.18.1
- com.atlassian.commonmark
+ org.commonmark
commonmark-ext-task-list-items
- 0.15.2
+ 0.18.1
@@ -118,13 +124,31 @@
org.lionsoul
ip2region
- 1.7.2
+ 2.6.5
nl.basjes.parse.useragent
yauaa
- 5.20
+ 7.11.0
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ com.upyun
+ java-sdk
+ 4.2.3
+
+
+
+ cn.hutool
+ hutool-crypto
+ 5.8.11
diff --git a/blog-api/src/main/java/top/naccl/annotation/VisitLogger.java b/blog-api/src/main/java/top/naccl/annotation/VisitLogger.java
index 96f9ccf49..321578024 100644
--- a/blog-api/src/main/java/top/naccl/annotation/VisitLogger.java
+++ b/blog-api/src/main/java/top/naccl/annotation/VisitLogger.java
@@ -1,5 +1,7 @@
package top.naccl.annotation;
+import top.naccl.enums.VisitBehavior;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -13,13 +15,8 @@
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLogger {
- /**
- * 访问行为
- */
- String behavior() default "";
-
- /**
- * 访问内容
- */
- String content() default "";
+ /**
+ * 访问行为枚举
+ */
+ VisitBehavior value() default VisitBehavior.UNKNOWN;
}
diff --git a/blog-api/src/main/java/top/naccl/aspect/ExceptionLogAspect.java b/blog-api/src/main/java/top/naccl/aspect/ExceptionLogAspect.java
index c24ef62b0..12171b932 100644
--- a/blog-api/src/main/java/top/naccl/aspect/ExceptionLogAspect.java
+++ b/blog-api/src/main/java/top/naccl/aspect/ExceptionLogAspect.java
@@ -58,7 +58,6 @@ private ExceptionLog handleLog(JoinPoint joinPoint, Exception e) {
String method = request.getMethod();
String ip = IpAddressUtils.getIpAddress(request);
String userAgent = request.getHeader("User-Agent");
- //todo 使用swagger后,可以直接使用注解上的内容作为 ExceptionLog 的 description
String description = getDescriptionFromAnnotations(joinPoint);
String error = StringUtils.getStackTrace(e);
ExceptionLog log = new ExceptionLog(uri, method, description, error, ip, userAgent);
@@ -68,18 +67,15 @@ private ExceptionLog handleLog(JoinPoint joinPoint, Exception e) {
}
private String getDescriptionFromAnnotations(JoinPoint joinPoint) {
- String description = "";
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
OperationLogger operationLogger = method.getAnnotation(OperationLogger.class);
if (operationLogger != null) {
- description = operationLogger.value();
- return description;
+ return operationLogger.value();
}
VisitLogger visitLogger = method.getAnnotation(VisitLogger.class);
if (visitLogger != null) {
- description = visitLogger.behavior();
- return description;
+ return visitLogger.value().getBehavior();
}
- return description;
+ return "";
}
-}
\ No newline at end of file
+}
diff --git a/blog-api/src/main/java/top/naccl/aspect/VisitLogAspect.java b/blog-api/src/main/java/top/naccl/aspect/VisitLogAspect.java
index 5ab0399eb..52e1df0b3 100644
--- a/blog-api/src/main/java/top/naccl/aspect/VisitLogAspect.java
+++ b/blog-api/src/main/java/top/naccl/aspect/VisitLogAspect.java
@@ -9,9 +9,11 @@
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.naccl.annotation.VisitLogger;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.VisitLog;
import top.naccl.entity.Visitor;
+import top.naccl.enums.VisitBehavior;
+import top.naccl.model.dto.VisitLogRemark;
import top.naccl.model.vo.BlogDetail;
import top.naccl.model.vo.Result;
import top.naccl.service.RedisService;
@@ -25,7 +27,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
-import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -63,7 +64,7 @@ public void logPointcut(VisitLogger visitLogger) {
@Around("logPointcut(visitLogger)")
public Object logAround(ProceedingJoinPoint joinPoint, VisitLogger visitLogger) throws Throwable {
currentTime.set(System.currentTimeMillis());
- Object result = joinPoint.proceed();
+ Result result = (Result) joinPoint.proceed();
int times = (int) (System.currentTimeMillis() - currentTime.get());
currentTime.remove();
//获取请求对象
@@ -89,14 +90,14 @@ private String checkIdentification(HttpServletRequest request) {
identification = saveUUID(request);
} else {
//校验Redis中是否存在uuid
- boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, identification);
+ boolean redisHas = redisService.hasValueInSet(RedisKeyConstants.IDENTIFICATION_SET, identification);
//Redis中不存在uuid
if (!redisHas) {
//校验数据库中是否存在uuid
boolean mysqlHas = visitorService.hasUUID(identification);
if (mysqlHas) {
//数据库存在,保存至Redis
- redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, identification);
+ redisService.saveValueToSet(RedisKeyConstants.IDENTIFICATION_SET, identification);
} else {
//数据库不存在,签发新的uuid
identification = saveUUID(request);
@@ -131,10 +132,10 @@ private String saveUUID(HttpServletRequest request) {
//暴露自定义header供页面资源使用
response.addHeader("Access-Control-Expose-Headers", "identification");
//校验Redis中是否存在uuid
- boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
+ boolean redisHas = redisService.hasValueInSet(RedisKeyConstants.IDENTIFICATION_SET, uuid);
if (!redisHas) {
//保存至Redis
- redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
+ redisService.saveValueToSet(RedisKeyConstants.IDENTIFICATION_SET, uuid);
//保存至数据库
Visitor visitor = new Visitor(uuid, ip, userAgent);
visitorService.saveVisitor(visitor);
@@ -151,17 +152,16 @@ private String saveUUID(HttpServletRequest request) {
* @param times
* @return
*/
- private VisitLog handleLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogger, HttpServletRequest request, Object result,
+ private VisitLog handleLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogger, HttpServletRequest request, Result result,
int times, String identification) {
String uri = request.getRequestURI();
String method = request.getMethod();
- String behavior = visitLogger.behavior();
- String content = visitLogger.content();
String ip = IpAddressUtils.getIpAddress(request);
String userAgent = request.getHeader("User-Agent");
Map requestParams = AopUtils.getRequestParams(joinPoint);
- Map map = judgeBehavior(behavior, content, requestParams, result);
- VisitLog log = new VisitLog(identification, uri, method, behavior, map.get("content"), map.get("remark"), ip, times, userAgent);
+ VisitLogRemark visitLogRemark = judgeBehavior(visitLogger.value(), requestParams, result);
+ VisitLog log = new VisitLog(identification, uri, method, visitLogger.value().getBehavior(),
+ visitLogRemark.getContent(), visitLogRemark.getRemark(), ip, times, userAgent);
log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));
return log;
}
@@ -170,49 +170,49 @@ private VisitLog handleLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogge
* 根据访问行为,设置对应的访问内容或备注
*
* @param behavior
- * @param content
* @param requestParams
* @param result
* @return
*/
- private Map judgeBehavior(String behavior, String content, Map requestParams, Object result) {
- Map map = new HashMap();
+ private VisitLogRemark judgeBehavior(VisitBehavior behavior, Map requestParams, Result result) {
String remark = "";
- if (behavior.equals("访问页面") && (content.equals("首页") || content.equals("动态"))) {
- int pageNum = (int) requestParams.get("pageNum");
- remark = "第" + pageNum + "页";
- } else if (behavior.equals("查看博客")) {
- Result res = (Result) result;
- if (res.getCode() == 200) {
- BlogDetail blog = (BlogDetail) res.getData();
- String title = blog.getTitle();
- content = title;
- remark = "文章标题:" + title;
- }
- } else if (behavior.equals("搜索博客")) {
- Result res = (Result) result;
- if (res.getCode() == 200) {
- String query = (String) requestParams.get("query");
- content = query;
- remark = "搜索内容:" + query;
- }
- } else if (behavior.equals("查看分类")) {
- String categoryName = (String) requestParams.get("categoryName");
- int pageNum = (int) requestParams.get("pageNum");
- content = categoryName;
- remark = "分类名称:" + categoryName + ",第" + pageNum + "页";
- } else if (behavior.equals("查看标签")) {
- String tagName = (String) requestParams.get("tagName");
- int pageNum = (int) requestParams.get("pageNum");
- content = tagName;
- remark = "标签名称:" + tagName + ",第" + pageNum + "页";
- } else if (behavior.equals("点击友链")) {
- String nickname = (String) requestParams.get("nickname");
- content = nickname;
- remark = "友链名称:" + nickname;
+ String content = behavior.getContent();
+ switch (behavior) {
+ case INDEX:
+ case MOMENT:
+ remark = "第" + requestParams.get("pageNum") + "页";
+ break;
+ case BLOG:
+ if (result.getCode() == 200) {
+ BlogDetail blog = (BlogDetail) result.getData();
+ String title = blog.getTitle();
+ content = title;
+ remark = "文章标题:" + title;
+ }
+ break;
+ case SEARCH:
+ if (result.getCode() == 200) {
+ String query = (String) requestParams.get("query");
+ content = query;
+ remark = "搜索内容:" + query;
+ }
+ break;
+ case CATEGORY:
+ String categoryName = (String) requestParams.get("categoryName");
+ content = categoryName;
+ remark = "分类名称:" + categoryName + ",第" + requestParams.get("pageNum") + "页";
+ break;
+ case TAG:
+ String tagName = (String) requestParams.get("tagName");
+ content = tagName;
+ remark = "标签名称:" + tagName + ",第" + requestParams.get("pageNum") + "页";
+ break;
+ case CLICK_FRIEND:
+ String nickname = (String) requestParams.get("nickname");
+ content = nickname;
+ remark = "友链名称:" + nickname;
+ break;
}
- map.put("remark", remark);
- map.put("content", content);
- return map;
+ return new VisitLogRemark(content, remark);
}
}
\ No newline at end of file
diff --git a/blog-api/src/main/java/top/naccl/config/JwtFilter.java b/blog-api/src/main/java/top/naccl/config/JwtFilter.java
index b4ef6de8a..85041db7b 100644
--- a/blog-api/src/main/java/top/naccl/config/JwtFilter.java
+++ b/blog-api/src/main/java/top/naccl/config/JwtFilter.java
@@ -31,7 +31,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//后台管理路径外的请求直接跳过
- if (!request.getRequestURI().startsWith("/admin")) {
+ if (!request.getRequestURI().startsWith(request.getContextPath() + "/admin")) {
filterChain.doFilter(request, servletResponse);
return;
}
diff --git a/blog-api/src/main/java/top/naccl/config/JwtLoginFilter.java b/blog-api/src/main/java/top/naccl/config/JwtLoginFilter.java
index 4e883a778..5b6562083 100644
--- a/blog-api/src/main/java/top/naccl/config/JwtLoginFilter.java
+++ b/blog-api/src/main/java/top/naccl/config/JwtLoginFilter.java
@@ -72,7 +72,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
response.setContentType("application/json;charset=utf-8");
User user = (User) authResult.getPrincipal();
user.setPassword(null);
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("user", user);
map.put("token", jwt);
Result result = Result.ok("登录成功", map);
diff --git a/blog-api/src/main/java/top/naccl/config/RestTemplateConfig.java b/blog-api/src/main/java/top/naccl/config/RestTemplateConfig.java
new file mode 100644
index 000000000..547c9e1c3
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/RestTemplateConfig.java
@@ -0,0 +1,47 @@
+package top.naccl.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+import top.naccl.config.properties.ProxyProperties;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/**
+ * RestTemplate相关的Bean配置
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@Configuration
+public class RestTemplateConfig {
+ @Autowired
+ private ProxyProperties proxyProperties;
+
+ /**
+ * 默认的RestTemplate
+ *
+ * @return
+ */
+ @Bean
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+
+ /**
+ * 配置了代理和超时时间的RestTemplate
+ *
+ * @return
+ */
+ @Bean
+ public RestTemplate restTemplateByProxy() {
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+ Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyProperties.getHost(), proxyProperties.getPort()));
+ requestFactory.setProxy(proxy);
+ requestFactory.setConnectTimeout(proxyProperties.getTimeout());
+ return new RestTemplate(requestFactory);
+ }
+}
\ No newline at end of file
diff --git a/blog-api/src/main/java/top/naccl/config/WebConfig.java b/blog-api/src/main/java/top/naccl/config/WebConfig.java
index e115683b5..3c156a470 100644
--- a/blog-api/src/main/java/top/naccl/config/WebConfig.java
+++ b/blog-api/src/main/java/top/naccl/config/WebConfig.java
@@ -1,12 +1,12 @@
package top.naccl.config;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
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 top.naccl.config.properties.UploadProperties;
import top.naccl.interceptor.AccessLimitInterceptor;
/**
@@ -18,24 +18,8 @@
public class WebConfig implements WebMvcConfigurer {
@Autowired
AccessLimitInterceptor accessLimitInterceptor;
- private String accessPath;
- private String resourcesLocations;
-
- /**
- * @param accessPath 请求地址映射
- */
- @Value("${upload.access.path}")
- public void setAccessPath(String accessPath) {
- this.accessPath = accessPath;
- }
-
- /**
- * @param resourcesLocations 本地文件路径映射
- */
- @Value("${upload.resources.locations}")
- public void setResourcesLocations(String resourcesLocations) {
- this.resourcesLocations = resourcesLocations;
- }
+ @Autowired
+ UploadProperties uploadProperties;
/**
* 跨域请求
@@ -68,6 +52,6 @@ public void addInterceptors(InterceptorRegistry registry) {
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler(accessPath).addResourceLocations(resourcesLocations);
+ registry.addResourceHandler(uploadProperties.getAccessPath()).addResourceLocations(uploadProperties.getResourcesLocations());
}
}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/BlogProperties.java b/blog-api/src/main/java/top/naccl/config/properties/BlogProperties.java
new file mode 100644
index 000000000..bef32f82f
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/BlogProperties.java
@@ -0,0 +1,39 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 博客配置(目前用于评论提醒模板中的超链接)
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "blog")
+public class BlogProperties {
+ /**
+ * 博客名称
+ */
+ private String name;
+ /**
+ * 博客后端接口URL
+ */
+ private String api;
+ /**
+ * 博客前端后台管理URL
+ */
+ private String cms;
+ /**
+ * 博客前端前台页面URL
+ */
+ private String view;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/GithubProperties.java b/blog-api/src/main/java/top/naccl/config/properties/GithubProperties.java
new file mode 100644
index 000000000..2335d3f2d
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/GithubProperties.java
@@ -0,0 +1,39 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * GitHub配置(目前用于评论中QQ头像的图床)
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "upload.github")
+public class GithubProperties {
+ /**
+ * GitHub token
+ */
+ private String token;
+ /**
+ * GitHub username
+ */
+ private String username;
+ /**
+ * GitHub 仓库名
+ */
+ private String repos;
+ /**
+ * GitHub 仓库路径
+ */
+ private String reposPath;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/ProxyProperties.java b/blog-api/src/main/java/top/naccl/config/properties/ProxyProperties.java
new file mode 100644
index 000000000..59b45aad0
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/ProxyProperties.java
@@ -0,0 +1,38 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+
+/**
+ * 代理配置(目前用于RestTemplate发送tg消息)
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "http.proxy.server")
+public class ProxyProperties {
+ /**
+ * 代理服务器地址
+ */
+ private String host;
+ /**
+ * 代理服务器端口
+ */
+ private Integer port;
+ /**
+ * 连接超时(单位毫秒),通常不应该为0,0为无限超时时间,-1为系统的默认超时时间
+ *
+ * @see SimpleClientHttpRequestFactory#setConnectTimeout(int)
+ */
+ private Integer timeout;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/TelegramProperties.java b/blog-api/src/main/java/top/naccl/config/properties/TelegramProperties.java
new file mode 100644
index 000000000..765979e2d
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/TelegramProperties.java
@@ -0,0 +1,48 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Telegram配置
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "tg.bot")
+public class TelegramProperties {
+ /**
+ * Telegram bot的api,默认是https://api.telegram.org/bot
+ * 如果使用自己的反代,可以修改它
+ */
+ private String api;
+ /**
+ * bot的token,可以从 @BotFather 处获取
+ */
+ private String token;
+ /**
+ * 自己账号和bot的聊天会话id
+ */
+ private String chatId;
+ /**
+ * 是否使用代理
+ */
+ private Boolean useProxy;
+ /**
+ * 是否使用反向代理
+ */
+ private Boolean useReverseProxy;
+ /**
+ * 反向代理URL
+ */
+ private String reverseProxyUrl;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/UploadProperties.java b/blog-api/src/main/java/top/naccl/config/properties/UploadProperties.java
new file mode 100644
index 000000000..4e3136e14
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/UploadProperties.java
@@ -0,0 +1,35 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 静态文件上传访问路径配置(目前用于评论中QQ头像的本地存储)
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "upload.file")
+public class UploadProperties {
+ /**
+ * 本地文件路径
+ */
+ private String path;
+ /**
+ * 请求地址映射
+ */
+ private String accessPath;
+ /**
+ * 本地文件路径映射
+ */
+ private String resourcesLocations;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/properties/UpyunProperties.java b/blog-api/src/main/java/top/naccl/config/properties/UpyunProperties.java
new file mode 100644
index 000000000..fea529ab5
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/config/properties/UpyunProperties.java
@@ -0,0 +1,43 @@
+package top.naccl.config.properties;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 又拍云配置(目前用于评论中QQ头像的图床)
+ *
+ * @author: Naccl
+ * @date: 2022年05月26日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+@Configuration
+@ConfigurationProperties(prefix = "upload.upyun")
+public class UpyunProperties {
+ /**
+ * 又拍云存储空间名称
+ */
+ private String bucketName;
+ /**
+ * 操作员名称
+ */
+ private String username;
+ /**
+ * 操作员密码
+ */
+ private String password;
+ /**
+ * 存储路径
+ */
+ private String path;
+ /**
+ * CDN访问域名
+ */
+ private String domain;
+}
diff --git a/blog-api/src/main/java/top/naccl/constant/CommentConstants.java b/blog-api/src/main/java/top/naccl/constant/CommentConstants.java
new file mode 100644
index 000000000..1a3f837b3
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/constant/CommentConstants.java
@@ -0,0 +1,19 @@
+package top.naccl.constant;
+
+/**
+ * 评论相关常量
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+public class CommentConstants {
+ /**
+ * 评论提醒方式-Telegram
+ */
+ public static final String TELEGRAM = "tg";
+
+ /**
+ * 评论提醒方式-邮件
+ */
+ public static final String MAIL = "mail";
+}
diff --git a/blog-api/src/main/java/top/naccl/constant/JwtConstants.java b/blog-api/src/main/java/top/naccl/constant/JwtConstants.java
new file mode 100644
index 000000000..796068434
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/constant/JwtConstants.java
@@ -0,0 +1,14 @@
+package top.naccl.constant;
+
+/**
+ * JWT常量
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+public class JwtConstants {
+ /**
+ * 博主token前缀
+ */
+ public static final String ADMIN_PREFIX = "admin:";
+}
diff --git a/blog-api/src/main/java/top/naccl/constant/PageConstants.java b/blog-api/src/main/java/top/naccl/constant/PageConstants.java
new file mode 100644
index 000000000..a788952d4
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/constant/PageConstants.java
@@ -0,0 +1,22 @@
+package top.naccl.constant;
+
+/**
+ * 页面相关常量
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+public class PageConstants {
+ /**
+ * 普通博客文章页面
+ */
+ public static final int BLOG = 0;
+ /**
+ * 关于我页面
+ */
+ public static final int ABOUT = 1;
+ /**
+ * 友链页面
+ */
+ public static final int FRIEND = 2;
+}
diff --git a/blog-api/src/main/java/top/naccl/config/RedisKeyConfig.java b/blog-api/src/main/java/top/naccl/constant/RedisKeyConstants.java
similarity index 54%
rename from blog-api/src/main/java/top/naccl/config/RedisKeyConfig.java
rename to blog-api/src/main/java/top/naccl/constant/RedisKeyConstants.java
index 4b0be95c4..af39c86d3 100644
--- a/blog-api/src/main/java/top/naccl/config/RedisKeyConfig.java
+++ b/blog-api/src/main/java/top/naccl/constant/RedisKeyConstants.java
@@ -1,38 +1,54 @@
-package top.naccl.config;
+package top.naccl.constant;
/**
* @Description: Redis key配置
* @Author: Naccl
* @Date: 2020年09月27日
*/
-public class RedisKeyConfig {
- //首页博客简介列表 分页对象key:homeBlogInfoList : {{1,"第一页的缓存"},{2,"第二页的缓存"}}
+public class RedisKeyConstants {
+ /**
+ * 首页博客简介列表 分页对象key
+ * homeBlogInfoList : {{1,"第一页的缓存"},{2,"第二页的缓存"}}
+ */
public static final String HOME_BLOG_INFO_LIST = "homeBlogInfoList";
-
- //分类名列表key
+ /**
+ * 分类名列表key
+ */
public static final String CATEGORY_NAME_LIST = "categoryNameList";
-
- //标签云列表key
+ /**
+ * 标签云列表key
+ */
public static final String TAG_CLOUD_LIST = "tagCloudList";
-
- //站点信息key
+ /**
+ * 站点信息key
+ */
public static final String SITE_INFO_MAP = "siteInfoMap";
-
- //最新推荐博客key
+ /**
+ * 最新推荐博客key
+ */
public static final String NEW_BLOG_LIST = "newBlogList";
-
- //关于我页面key
+ /**
+ * 关于我页面key
+ */
public static final String ABOUT_INFO_MAP = "aboutInfoMap";
-
- //友链页面信息key
+ /**
+ * 友链页面信息key
+ */
public static final String FRIEND_INFO_MAP = "friendInfoMap";
-
- //博客归档key
+ /**
+ * 博客归档key
+ */
public static final String ARCHIVE_BLOG_MAP = "archiveBlogMap";
-
- //博客访问量key
+ /**
+ * 博客访问量key
+ */
public static final String BLOG_VIEWS_MAP = "blogViewsMap";
-
- //访客标识码key
+ /**
+ * 访客标识码key
+ */
public static final String IDENTIFICATION_SET = "identificationSet";
+ /**
+ * QQ号与对应头像URL key
+ */
+ public static final String QQ_AVATAR_URL_MAP = "qqAvatarUrlMap";
}
diff --git a/blog-api/src/main/java/top/naccl/constant/SiteSettingConstants.java b/blog-api/src/main/java/top/naccl/constant/SiteSettingConstants.java
new file mode 100644
index 000000000..fe3c79e91
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/constant/SiteSettingConstants.java
@@ -0,0 +1,21 @@
+package top.naccl.constant;
+
+/**
+ * 站点设置常量
+ *
+ * @author: Naccl
+ * @date: 2022年01月28日
+ */
+public class SiteSettingConstants {
+ public static final String COPYRIGHT = "copyright";
+ public static final String AVATAR = "avatar";
+ public static final String NAME = "name";
+ public static final String GITHUB = "github";
+ public static final String TELEGRAM = "telegram";
+ public static final String QQ = "qq";
+ public static final String BILIBILI = "bilibili";
+ public static final String NETEASE = "netease";
+ public static final String EMAIL = "email";
+ public static final String FAVORITE = "favorite";
+ public static final String ROLL_TEXT = "rollText";
+}
diff --git a/blog-api/src/main/java/top/naccl/constant/UploadConstants.java b/blog-api/src/main/java/top/naccl/constant/UploadConstants.java
new file mode 100644
index 000000000..556f140de
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/constant/UploadConstants.java
@@ -0,0 +1,26 @@
+package top.naccl.constant;
+
+/**
+ * 上传文件相关常量
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+public class UploadConstants {
+ /**
+ * 评论中QQ头像存储方式-本地
+ */
+ public static final String LOCAL = "local";
+ /**
+ * 评论中QQ头像存储方式-GitHub
+ */
+ public static final String GITHUB = "github";
+ /**
+ * 评论中QQ头像存储方式-又拍云
+ */
+ public static final String UPYUN = "upyun";
+ /**
+ * 图片ContentType
+ */
+ public static final String IMAGE = "image";
+}
diff --git a/blog-api/src/main/java/top/naccl/controller/AboutController.java b/blog-api/src/main/java/top/naccl/controller/AboutController.java
index 674973eb9..2a91390da 100644
--- a/blog-api/src/main/java/top/naccl/controller/AboutController.java
+++ b/blog-api/src/main/java/top/naccl/controller/AboutController.java
@@ -4,6 +4,7 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.Result;
import top.naccl.service.AboutService;
@@ -22,7 +23,7 @@ public class AboutController {
*
* @return
*/
- @VisitLogger(behavior = "访问页面", content = "关于我")
+ @VisitLogger(VisitBehavior.ABOUT)
@GetMapping("/about")
public Result about() {
return Result.ok("获取成功", aboutService.getAboutInfo());
diff --git a/blog-api/src/main/java/top/naccl/controller/ArchiveController.java b/blog-api/src/main/java/top/naccl/controller/ArchiveController.java
index bad64e1e9..52fa2448d 100644
--- a/blog-api/src/main/java/top/naccl/controller/ArchiveController.java
+++ b/blog-api/src/main/java/top/naccl/controller/ArchiveController.java
@@ -4,6 +4,7 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.Result;
import top.naccl.service.BlogService;
@@ -24,7 +25,7 @@ public class ArchiveController {
*
* @return
*/
- @VisitLogger(behavior = "访问页面", content = "文章归档")
+ @VisitLogger(VisitBehavior.ARCHIVE)
@GetMapping("/archives")
public Result archives() {
Map archiveBlogMap = blogService.getArchiveBlogAndCountByIsPublished();
diff --git a/blog-api/src/main/java/top/naccl/controller/BlogController.java b/blog-api/src/main/java/top/naccl/controller/BlogController.java
index 664d00531..f284e93bf 100644
--- a/blog-api/src/main/java/top/naccl/controller/BlogController.java
+++ b/blog-api/src/main/java/top/naccl/controller/BlogController.java
@@ -8,7 +8,9 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.constant.JwtConstants;
import top.naccl.entity.User;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.dto.BlogPassword;
import top.naccl.model.vo.BlogDetail;
import top.naccl.model.vo.BlogInfo;
@@ -40,7 +42,7 @@ public class BlogController {
* @param pageNum 页码
* @return
*/
- @VisitLogger(behavior = "访问页面", content = "首页")
+ @VisitLogger(VisitBehavior.INDEX)
@GetMapping("/blogs")
public Result blogs(@RequestParam(defaultValue = "1") Integer pageNum) {
PageResult pageResult = blogService.getBlogInfoListByIsPublished(pageNum);
@@ -54,7 +56,7 @@ public Result blogs(@RequestParam(defaultValue = "1") Integer pageNum) {
* @param jwt 密码保护文章的访问Token
* @return
*/
- @VisitLogger(behavior = "查看博客")
+ @VisitLogger(VisitBehavior.BLOG)
@GetMapping("/blog")
public Result getBlog(@RequestParam Long id,
@RequestHeader(value = "Authorization", defaultValue = "") String jwt) {
@@ -64,13 +66,15 @@ public Result getBlog(@RequestParam Long id,
if (JwtUtils.judgeTokenIsExist(jwt)) {
try {
String subject = JwtUtils.getTokenBody(jwt).getSubject();
- if (subject.startsWith("admin:")) {//博主身份Token
- String username = subject.replace("admin:", "");
+ if (subject.startsWith(JwtConstants.ADMIN_PREFIX)) {
+ //博主身份Token
+ String username = subject.replace(JwtConstants.ADMIN_PREFIX, "");
User admin = (User) userService.loadUserByUsername(username);
if (admin == null) {
return Result.create(403, "博主身份Token已失效,请重新登录!");
}
- } else {//经密码验证后的Token
+ } else {
+ //经密码验证后的Token
Long tokenBlogId = Long.parseLong(subject);
//博客id不匹配,验证不通过,可能博客id改变或客户端传递了其它密码保护文章的Token
if (!tokenBlogId.equals(id)) {
@@ -96,7 +100,7 @@ public Result getBlog(@RequestParam Long id,
* @param blogPassword 博客id、密码
* @return
*/
- @VisitLogger(behavior = "校验博客密码")
+ @VisitLogger(VisitBehavior.CHECK_PASSWORD)
@PostMapping("/checkBlogPassword")
public Result checkBlogPassword(@RequestBody BlogPassword blogPassword) {
String password = blogService.getBlogPassword(blogPassword.getBlogId());
@@ -115,7 +119,7 @@ public Result checkBlogPassword(@RequestBody BlogPassword blogPassword) {
* @param query 关键字字符串
* @return
*/
- @VisitLogger(behavior = "搜索博客")
+ @VisitLogger(VisitBehavior.SEARCH)
@GetMapping("/searchBlog")
public Result searchBlog(@RequestParam String query) {
//校验关键字字符串合法性
diff --git a/blog-api/src/main/java/top/naccl/controller/CategoryController.java b/blog-api/src/main/java/top/naccl/controller/CategoryController.java
index caaa6e014..53b7fe75c 100644
--- a/blog-api/src/main/java/top/naccl/controller/CategoryController.java
+++ b/blog-api/src/main/java/top/naccl/controller/CategoryController.java
@@ -5,6 +5,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.BlogInfo;
import top.naccl.model.vo.PageResult;
import top.naccl.model.vo.Result;
@@ -27,7 +28,7 @@ public class CategoryController {
* @param pageNum 页码
* @return
*/
- @VisitLogger(behavior = "查看分类")
+ @VisitLogger(VisitBehavior.CATEGORY)
@GetMapping("/category")
public Result category(@RequestParam String categoryName,
@RequestParam(defaultValue = "1") Integer pageNum) {
diff --git a/blog-api/src/main/java/top/naccl/controller/CommentController.java b/blog-api/src/main/java/top/naccl/controller/CommentController.java
index 3d533db76..2961603c2 100644
--- a/blog-api/src/main/java/top/naccl/controller/CommentController.java
+++ b/blog-api/src/main/java/top/naccl/controller/CommentController.java
@@ -3,8 +3,6 @@
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -12,26 +10,20 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.AccessLimit;
+import top.naccl.constant.JwtConstants;
import top.naccl.entity.User;
import top.naccl.model.dto.Comment;
-import top.naccl.model.vo.FriendInfo;
import top.naccl.model.vo.PageComment;
import top.naccl.model.vo.PageResult;
import top.naccl.model.vo.Result;
-import top.naccl.service.AboutService;
-import top.naccl.service.BlogService;
import top.naccl.service.CommentService;
-import top.naccl.service.FriendService;
import top.naccl.service.impl.UserServiceImpl;
-import top.naccl.util.HashUtils;
-import top.naccl.util.IpAddressUtils;
import top.naccl.util.JwtUtils;
-import top.naccl.util.MailUtils;
-import top.naccl.util.QQInfoUtils;
import top.naccl.util.StringUtils;
+import top.naccl.util.comment.CommentUtils;
+import top.naccl.enums.CommentOpenStateEnum;
import javax.servlet.http.HttpServletRequest;
-import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -45,35 +37,9 @@ public class CommentController {
@Autowired
CommentService commentService;
@Autowired
- BlogService blogService;
- @Autowired
- AboutService aboutService;
- @Autowired
UserServiceImpl userService;
@Autowired
- FriendService friendService;
- @Autowired
- MailProperties mailProperties;
- @Autowired
- MailUtils mailUtils;
- private String blogName;
- private String cmsUrl;
- private String websiteUrl;
-
- @Value("${custom.blog.name}")
- public void setBlogName(String blogName) {
- this.blogName = blogName;
- }
-
- @Value("${custom.url.cms}")
- public void setCmsUrl(String cmsUrl) {
- this.cmsUrl = cmsUrl;
- }
-
- @Value("${custom.url.website}")
- public void setWebsiteUrl(String websiteUrl) {
- this.websiteUrl = websiteUrl;
- }
+ CommentUtils commentUtils;
/**
* 根据页面分页查询评论列表
@@ -91,44 +57,51 @@ public Result comments(@RequestParam Integer page,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestHeader(value = "Authorization", defaultValue = "") String jwt) {
- int judgeResult = judgeCommentEnabled(page, blogId);
- if (judgeResult == 2) {
- return Result.create(404, "该博客不存在");
- } else if (judgeResult == 1) {
- return Result.create(403, "评论已关闭");
- } else if (judgeResult == 3) {//文章受密码保护,需要验证Token
- if (JwtUtils.judgeTokenIsExist(jwt)) {
- try {
- String subject = JwtUtils.getTokenBody(jwt).getSubject();
- if (subject.startsWith("admin:")) {//博主身份Token
- String username = subject.replace("admin:", "");
- User admin = (User) userService.loadUserByUsername(username);
- if (admin == null) {
- return Result.create(403, "博主身份Token已失效,请重新登录!");
- }
- } else {//经密码验证后的Token
- Long tokenBlogId = Long.parseLong(subject);
- //博客id不匹配,验证不通过,可能博客id改变或客户端传递了其它密码保护文章的Token
- if (!tokenBlogId.equals(blogId)) {
- return Result.create(403, "Token不匹配,请重新验证密码!");
+ CommentOpenStateEnum openState = commentUtils.judgeCommentState(page, blogId);
+ switch (openState) {
+ case NOT_FOUND:
+ return Result.create(404, "该博客不存在");
+ case CLOSE:
+ return Result.create(403, "评论已关闭");
+ case PASSWORD:
+ //文章受密码保护,需要验证Token
+ if (JwtUtils.judgeTokenIsExist(jwt)) {
+ try {
+ String subject = JwtUtils.getTokenBody(jwt).getSubject();
+ if (subject.startsWith(JwtConstants.ADMIN_PREFIX)) {
+ //博主身份Token
+ String username = subject.replace(JwtConstants.ADMIN_PREFIX, "");
+ User admin = (User) userService.loadUserByUsername(username);
+ if (admin == null) {
+ return Result.create(403, "博主身份Token已失效,请重新登录!");
+ }
+ } else {
+ //经密码验证后的Token
+ Long tokenBlogId = Long.parseLong(subject);
+ //博客id不匹配,验证不通过,可能博客id改变或客户端传递了其它密码保护文章的Token
+ if (!tokenBlogId.equals(blogId)) {
+ return Result.create(403, "Token不匹配,请重新验证密码!");
+ }
}
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Result.create(403, "Token已失效,请重新验证密码!");
}
- } catch (Exception e) {
- e.printStackTrace();
- return Result.create(403, "Token已失效,请重新验证密码!");
+ } else {
+ return Result.create(403, "此文章受密码保护,请验证密码!");
}
- } else {
- return Result.create(403, "此文章受密码保护,请验证密码!");
- }
+ break;
+ default:
+ break;
}
//查询该页面所有评论的数量
Integer allComment = commentService.countByPageAndIsPublished(page, blogId, null);
//查询该页面公开评论的数量
Integer openComment = commentService.countByPageAndIsPublished(page, blogId, true);
PageHelper.startPage(pageNum, pageSize);
- PageInfo pageInfo = new PageInfo(commentService.getPageCommentList(page, blogId, (long) -1));
+ PageInfo pageInfo = new PageInfo(commentService.getPageCommentList(page, blogId, -1L));
PageResult pageResult = new PageResult(pageInfo.getPages(), pageInfo.getList());
- Map map = new HashMap();
+ Map map = new HashMap(8);
map.put("allComment", allComment);
map.put("closeComment", allComment - openComment);
map.put("comments", pageResult);
@@ -136,43 +109,7 @@ public Result comments(@RequestParam Integer page,
}
/**
- * 查询对应页面评论是否开启
- *
- * @param page 页面分类(0普通文章,1关于我,2友链)
- * @param blogId 如果page==0,需要博客id参数,校验文章是否公开状态
- * @return 0:公开可查询状态 1:评论关闭 2:该博客不存在 3:文章受密码保护
- */
- private int judgeCommentEnabled(Integer page, Long blogId) {
- if (page == 0) {//普通博客
- Boolean commentEnabled = blogService.getCommentEnabledByBlogId(blogId);
- Boolean published = blogService.getPublishedByBlogId(blogId);
- if (commentEnabled == null || published == null) {//未查询到此博客
- return 2;
- } else if (!published) {//博客未公开
- return 2;
- } else if (!commentEnabled) {//博客评论已关闭
- return 1;
- }
- //判断文章是否存在密码
- String password = blogService.getBlogPassword(blogId);
- if (!"".equals(password)) {
- return 3;
- }
- } else if (page == 1) {//关于我页面
- if (!aboutService.getAboutCommentEnabled()) {//页面评论已关闭
- return 1;
- }
- } else if (page == 2) {//友链页面
- FriendInfo friendInfo = friendService.getFriendInfo(true, false);
- if (!friendInfo.getCommentEnabled()) {
- return 1;
- }
- }
- return 0;
- }
-
- /**
- * 提交评论 又长又臭 能用就不改了:) https://cdn.jsdelivr.net/gh/Naccl/blog-resource/img/1stLaw4Coding.jpg
+ * 提交评论 又长又臭 能用就不改了:)
* 单个ip,30秒内允许提交1次评论
*
* @param comment 评论DTO
@@ -209,254 +146,95 @@ public Result postComment(@RequestBody Comment comment,
}
}
//判断是否可评论
- int judgeResult = judgeCommentEnabled(comment.getPage(), comment.getBlogId());
- if (judgeResult == 2) {
- return Result.create(404, "该博客不存在");
- } else if (judgeResult == 1) {
- return Result.create(403, "评论已关闭");
- } else if (judgeResult == 3) {//文章受密码保护
- //验证Token合法性
- if (JwtUtils.judgeTokenIsExist(jwt)) {
- String subject;
- try {
- subject = JwtUtils.getTokenBody(jwt).getSubject();
- } catch (Exception e) {
- e.printStackTrace();
- return Result.create(403, "Token已失效,请重新验证密码!");
- }
- //博主评论,不受密码保护限制,根据博主信息设置评论属性
- if (subject.startsWith("admin:")) {
- //Token验证通过,获取Token中用户名
- String username = subject.replace("admin:", "");
- User admin = (User) userService.loadUserByUsername(username);
- if (admin == null) {
- return Result.create(403, "博主身份Token已失效,请重新登录!");
- }
- setAdminComment(comment, request, admin);
- isVisitorComment = false;
- } else {//普通访客经文章密码验证后携带Token
- //对访客的评论昵称、邮箱合法性校验
- if (StringUtils.isEmpty(comment.getNickname(), comment.getEmail()) || comment.getNickname().length()> 15) {
- return Result.error("参数有误");
+ CommentOpenStateEnum openState = commentUtils.judgeCommentState(comment.getPage(), comment.getBlogId());
+ switch (openState) {
+ case NOT_FOUND:
+ return Result.create(404, "该博客不存在");
+ case CLOSE:
+ return Result.create(403, "评论已关闭");
+ case PASSWORD:
+ //文章受密码保护
+ //验证Token合法性
+ if (JwtUtils.judgeTokenIsExist(jwt)) {
+ String subject;
+ try {
+ subject = JwtUtils.getTokenBody(jwt).getSubject();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Result.create(403, "Token已失效,请重新验证密码!");
}
- //对于受密码保护的文章,则Token是必须的
- Long tokenBlogId = Long.parseLong(subject);
- //博客id不匹配,验证不通过,可能博客id改变或客户端传递了其它密码保护文章的Token
- if (!tokenBlogId.equals(comment.getBlogId())) {
- return Result.create(403, "Token不匹配,请重新验证密码!");
+ //博主评论,不受密码保护限制,根据博主信息设置评论属性
+ if (subject.startsWith(JwtConstants.ADMIN_PREFIX)) {
+ //Token验证通过,获取Token中用户名
+ String username = subject.replace(JwtConstants.ADMIN_PREFIX, "");
+ User admin = (User) userService.loadUserByUsername(username);
+ if (admin == null) {
+ return Result.create(403, "博主身份Token已失效,请重新登录!");
+ }
+ commentUtils.setAdminComment(comment, request, admin);
+ isVisitorComment = false;
+ } else {//普通访客经文章密码验证后携带Token
+ //对访客的评论昵称、邮箱合法性校验
+ if (StringUtils.isEmpty(comment.getNickname(), comment.getEmail()) || comment.getNickname().length()> 15) {
+ return Result.error("参数有误");
+ }
+ //对于受密码保护的文章,则Token是必须的
+ Long tokenBlogId = Long.parseLong(subject);
+ //博客id不匹配,验证不通过,可能博客id改变或客户端传递了其它密码保护文章的Token
+ if (!tokenBlogId.equals(comment.getBlogId())) {
+ return Result.create(403, "Token不匹配,请重新验证密码!");
+ }
+ commentUtils.setVisitorComment(comment, request);
+ isVisitorComment = true;
}
- setVisitorComment(comment, request);
- isVisitorComment = true;
- }
- } else {//不存在Token则无评论权限
- return Result.create(403, "此文章受密码保护,请验证密码!");
- }
- } else if (judgeResult == 0) {//普通文章
- //有Token则为博主评论,或文章原先为密码保护,后取消保护,但客户端仍存在Token
- if (JwtUtils.judgeTokenIsExist(jwt)) {
- String subject;
- try {
- subject = JwtUtils.getTokenBody(jwt).getSubject();
- } catch (Exception e) {
- e.printStackTrace();
- return Result.create(403, "Token已失效,请重新验证密码");
+ } else {//不存在Token则无评论权限
+ return Result.create(403, "此文章受密码保护,请验证密码!");
}
- //博主评论,根据博主信息设置评论属性
- if (subject.startsWith("admin:")) {
- //Token验证通过,获取Token中用户名
- String username = subject.replace("admin:", "");
- User admin = (User) userService.loadUserByUsername(username);
- if (admin == null) {
- return Result.create(403, "博主身份Token已失效,请重新登录!");
+ break;
+ case OPEN:
+ //评论正常开放
+ //有Token则为博主评论,或文章原先为密码保护,后取消保护,但客户端仍存在Token
+ if (JwtUtils.judgeTokenIsExist(jwt)) {
+ String subject;
+ try {
+ subject = JwtUtils.getTokenBody(jwt).getSubject();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Result.create(403, "Token已失效,请重新验证密码");
}
- setAdminComment(comment, request, admin);
- isVisitorComment = false;
- } else {//文章原先为密码保护,后取消保护,但客户端仍存在Token,则忽略Token
+ //博主评论,根据博主信息设置评论属性
+ if (subject.startsWith(JwtConstants.ADMIN_PREFIX)) {
+ //Token验证通过,获取Token中用户名
+ String username = subject.replace(JwtConstants.ADMIN_PREFIX, "");
+ User admin = (User) userService.loadUserByUsername(username);
+ if (admin == null) {
+ return Result.create(403, "博主身份Token已失效,请重新登录!");
+ }
+ commentUtils.setAdminComment(comment, request, admin);
+ isVisitorComment = false;
+ } else {//文章原先为密码保护,后取消保护,但客户端仍存在Token,则忽略Token
+ //对访客的评论昵称、邮箱合法性校验
+ if (StringUtils.isEmpty(comment.getNickname(), comment.getEmail()) || comment.getNickname().length()> 15) {
+ return Result.error("参数有误");
+ }
+ commentUtils.setVisitorComment(comment, request);
+ isVisitorComment = true;
+ }
+ } else {
+ //访客评论
//对访客的评论昵称、邮箱合法性校验
if (StringUtils.isEmpty(comment.getNickname(), comment.getEmail()) || comment.getNickname().length()> 15) {
return Result.error("参数有误");
}
- setVisitorComment(comment, request);
+ commentUtils.setVisitorComment(comment, request);
isVisitorComment = true;
}
- } else {//访客评论
- //对访客的评论昵称、邮箱合法性校验
- if (StringUtils.isEmpty(comment.getNickname(), comment.getEmail()) || comment.getNickname().length()> 15) {
- return Result.error("参数有误");
- }
- setVisitorComment(comment, request);
- isVisitorComment = true;
- }
+ break;
+ default:
+ break;
}
commentService.saveComment(comment);
- judgeSendMail(comment, isVisitorComment, parentComment);
+ commentUtils.judgeSendNotify(comment, isVisitorComment, parentComment);
return Result.ok("评论成功");
}
-
- /**
- * 设置博主评论属性
- *
- * @param comment 评论DTO
- * @param request 获取ip
- * @param admin 博主信息
- */
- private void setAdminComment(Comment comment, HttpServletRequest request, User admin) {
- comment.setAdminComment(true);
- comment.setCreateTime(new Date());
- comment.setPublished(true);
- comment.setAvatar(admin.getAvatar());
- comment.setWebsite("/");
- comment.setNickname(admin.getNickname());
- comment.setEmail(admin.getEmail());
- comment.setIp(IpAddressUtils.getIpAddress(request));
- comment.setNotice(false);
- }
-
- /**
- * 设置访客评论属性
- *
- * @param comment 评论DTO
- * @param request 用于获取ip
- */
- private void setVisitorComment(Comment comment, HttpServletRequest request) {
- String commentNickname = comment.getNickname();
- try {
- if (QQInfoUtils.isQQNumber(commentNickname)) {
- comment.setQq(commentNickname);
- comment.setNickname(QQInfoUtils.getQQNickname(commentNickname));
- comment.setAvatar(QQInfoUtils.getQQAvatarURLByGithubUpload(commentNickname));
- } else {
- comment.setNickname(comment.getNickname().trim());
- setCommentRandomAvatar(comment);
- }
- } catch (Exception e) {
- e.printStackTrace();
- comment.setNickname(comment.getNickname().trim());
- setCommentRandomAvatar(comment);
- }
-
- //set website
- String website = comment.getWebsite().trim();
- if (!"".equals(website) && !website.startsWith("http://") && !website.startsWith("https://")) {
- website = "http://" + website;
- }
- comment.setAdminComment(false);
- comment.setCreateTime(new Date());
- comment.setPublished(true);//默认不需要审核
- comment.setWebsite(website);
- comment.setEmail(comment.getEmail().trim());
- comment.setIp(IpAddressUtils.getIpAddress(request));
- }
-
- /**
- * 对于昵称不是QQ号的评论,根据昵称Hash设置头像
- *
- * @param comment 评论DTO
- */
- private void setCommentRandomAvatar(Comment comment) {
- //设置随机头像
- long nicknameHash = HashUtils.getMurmurHash32(comment.getNickname());//根据评论昵称取Hash,保证每一个昵称对应一个头像
- long num = nicknameHash % 6 + 1;//计算对应的头像
- String avatar = "/img/comment-avatar/" + num + ".jpg";
- comment.setAvatar(avatar);
- }
-
- /**
- * 判断是否发送邮件
- * 6种情况:
- * 1.我以父评论提交:不用邮件提醒
- * 2.我回复我自己:不用邮件提醒
- * 3.我回复访客的评论:只提醒该访客
- * 4.访客以父评论提交:只提醒我自己
- * 5.访客回复我的评论:只提醒我自己
- * 6.访客回复访客的评论(即使是他自己先前的评论):提醒我自己和他回复的评论
- *
- * @param comment 当前评论
- * @param isVisitorComment 是否访客评论
- * @param parentComment 父评论
- */
- private void judgeSendMail(Comment comment, boolean isVisitorComment, top.naccl.entity.Comment parentComment) {
- if (parentComment != null && !parentComment.getAdminComment() && parentComment.getNotice()) {
- //我回复访客的评论,且对方接收提醒,邮件提醒对方(3)
- //访客回复访客的评论(即使是他自己先前的评论),且对方接收提醒,邮件提醒对方(6)
- sendMailToParentComment(parentComment, comment);
- }
- if (isVisitorComment) {
- //访客以父评论提交,只邮件提醒我自己(4)
- //访客回复我的评论,邮件提醒我自己(5)
- //访客回复访客的评论,不管对方是否接收提醒,都要提醒我有新评论(6)
- sendMailToMe(comment);
- }
- }
-
- /**
- * 发送邮件提醒回复对象
- *
- * @param parentComment 父评论
- * @param comment 当前评论
- */
- private void sendMailToParentComment(top.naccl.entity.Comment parentComment, Comment comment) {
- String path = "";
- String title = "";
- if (comment.getPage() == 0) {
- //普通博客
- title = parentComment.getBlog().getTitle();
- path = "/blog/" + comment.getBlogId();
- } else if (comment.getPage() == 1) {
- //关于我页面
- title = "关于我";
- path = "/about";
- } else if (comment.getPage() == 2) {
- //友链页面
- title = "友人帐";
- path = "/friends";
- }
- Map map = new HashMap();
- map.put("parentNickname", parentComment.getNickname());
- map.put("nickname", comment.getNickname());
- map.put("title", title);
- map.put("time", comment.getCreateTime());
- map.put("parentContent", parentComment.getContent());
- map.put("content", comment.getContent());
- map.put("url", websiteUrl + path);
- String toAccount = parentComment.getEmail();
- String subject = "您在 " + blogName + " 的评论有了新回复";
- mailUtils.sendHtmlTemplateMail(map, toAccount, subject, "guest.html");
- }
-
- /**
- * 发送邮件提醒我自己
- *
- * @param comment 当前评论
- */
- private void sendMailToMe(Comment comment) {
- String path = "";
- String title = "";
- if (comment.getPage() == 0) {
- //普通博客
- title = blogService.getTitleByBlogId(comment.getBlogId());
- path = "/blog/" + comment.getBlogId();
- } else if (comment.getPage() == 1) {
- //关于我页面
- title = "关于我";
- path = "/about";
- } else if (comment.getPage() == 2) {
- //友链页面
- title = "友人帐";
- path = "/friends";
- }
- Map map = new HashMap();
- map.put("title", title);
- map.put("time", comment.getCreateTime());
- map.put("nickname", comment.getNickname());
- map.put("content", comment.getContent());
- map.put("ip", comment.getIp());
- map.put("email", comment.getEmail());
- map.put("status", comment.getPublished() ? "公开" : "待审核");
- map.put("url", websiteUrl + path);
- map.put("manageUrl", cmsUrl + "/comments");
- String toAccount = mailProperties.getUsername();
- String subject = blogName + " 收到新评论";
- mailUtils.sendHtmlTemplateMail(map, toAccount, subject, "owner.html");
- }
}
\ No newline at end of file
diff --git a/blog-api/src/main/java/top/naccl/controller/FriendController.java b/blog-api/src/main/java/top/naccl/controller/FriendController.java
index 2bf665382..f9773bf84 100644
--- a/blog-api/src/main/java/top/naccl/controller/FriendController.java
+++ b/blog-api/src/main/java/top/naccl/controller/FriendController.java
@@ -6,6 +6,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.Friend;
import top.naccl.model.vo.FriendInfo;
import top.naccl.model.vo.Result;
@@ -30,12 +31,12 @@ public class FriendController {
*
* @return
*/
- @VisitLogger(behavior = "访问页面", content = "友链")
+ @VisitLogger(VisitBehavior.FRIEND)
@GetMapping("/friends")
public Result friends() {
List friendList = friendService.getFriendVOList();
FriendInfo friendInfo = friendService.getFriendInfo(true, true);
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("friendList", friendList);
map.put("friendInfo", friendInfo);
return Result.ok("获取成功", map);
@@ -47,7 +48,7 @@ public Result friends() {
* @param nickname 友链昵称
* @return
*/
- @VisitLogger(behavior = "点击友链")
+ @VisitLogger(VisitBehavior.CLICK_FRIEND)
@PostMapping("/friend")
public Result addViews(@RequestParam String nickname) {
friendService.updateViewsByNickname(nickname);
diff --git a/blog-api/src/main/java/top/naccl/controller/LoginController.java b/blog-api/src/main/java/top/naccl/controller/LoginController.java
index 137f3c073..bf941c3fe 100644
--- a/blog-api/src/main/java/top/naccl/controller/LoginController.java
+++ b/blog-api/src/main/java/top/naccl/controller/LoginController.java
@@ -4,6 +4,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
+import top.naccl.constant.JwtConstants;
import top.naccl.entity.User;
import top.naccl.model.dto.LoginInfo;
import top.naccl.model.vo.Result;
@@ -36,8 +37,8 @@ public Result login(@RequestBody LoginInfo loginInfo) {
return Result.create(403, "无权限");
}
user.setPassword(null);
- String jwt = JwtUtils.generateToken("admin:" + user.getUsername());
- Map map = new HashMap();
+ String jwt = JwtUtils.generateToken(JwtConstants.ADMIN_PREFIX + user.getUsername());
+ Map map = new HashMap(4);
map.put("user", user);
map.put("token", jwt);
return Result.ok("登录成功", map);
diff --git a/blog-api/src/main/java/top/naccl/controller/MomentController.java b/blog-api/src/main/java/top/naccl/controller/MomentController.java
index 3bd133505..43a22ec7e 100644
--- a/blog-api/src/main/java/top/naccl/controller/MomentController.java
+++ b/blog-api/src/main/java/top/naccl/controller/MomentController.java
@@ -10,8 +10,10 @@
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.AccessLimit;
import top.naccl.annotation.VisitLogger;
+import top.naccl.constant.JwtConstants;
import top.naccl.entity.Moment;
import top.naccl.entity.User;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.PageResult;
import top.naccl.model.vo.Result;
import top.naccl.service.MomentService;
@@ -37,7 +39,7 @@ public class MomentController {
* @param jwt 博主访问Token
* @return
*/
- @VisitLogger(behavior = "访问页面", content = "动态")
+ @VisitLogger(VisitBehavior.MOMENT)
@GetMapping("/moments")
public Result moments(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestHeader(value = "Authorization", defaultValue = "") String jwt) {
@@ -45,8 +47,9 @@ public Result moments(@RequestParam(defaultValue = "1") Integer pageNum,
if (JwtUtils.judgeTokenIsExist(jwt)) {
try {
String subject = JwtUtils.getTokenBody(jwt).getSubject();
- if (subject.startsWith("admin:")) {//博主身份Token
- String username = subject.replace("admin:", "");
+ if (subject.startsWith(JwtConstants.ADMIN_PREFIX)) {
+ //博主身份Token
+ String username = subject.replace(JwtConstants.ADMIN_PREFIX, "");
User admin = (User) userService.loadUserByUsername(username);
if (admin != null) {
adminIdentity = true;
@@ -69,7 +72,7 @@ public Result moments(@RequestParam(defaultValue = "1") Integer pageNum,
* @return
*/
@AccessLimit(seconds = 86400, maxCount = 1, msg = "不可以重复点赞哦")
- @VisitLogger(behavior = "点赞动态")
+ @VisitLogger(VisitBehavior.LIKE_MOMENT)
@PostMapping("/moment/like/{id}")
public Result like(@PathVariable Long id) {
momentService.addLikeByMomentId(id);
diff --git a/blog-api/src/main/java/top/naccl/controller/TagController.java b/blog-api/src/main/java/top/naccl/controller/TagController.java
index 90626f258..83207014c 100644
--- a/blog-api/src/main/java/top/naccl/controller/TagController.java
+++ b/blog-api/src/main/java/top/naccl/controller/TagController.java
@@ -5,6 +5,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.naccl.annotation.VisitLogger;
+import top.naccl.enums.VisitBehavior;
import top.naccl.model.vo.BlogInfo;
import top.naccl.model.vo.PageResult;
import top.naccl.model.vo.Result;
@@ -27,7 +28,7 @@ public class TagController {
* @param pageNum 页码
* @return
*/
- @VisitLogger(behavior = "查看标签")
+ @VisitLogger(VisitBehavior.TAG)
@GetMapping("/tag")
public Result tag(@RequestParam String tagName,
@RequestParam(defaultValue = "1") Integer pageNum) {
diff --git a/blog-api/src/main/java/top/naccl/controller/TelegramBotController.java b/blog-api/src/main/java/top/naccl/controller/TelegramBotController.java
new file mode 100644
index 000000000..d0a1d0f25
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/controller/TelegramBotController.java
@@ -0,0 +1,48 @@
+package top.naccl.controller;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import top.naccl.config.properties.TelegramProperties;
+import top.naccl.constant.CommentConstants;
+import top.naccl.model.dto.TgMessage;
+import top.naccl.util.telegram.TelegramBotMsgHandler;
+
+/**
+ * 处理TelegramBot接收到的新消息
+ * 如果不使用Telegram方式,即comment.notify.channel != tg,则该类不会被实例化,对应的Webhook接口也不会被创建
+ *
+ * @author: Naccl
+ * @date: 2022年01月24日
+ */
+@Slf4j
+@ConditionalOnProperty(name = "comment.notify.channel", havingValue = CommentConstants.TELEGRAM)
+@RestController
+public class TelegramBotController {
+ @Autowired
+ private TelegramBotMsgHandler msgHandler;
+ @Autowired
+ private TelegramProperties telegramProperties;
+
+ /**
+ * webhook方式监听bot收到的新消息
+ *
+ * @param message 新消息
+ */
+ @PostMapping("/tg/${tg.bot.token}")
+ public void getUpdate(@RequestBody TgMessage message) {
+ log.info("Telegram bot receive message: {}", message);
+ //判断消息是否是自己发出的
+ if (message != null && message.getMessage() != null && message.getMessage().getChat() != null
+ && telegramProperties.getChatId().equals(message.getMessage().getChat().getId())) {
+ //判断是不是正常的文本消息
+ if (message.getMessage().getText() != null) {
+ //处理消息
+ msgHandler.processCommand(message.getMessage().getText());
+ }
+ }
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/controller/admin/AccountAdminController.java b/blog-api/src/main/java/top/naccl/controller/admin/AccountAdminController.java
new file mode 100644
index 000000000..86414781b
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/controller/admin/AccountAdminController.java
@@ -0,0 +1,32 @@
+package top.naccl.controller.admin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import top.naccl.entity.User;
+import top.naccl.model.vo.Result;
+import top.naccl.service.UserService;
+
+/**
+ * @Description: 账号后台管理
+ * @Author: Naccl
+ * @Date: 2023年01月31日
+ */
+@RestController
+@RequestMapping("/admin")
+public class AccountAdminController {
+ @Autowired
+ UserService userService;
+
+ /**
+ * 账号密码修改
+ */
+ @PostMapping("/account")
+ public Result account(@RequestBody User user, @RequestHeader(value = "Authorization", defaultValue = "") String jwt) {
+ boolean res = userService.changeAccount(user, jwt);
+ return res ? Result.ok("修改成功") : Result.error("修改失败");
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/controller/admin/BlogAdminController.java b/blog-api/src/main/java/top/naccl/controller/admin/BlogAdminController.java
index 1f7b513ad..2979afb8a 100644
--- a/blog-api/src/main/java/top/naccl/controller/admin/BlogAdminController.java
+++ b/blog-api/src/main/java/top/naccl/controller/admin/BlogAdminController.java
@@ -66,7 +66,7 @@ public Result blogs(@RequestParam(defaultValue = "") String title,
PageHelper.startPage(pageNum, pageSize, orderBy);
PageInfo pageInfo = new PageInfo(blogService.getListByTitleAndCategoryId(title, categoryId));
List categories = categoryService.getCategoryList();
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("blogs", pageInfo);
map.put("categories", categories);
return Result.ok("请求成功", map);
@@ -96,7 +96,7 @@ public Result delete(@RequestParam Long id) {
public Result categoryAndTag() {
List categories = categoryService.getCategoryList();
List tags = tagService.getTagList();
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("categories", categories);
map.put("tags", tags);
return Result.ok("请求成功", map);
@@ -249,7 +249,7 @@ private Result getResult(top.naccl.model.dto.Blog blog, String type) {
blog.setCreateTime(date);
blog.setUpdateTime(date);
User user = new User();
- user.setId((long) 1);//个人博客默认只有一个作者
+ user.setId(1L);//个人博客默认只有一个作者
blog.setUser(user);
blogService.saveBlog(blog);
diff --git a/blog-api/src/main/java/top/naccl/controller/admin/CommentAdminController.java b/blog-api/src/main/java/top/naccl/controller/admin/CommentAdminController.java
index 5676459c6..80affa5ca 100644
--- a/blog-api/src/main/java/top/naccl/controller/admin/CommentAdminController.java
+++ b/blog-api/src/main/java/top/naccl/controller/admin/CommentAdminController.java
@@ -49,7 +49,7 @@ public Result comments(@RequestParam(defaultValue = "") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
String orderBy = "create_time desc";
PageHelper.startPage(pageNum, pageSize, orderBy);
- List comments = commentService.getListByPageAndParentCommentId(page, blogId, (long) -1);
+ List comments = commentService.getListByPageAndParentCommentId(page, blogId, -1L);
PageInfo pageInfo = new PageInfo(comments);
return Result.ok("请求成功", pageInfo);
}
diff --git a/blog-api/src/main/java/top/naccl/controller/admin/DashboardAdminController.java b/blog-api/src/main/java/top/naccl/controller/admin/DashboardAdminController.java
index f0e89ffbb..c7a02905a 100644
--- a/blog-api/src/main/java/top/naccl/controller/admin/DashboardAdminController.java
+++ b/blog-api/src/main/java/top/naccl/controller/admin/DashboardAdminController.java
@@ -4,7 +4,7 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.CityVisitor;
import top.naccl.model.vo.Result;
import top.naccl.service.DashboardService;
@@ -30,7 +30,7 @@ public class DashboardAdminController {
@GetMapping("/dashboard")
public Result dashboard() {
int todayPV = dashboardService.countVisitLogByToday();
- int todayUV = redisService.countBySet(RedisKeyConfig.IDENTIFICATION_SET);
+ int todayUV = redisService.countBySet(RedisKeyConstants.IDENTIFICATION_SET);
int blogCount = dashboardService.getBlogCount();
int commentCount = dashboardService.getCommentCount();
Map categoryBlogCountMap = dashboardService.getCategoryBlogCountMap();
@@ -38,7 +38,7 @@ public Result dashboard() {
Map visitRecordMap = dashboardService.getVisitRecordMap();
List cityVisitorList = dashboardService.getCityVisitorList();
- Map map = new HashMap();
+ Map map = new HashMap(16);
map.put("pv", todayPV);
map.put("uv", todayUV);
map.put("blogCount", blogCount);
diff --git a/blog-api/src/main/java/top/naccl/entity/SiteSetting.java b/blog-api/src/main/java/top/naccl/entity/SiteSetting.java
index 93063fed7..c3037c4bd 100644
--- a/blog-api/src/main/java/top/naccl/entity/SiteSetting.java
+++ b/blog-api/src/main/java/top/naccl/entity/SiteSetting.java
@@ -1,5 +1,6 @@
package top.naccl.entity;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -14,6 +15,7 @@
@Getter
@Setter
@ToString
+@JsonIgnoreProperties(ignoreUnknown = true)
public class SiteSetting {
private Long id;
private String nameEn;
diff --git a/blog-api/src/main/java/top/naccl/enums/CommentOpenStateEnum.java b/blog-api/src/main/java/top/naccl/enums/CommentOpenStateEnum.java
new file mode 100644
index 000000000..9e3c9e0ec
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/enums/CommentOpenStateEnum.java
@@ -0,0 +1,26 @@
+package top.naccl.enums;
+
+/**
+ * 评论开放状态枚举类
+ *
+ * @author: Naccl
+ * @date: 2022年01月23日
+ */
+public enum CommentOpenStateEnum {
+ /**
+ * 博客不存在,或博客未公开
+ */
+ NOT_FOUND,
+ /**
+ * 评论正常开放
+ */
+ OPEN,
+ /**
+ * 评论已关闭
+ */
+ CLOSE,
+ /**
+ * 评论所在页面需要密码
+ */
+ PASSWORD,
+}
diff --git a/blog-api/src/main/java/top/naccl/enums/CommentPageEnum.java b/blog-api/src/main/java/top/naccl/enums/CommentPageEnum.java
new file mode 100644
index 000000000..c0bea9d75
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/enums/CommentPageEnum.java
@@ -0,0 +1,40 @@
+package top.naccl.enums;
+
+/**
+ * 评论页面枚举类
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+public enum CommentPageEnum {
+ UNKNOWN("UNKNOWN", "UNKNOWN"),
+
+ BLOG("", ""),
+ ABOUT("关于我", "/about"),
+ FRIEND("友人帐", "/friends"),
+ ;
+
+ private String title;
+ private String path;
+
+ CommentPageEnum(String title, String path) {
+ this.title = title;
+ this.path = path;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/enums/VisitBehavior.java b/blog-api/src/main/java/top/naccl/enums/VisitBehavior.java
new file mode 100644
index 000000000..e39684dcf
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/enums/VisitBehavior.java
@@ -0,0 +1,48 @@
+package top.naccl.enums;
+
+/**
+ * 访问行为枚举类
+ *
+ * @author: Naccl
+ * @date: 2022年01月08日
+ */
+public enum VisitBehavior {
+ UNKNOWN("UNKNOWN", "UNKNOWN"),
+
+ INDEX("访问页面", "首页"),
+ ARCHIVE("访问页面", "归档"),
+ MOMENT("访问页面", "动态"),
+ FRIEND("访问页面", "友链"),
+ ABOUT("访问页面", "关于我"),
+
+ BLOG("查看博客", ""),
+ CATEGORY("查看分类", ""),
+ TAG("查看标签", ""),
+ SEARCH("搜索博客", ""),
+ CLICK_FRIEND("点击友链", ""),
+ LIKE_MOMENT("点赞动态", ""),
+ CHECK_PASSWORD("校验博客密码", ""),
+ ;
+
+ /**
+ * 访问行为
+ */
+ private String behavior;
+ /**
+ * 访问内容
+ */
+ private String content;
+
+ VisitBehavior(String behavior, String content) {
+ this.behavior = behavior;
+ this.content = content;
+ }
+
+ public String getBehavior() {
+ return behavior;
+ }
+
+ public String getContent() {
+ return content;
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/mapper/UserMapper.java b/blog-api/src/main/java/top/naccl/mapper/UserMapper.java
index 4f2a2016e..bdae0272a 100644
--- a/blog-api/src/main/java/top/naccl/mapper/UserMapper.java
+++ b/blog-api/src/main/java/top/naccl/mapper/UserMapper.java
@@ -13,4 +13,8 @@
@Repository
public interface UserMapper {
User findByUsername(String username);
+
+ User findById(Long id);
+
+ int updateUserByUsername(String username, User user);
}
diff --git a/blog-api/src/main/java/top/naccl/model/dto/TgMessage.java b/blog-api/src/main/java/top/naccl/model/dto/TgMessage.java
new file mode 100644
index 000000000..ea26d7dfd
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/dto/TgMessage.java
@@ -0,0 +1,63 @@
+package top.naccl.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * Telegram新消息
+ *
+ * @author: Naccl
+ * @date: 2022年01月24日
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+public class TgMessage {
+ @JsonProperty("update_id")
+ private String updateId;
+ private Message message;
+
+ @NoArgsConstructor
+ @Getter
+ @Setter
+ @ToString
+ public class Message {
+ @JsonProperty("message_id")
+ private String messageId;
+ private From from;
+ private Chat chat;
+ private String date;
+ private String text;
+
+ @NoArgsConstructor
+ @Getter
+ @Setter
+ @ToString
+ public class From {
+ private String id;
+ @JsonProperty("is_bot")
+ private Boolean isBot;
+ @JsonProperty("first_name")
+ private String firstName;
+ private String username;
+ @JsonProperty("language_code")
+ private String languageCode;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ @Setter
+ @ToString
+ public class Chat {
+ private String id;
+ @JsonProperty("first_name")
+ private String firstName;
+ private String username;
+ private String type;
+ }
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/model/dto/UserAgentDTO.java b/blog-api/src/main/java/top/naccl/model/dto/UserAgentDTO.java
new file mode 100644
index 000000000..3c45c9b8a
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/dto/UserAgentDTO.java
@@ -0,0 +1,22 @@
+package top.naccl.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @Description: UserAgent解析DTO
+ * @Author: Naccl
+ * @Date: 2022年10月13日
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+public class UserAgentDTO {
+ private String os;
+ private String browser;
+}
diff --git a/blog-api/src/main/java/top/naccl/model/dto/VisitLogRemark.java b/blog-api/src/main/java/top/naccl/model/dto/VisitLogRemark.java
new file mode 100644
index 000000000..c1917128b
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/dto/VisitLogRemark.java
@@ -0,0 +1,30 @@
+package top.naccl.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 访问日志备注
+ *
+ * @author: Naccl
+ * @date: 2022年01月08日
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+public class VisitLogRemark {
+ /**
+ * 访问内容
+ */
+ private String content;
+
+ /**
+ * 备注
+ */
+ private String remark;
+}
diff --git a/blog-api/src/main/java/top/naccl/model/bean/Badge.java b/blog-api/src/main/java/top/naccl/model/vo/Badge.java
similarity index 92%
rename from blog-api/src/main/java/top/naccl/model/bean/Badge.java
rename to blog-api/src/main/java/top/naccl/model/vo/Badge.java
index 0b9cbedf8..4caf45e07 100644
--- a/blog-api/src/main/java/top/naccl/model/bean/Badge.java
+++ b/blog-api/src/main/java/top/naccl/model/vo/Badge.java
@@ -1,4 +1,4 @@
-package top.naccl.model.bean;
+package top.naccl.model.vo;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/blog-api/src/main/java/top/naccl/model/bean/Copyright.java b/blog-api/src/main/java/top/naccl/model/vo/Copyright.java
similarity index 90%
rename from blog-api/src/main/java/top/naccl/model/bean/Copyright.java
rename to blog-api/src/main/java/top/naccl/model/vo/Copyright.java
index 1b6b7fa75..c0a299322 100644
--- a/blog-api/src/main/java/top/naccl/model/bean/Copyright.java
+++ b/blog-api/src/main/java/top/naccl/model/vo/Copyright.java
@@ -1,4 +1,4 @@
-package top.naccl.model.bean;
+package top.naccl.model.vo;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/blog-api/src/main/java/top/naccl/model/bean/Favorite.java b/blog-api/src/main/java/top/naccl/model/vo/Favorite.java
similarity index 90%
rename from blog-api/src/main/java/top/naccl/model/bean/Favorite.java
rename to blog-api/src/main/java/top/naccl/model/vo/Favorite.java
index 5241b2bfb..3f05eb2ea 100644
--- a/blog-api/src/main/java/top/naccl/model/bean/Favorite.java
+++ b/blog-api/src/main/java/top/naccl/model/vo/Favorite.java
@@ -1,4 +1,4 @@
-package top.naccl.model.bean;
+package top.naccl.model.vo;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/blog-api/src/main/java/top/naccl/model/vo/Introduction.java b/blog-api/src/main/java/top/naccl/model/vo/Introduction.java
index 512e8ffb7..ebcccca63 100644
--- a/blog-api/src/main/java/top/naccl/model/vo/Introduction.java
+++ b/blog-api/src/main/java/top/naccl/model/vo/Introduction.java
@@ -4,7 +4,6 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
-import top.naccl.model.bean.Favorite;
import java.util.ArrayList;
import java.util.List;
@@ -22,6 +21,7 @@ public class Introduction {
private String avatar;
private String name;
private String github;
+ private String telegram;
private String qq;
private String bilibili;
private String netease;
diff --git a/blog-api/src/main/java/top/naccl/model/vo/QqResultVO.java b/blog-api/src/main/java/top/naccl/model/vo/QqResultVO.java
new file mode 100644
index 000000000..2209510d1
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/vo/QqResultVO.java
@@ -0,0 +1,22 @@
+package top.naccl.model.vo;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @author raxcl
+ * @date 2024年01月19日 9:54:53
+ */
+@Data
+public class QqResultVO {
+ private String success;
+
+ private String msg;
+
+ private Map data;
+
+ private String time;
+
+ private String api_vers;
+}
diff --git a/blog-api/src/main/java/top/naccl/model/vo/QqVO.java b/blog-api/src/main/java/top/naccl/model/vo/QqVO.java
new file mode 100644
index 000000000..d3e1f5c87
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/model/vo/QqVO.java
@@ -0,0 +1,30 @@
+package top.naccl.model.vo;
+
+import lombok.Data;
+
+/**
+ * @author raxcl
+ * @date 2024年01月19日 9:54:53
+ */
+@Data
+public class QqVO {
+ /**
+ * qq号
+ */
+ private Long qq;
+
+ /**
+ * qq昵称
+ */
+ private String name;
+
+ /**
+ * qq邮箱
+ */
+ private String email;
+
+ /**
+ * qq头像
+ */
+ private String avatar;
+}
diff --git a/blog-api/src/main/java/top/naccl/service/UserService.java b/blog-api/src/main/java/top/naccl/service/UserService.java
index 984f61fcb..1bb5b1de8 100644
--- a/blog-api/src/main/java/top/naccl/service/UserService.java
+++ b/blog-api/src/main/java/top/naccl/service/UserService.java
@@ -4,4 +4,8 @@
public interface UserService {
User findUserByUsernameAndPassword(String username, String password);
+
+ User findUserById(Long id);
+
+ boolean changeAccount(User user, String jwt);
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/AboutServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/AboutServiceImpl.java
index 434b752de..15825ea23 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/AboutServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/AboutServiceImpl.java
@@ -3,7 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.About;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.AboutMapper;
@@ -30,13 +30,13 @@ public class AboutServiceImpl implements AboutService {
@Override
public Map getAboutInfo() {
- String redisKey = RedisKeyConfig.ABOUT_INFO_MAP;
+ String redisKey = RedisKeyConstants.ABOUT_INFO_MAP;
Map aboutInfoMapFromRedis = redisService.getMapByValue(redisKey);
if (aboutInfoMapFromRedis != null) {
return aboutInfoMapFromRedis;
}
List abouts = aboutMapper.getList();
- Map aboutInfoMap = new HashMap();
+ Map aboutInfoMap = new HashMap(16);
for (About about : abouts) {
if ("content".equals(about.getNameEn())) {
about.setValue(MarkdownUtils.markdownToHtmlExtensions(about.getValue()));
@@ -50,7 +50,7 @@ public Map getAboutInfo() {
@Override
public Map getAboutSetting() {
List abouts = aboutMapper.getList();
- Map map = new HashMap();
+ Map map = new HashMap(16);
for (About about : abouts) {
map.put(about.getNameEn(), about.getValue());
}
@@ -66,7 +66,7 @@ public void updateAbout(Map map) {
deleteAboutRedisCache();
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
public void updateOneAbout(String nameEn, String value) {
if (aboutMapper.updateAbout(nameEn, value) != 1) {
throw new PersistenceException("修改失败");
@@ -83,6 +83,6 @@ public boolean getAboutCommentEnabled() {
* 删除关于我页面缓存
*/
private void deleteAboutRedisCache() {
- redisService.deleteCacheByKey(RedisKeyConfig.ABOUT_INFO_MAP);
+ redisService.deleteCacheByKey(RedisKeyConstants.ABOUT_INFO_MAP);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/BlogServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/BlogServiceImpl.java
index 3e8a2b321..0b4f423e0 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/BlogServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/BlogServiceImpl.java
@@ -5,7 +5,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.Blog;
import top.naccl.exception.NotFoundException;
import top.naccl.exception.PersistenceException;
@@ -22,7 +22,6 @@
import top.naccl.service.BlogService;
import top.naccl.service.RedisService;
import top.naccl.service.TagService;
-import top.naccl.task.RedisSyncScheduleTask;
import top.naccl.util.JacksonUtils;
import top.naccl.util.markdown.MarkdownUtils;
@@ -45,8 +44,6 @@ public class BlogServiceImpl implements BlogService {
TagService tagService;
@Autowired
RedisService redisService;
- @Autowired
- RedisSyncScheduleTask redisSyncScheduleTask;
//随机博客显示5条
private static final int randomBlogLimitNum = 5;
//最新推荐博客显示3条
@@ -63,7 +60,7 @@ public class BlogServiceImpl implements BlogService {
*/
@PostConstruct
private void saveBlogViewsToRedis() {
- String redisKey = RedisKeyConfig.BLOG_VIEWS_MAP;
+ String redisKey = RedisKeyConstants.BLOG_VIEWS_MAP;
//Redis中没有存储博客浏览量的Hash
if (!redisService.hasKey(redisKey)) {
//从数据库中读取并存入Redis
@@ -80,14 +77,16 @@ public List getListByTitleAndCategoryId(String title, Integer categoryId)
@Override
public List getSearchBlogListByQueryAndIsPublished(String query) {
List searchBlogs = blogMapper.getSearchBlogListByQueryAndIsPublished(query);
+ // 数据库的处理是不区分大小写的,那么这里的匹配串处理也应该不区分大小写,否则会出现不准确的结果
+ query = query.toUpperCase();
for (SearchBlog searchBlog : searchBlogs) {
- String content = searchBlog.getContent();
+ String content = searchBlog.getContent().toUpperCase();
int contentLength = content.length();
int index = content.indexOf(query) - 10;
- index = index < 0 ? 0 : index; + index = Math.max(index, 0); int end = index + 21;//以关键字字符串为中心返回21个字 - end = end> contentLength - 1 ? contentLength - 1 : end;
- searchBlog.setContent(content.substring(index, end));
+ end = Math.min(end, contentLength - 1);
+ searchBlog.setContent(searchBlog.getContent().substring(index, end));
}
return searchBlogs;
}
@@ -99,7 +98,7 @@ public List getIdAndTitleList() {
@Override
public List getNewBlogListByIsPublished() {
- String redisKey = RedisKeyConfig.NEW_BLOG_LIST;
+ String redisKey = RedisKeyConstants.NEW_BLOG_LIST;
List newBlogListFromRedis = redisService.getListByValue(redisKey);
if (newBlogListFromRedis != null) {
return newBlogListFromRedis;
@@ -120,7 +119,7 @@ public List getNewBlogListByIsPublished() {
@Override
public PageResult getBlogInfoListByIsPublished(Integer pageNum) {
- String redisKey = RedisKeyConfig.HOME_BLOG_INFO_LIST;
+ String redisKey = RedisKeyConstants.HOME_BLOG_INFO_LIST;
//redis已有当前页缓存
PageResult pageResultFromRedis = redisService.getBlogInfoPageResultByHash(redisKey, pageNum);
if (pageResultFromRedis != null) {
@@ -144,11 +143,23 @@ public PageResult getBlogInfoListByIsPublished(Integer pageNum) {
* @param pageResult
*/
private void setBlogViewsFromRedisToPageResult(PageResult pageResult) {
- String redisKey = RedisKeyConfig.BLOG_VIEWS_MAP;
+ String redisKey = RedisKeyConstants.BLOG_VIEWS_MAP;
List blogInfos = pageResult.getList();
for (int i = 0; i < blogInfos.size(); i++) { BlogInfo blogInfo = JacksonUtils.convertValue(blogInfos.get(i), BlogInfo.class); Long blogId = blogInfo.getId(); + /** + * 这里如果出现异常,通常是手动修改过 MySQL 而没有通过后台管理,导致 Redis 和 MySQL 不同步 + * 从 Redis 中查出了 null,强转 int 时出现 NullPointerException + * 直接抛出异常比带着 bug 继续跑要好得多 + * + * 解决步骤: + * 1.结束程序 + * 2.删除 Redis DB 中 blogViewsMap 这个 key(或者直接清空对应的整个 DB) + * 3.重新启动程序 + * + * 具体请查看: https://github.com/Naccl/NBlog/issues/58 + */ int view = (int) redisService.getValueByHashKey(redisKey, blogId); blogInfo.setViews(view); blogInfos.set(i, blogInfo); @@ -192,12 +203,11 @@ private List processBlogInfosPassword(List blogInfos) {
@Override
public Map getArchiveBlogAndCountByIsPublished() {
- String redisKey = RedisKeyConfig.ARCHIVE_BLOG_MAP;
+ String redisKey = RedisKeyConstants.ARCHIVE_BLOG_MAP;
Map mapFromRedis = redisService.getMapByValue(redisKey);
if (mapFromRedis != null) {
return mapFromRedis;
}
- Map map = new HashMap();
List groupYearMonth = blogMapper.getGroupYearMonthByIsPublished();
Map> archiveBlogMap = new LinkedHashMap();
for (String s : groupYearMonth) {
@@ -213,6 +223,7 @@ public Map getArchiveBlogAndCountByIsPublished() {
archiveBlogMap.put(s, archiveBlogs);
}
Integer count = countBlogByIsPublished();
+ Map map = new HashMap(4);
map.put("blogMap", archiveBlogMap);
map.put("count", count);
redisService.saveMapToValue(redisKey, map);
@@ -235,24 +246,24 @@ public List getRandomBlogListByLimitNumAndIsPublishedAndIsRecommend(
private Map getBlogViewsMap() {
List blogViewList = blogMapper.getBlogViewsList();
- Map blogViewsMap = new HashMap();
+ Map blogViewsMap = new HashMap(128);
for (BlogView blogView : blogViewList) {
blogViewsMap.put(blogView.getId(), blogView.getViews());
}
return blogViewsMap;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteBlogById(Long id) {
if (blogMapper.deleteBlogById(id) != 1) {
throw new NotFoundException("该博客不存在");
}
deleteBlogRedisCache();
- redisService.deleteByHashKey(RedisKeyConfig.BLOG_VIEWS_MAP, id);
+ redisService.deleteByHashKey(RedisKeyConstants.BLOG_VIEWS_MAP, id);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteBlogTagByBlogId(Long blogId) {
if (blogMapper.deleteBlogTagByBlogId(blogId) == 0) {
@@ -260,17 +271,17 @@ public void deleteBlogTagByBlogId(Long blogId) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveBlog(top.naccl.model.dto.Blog blog) {
if (blogMapper.saveBlog(blog) != 1) {
throw new PersistenceException("添加博客失败");
}
- redisService.saveKVToHash(RedisKeyConfig.BLOG_VIEWS_MAP, blog.getId(), 0);
+ redisService.saveKVToHash(RedisKeyConstants.BLOG_VIEWS_MAP, blog.getId(), 0);
deleteBlogRedisCache();
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveBlogTag(Long blogId, Long tagId) {
if (blogMapper.saveBlogTag(blogId, tagId) != 1) {
@@ -278,7 +289,7 @@ public void saveBlogTag(Long blogId, Long tagId) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateBlogRecommendById(Long blogId, Boolean recommend) {
if (blogMapper.updateBlogRecommendById(blogId, recommend) != 1) {
@@ -286,32 +297,32 @@ public void updateBlogRecommendById(Long blogId, Boolean recommend) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateBlogVisibilityById(Long blogId, BlogVisibility blogVisibility) {
if (blogMapper.updateBlogVisibilityById(blogId, blogVisibility) != 1) {
throw new PersistenceException("操作失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.HOME_BLOG_INFO_LIST);
- redisService.deleteCacheByKey(RedisKeyConfig.NEW_BLOG_LIST);
- redisService.deleteCacheByKey(RedisKeyConfig.ARCHIVE_BLOG_MAP);
+ redisService.deleteCacheByKey(RedisKeyConstants.HOME_BLOG_INFO_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.NEW_BLOG_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.ARCHIVE_BLOG_MAP);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateBlogTopById(Long blogId, Boolean top) {
if (blogMapper.updateBlogTopById(blogId, top) != 1) {
throw new PersistenceException("操作失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.HOME_BLOG_INFO_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.HOME_BLOG_INFO_LIST);
}
@Override
public void updateViewsToRedis(Long blogId) {
- redisService.incrementByHashKey(RedisKeyConfig.BLOG_VIEWS_MAP, blogId, 1);
+ redisService.incrementByHashKey(RedisKeyConstants.BLOG_VIEWS_MAP, blogId, 1);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateViews(Long blogId, Integer views) {
if (blogMapper.updateViews(blogId, views) != 1) {
@@ -325,8 +336,12 @@ public Blog getBlogById(Long id) {
if (blog == null) {
throw new NotFoundException("博客不存在");
}
- //将浏览量设置为Redis中的最新值
- int view = (int) redisService.getValueByHashKey(RedisKeyConfig.BLOG_VIEWS_MAP, blog.getId());
+ /**
+ * 将浏览量设置为Redis中的最新值
+ * 这里如果出现异常,查看第 152 行注释说明
+ * @see BlogServiceImpl#setBlogViewsFromRedisToPageResult
+ */
+ int view = (int) redisService.getValueByHashKey(RedisKeyConstants.BLOG_VIEWS_MAP, blog.getId());
blog.setViews(view);
return blog;
}
@@ -343,8 +358,12 @@ public BlogDetail getBlogByIdAndIsPublished(Long id) {
throw new NotFoundException("该博客不存在");
}
blog.setContent(MarkdownUtils.markdownToHtmlExtensions(blog.getContent()));
- //将浏览量设置为Redis中的最新值
- int view = (int) redisService.getValueByHashKey(RedisKeyConfig.BLOG_VIEWS_MAP, blog.getId());
+ /**
+ * 将浏览量设置为Redis中的最新值
+ * 这里如果出现异常,查看第 152 行注释说明
+ * @see BlogServiceImpl#setBlogViewsFromRedisToPageResult
+ */
+ int view = (int) redisService.getValueByHashKey(RedisKeyConstants.BLOG_VIEWS_MAP, blog.getId());
blog.setViews(view);
return blog;
}
@@ -354,14 +373,14 @@ public String getBlogPassword(Long blogId) {
return blogMapper.getBlogPassword(blogId);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateBlog(top.naccl.model.dto.Blog blog) {
if (blogMapper.updateBlog(blog) != 1) {
throw new PersistenceException("更新博客失败");
}
deleteBlogRedisCache();
- redisService.saveKVToHash(RedisKeyConfig.BLOG_VIEWS_MAP, blog.getId(), blog.getViews());
+ redisService.saveKVToHash(RedisKeyConstants.BLOG_VIEWS_MAP, blog.getId(), blog.getViews());
}
@Override
@@ -393,8 +412,8 @@ public Boolean getPublishedByBlogId(Long blogId) {
* 删除首页缓存、最新推荐缓存、归档页面缓存、博客浏览量缓存
*/
private void deleteBlogRedisCache() {
- redisService.deleteCacheByKey(RedisKeyConfig.HOME_BLOG_INFO_LIST);
- redisService.deleteCacheByKey(RedisKeyConfig.NEW_BLOG_LIST);
- redisService.deleteCacheByKey(RedisKeyConfig.ARCHIVE_BLOG_MAP);
+ redisService.deleteCacheByKey(RedisKeyConstants.HOME_BLOG_INFO_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.NEW_BLOG_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.ARCHIVE_BLOG_MAP);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/CategoryServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/CategoryServiceImpl.java
index 73ac12637..5b062d6d2 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/CategoryServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/CategoryServiceImpl.java
@@ -3,7 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.Category;
import top.naccl.exception.NotFoundException;
import top.naccl.exception.PersistenceException;
@@ -35,7 +35,7 @@ public List getCategoryList() {
@Override
public List getCategoryNameList() {
- String redisKey = RedisKeyConfig.CATEGORY_NAME_LIST;
+ String redisKey = RedisKeyConstants.CATEGORY_NAME_LIST;
List categoryListFromRedis = redisService.getListByValue(redisKey);
if (categoryListFromRedis != null) {
return categoryListFromRedis;
@@ -45,13 +45,13 @@ public List getCategoryNameList() {
return categoryList;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveCategory(Category category) {
if (categoryMapper.saveCategory(category) != 1) {
throw new PersistenceException("分类添加失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.CATEGORY_NAME_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.CATEGORY_NAME_LIST);
}
@Override
@@ -68,23 +68,23 @@ public Category getCategoryByName(String name) {
return categoryMapper.getCategoryByName(name);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteCategoryById(Long id) {
if (categoryMapper.deleteCategoryById(id) != 1) {
throw new PersistenceException("删除分类失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.CATEGORY_NAME_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.CATEGORY_NAME_LIST);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateCategory(Category category) {
if (categoryMapper.updateCategory(category) != 1) {
throw new PersistenceException("分类更新失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.CATEGORY_NAME_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.CATEGORY_NAME_LIST);
//修改了分类名,可能有首页文章关联了分类,也要更新首页缓存
- redisService.deleteCacheByKey(RedisKeyConfig.HOME_BLOG_INFO_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.HOME_BLOG_INFO_LIST);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/CityVisitorServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/CityVisitorServiceImpl.java
index 8853b13d3..8d08f7225 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/CityVisitorServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/CityVisitorServiceImpl.java
@@ -2,6 +2,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import top.naccl.entity.CityVisitor;
import top.naccl.mapper.CityVisitorMapper;
import top.naccl.service.CityVisitorService;
@@ -16,6 +17,7 @@ public class CityVisitorServiceImpl implements CityVisitorService {
@Autowired
CityVisitorMapper cityVisitorMapper;
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveCityVisitor(CityVisitor cityVisitor) {
cityVisitorMapper.saveCityVisitor(cityVisitor);
diff --git a/blog-api/src/main/java/top/naccl/service/impl/CommentServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/CommentServiceImpl.java
index 77d1051da..4d391ea2a 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/CommentServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/CommentServiceImpl.java
@@ -10,6 +10,7 @@
import top.naccl.service.CommentService;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
/**
@@ -39,6 +40,11 @@ public List getPageCommentList(Integer page, Long blogId, Long pare
for (PageComment c : comments) {
List tmpComments = new ArrayList();
getReplyComments(tmpComments, c.getReplyComments());
+ //对于两列评论来说,按时间顺序排列应该比树形更合理些
+ //排序一下
+ Comparator comparator = Comparator.comparing(PageComment::getCreateTime);
+ tmpComments.sort(comparator);
+
c.setReplyComments(tmpComments);
}
return comments;
@@ -57,7 +63,6 @@ public Comment getCommentById(Long id) {
* 将所有子评论递归取出到一个List中
*
* @param comments
- * @return
*/
private void getReplyComments(List tmpComments, List comments) {
for (PageComment c : comments) {
@@ -75,15 +80,23 @@ private List getPageCommentListByPageAndParentCommentId(Integer pag
return comments;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateCommentPublishedById(Long commentId, Boolean published) {
+ //如果是隐藏评论,则所有子评论都要修改成隐藏状态
+ if (!published) {
+ List comments = getAllReplyComments(commentId);
+ for (Comment c : comments) {
+ hideComment(c);
+ }
+ }
+
if (commentMapper.updateCommentPublishedById(commentId, published) != 1) {
throw new PersistenceException("操作失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateCommentNoticeById(Long commentId, Boolean notice) {
if (commentMapper.updateCommentNoticeById(commentId, notice) != 1) {
@@ -91,7 +104,7 @@ public void updateCommentNoticeById(Long commentId, Boolean notice) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteCommentById(Long commentId) {
List comments = getAllReplyComments(commentId);
@@ -103,13 +116,13 @@ public void deleteCommentById(Long commentId) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteCommentsByBlogId(Long blogId) {
commentMapper.deleteCommentsByBlogId(blogId);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateComment(Comment comment) {
if (commentMapper.updateComment(comment) != 1) {
@@ -122,7 +135,7 @@ public int countByPageAndIsPublished(Integer page, Long blogId, Boolean isPublis
return commentMapper.countByPageAndIsPublished(page, blogId, isPublished);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveComment(top.naccl.model.dto.Comment comment) {
if (commentMapper.saveComment(comment) != 1) {
@@ -134,7 +147,6 @@ public void saveComment(top.naccl.model.dto.Comment comment) {
* 递归删除子评论
*
* @param comment 需要删除子评论的父评论
- * @return
*/
private void delete(Comment comment) {
for (Comment c : comment.getReplyComments()) {
@@ -145,6 +157,20 @@ private void delete(Comment comment) {
}
}
+ /**
+ * 递归隐藏子评论
+ *
+ * @param comment 需要隐藏子评论的父评论
+ */
+ private void hideComment(Comment comment) {
+ for (Comment c : comment.getReplyComments()) {
+ hideComment(c);
+ }
+ if (commentMapper.updateCommentPublishedById(comment.getId(), false) != 1) {
+ throw new PersistenceException("操作失败");
+ }
+ }
+
/**
* 按id递归查询子评论
*
diff --git a/blog-api/src/main/java/top/naccl/service/impl/DashboardServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/DashboardServiceImpl.java
index bc1248ea5..02f166ffc 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/DashboardServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/DashboardServiceImpl.java
@@ -75,7 +75,7 @@ public Map getCategoryBlogCountMap() {
//分类对应的博客数量List
List series = new ArrayList();
if (categoryBlogCountList.size() == categoryList.size()) {
- Map m = new HashMap();
+ Map m = new HashMap(16);
for (Category c : categoryList) {
m.put(c.getId(), c.getName());
}
@@ -84,7 +84,7 @@ public Map getCategoryBlogCountMap() {
series.add(c);
}
} else {
- Map m = new HashMap();
+ Map m = new HashMap(16);
for (CategoryBlogCount c : categoryBlogCountList) {
m.put(c.getId(), c.getValue());
}
@@ -100,7 +100,7 @@ public Map getCategoryBlogCountMap() {
series.add(categoryBlogCount);
}
}
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("legend", legend);
map.put("series", series);
return map;
@@ -120,7 +120,7 @@ public Map getTagBlogCountMap() {
//标签对应的博客数量List
List series = new ArrayList();
if (tagBlogCountList.size() == tagList.size()) {
- Map m = new HashMap();
+ Map m = new HashMap(64);
for (Tag t : tagList) {
m.put(t.getId(), t.getName());
}
@@ -129,7 +129,7 @@ public Map getTagBlogCountMap() {
series.add(t);
}
} else {
- Map m = new HashMap();
+ Map m = new HashMap(64);
for (TagBlogCount t : tagBlogCountList) {
m.put(t.getId(), t.getValue());
}
@@ -145,7 +145,7 @@ public Map getTagBlogCountMap() {
series.add(tagBlogCount);
}
}
- Map map = new HashMap();
+ Map map = new HashMap(4);
map.put("legend", legend);
map.put("series", series);
return map;
@@ -163,7 +163,7 @@ public Map getVisitRecordMap() {
pv.add(visitRecord.getPv());
uv.add(visitRecord.getUv());
}
- Map map = new HashMap();
+ Map map = new HashMap(8);
map.put("date", date);
map.put("pv", pv);
map.put("uv", uv);
diff --git a/blog-api/src/main/java/top/naccl/service/impl/ExceptionLogServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/ExceptionLogServiceImpl.java
index c2563df18..cce30c4ec 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/ExceptionLogServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/ExceptionLogServiceImpl.java
@@ -6,12 +6,12 @@
import top.naccl.entity.ExceptionLog;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.ExceptionLogMapper;
+import top.naccl.model.dto.UserAgentDTO;
import top.naccl.service.ExceptionLogService;
import top.naccl.util.IpAddressUtils;
import top.naccl.util.UserAgentUtils;
import java.util.List;
-import java.util.Map;
/**
* @Description: 异常日志业务层实现
@@ -30,22 +30,20 @@ public List getExceptionLogListByDate(String startDate, String end
return exceptionLogMapper.getExceptionLogListByDate(startDate, endDate);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveExceptionLog(ExceptionLog log) {
String ipSource = IpAddressUtils.getCityInfo(log.getIp());
- Map userAgentMap = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
- String os = userAgentMap.get("os");
- String browser = userAgentMap.get("browser");
+ UserAgentDTO userAgentDTO = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
log.setIpSource(ipSource);
- log.setOs(os);
- log.setBrowser(browser);
+ log.setOs(userAgentDTO.getOs());
+ log.setBrowser(userAgentDTO.getBrowser());
if (exceptionLogMapper.saveExceptionLog(log) != 1) {
throw new PersistenceException("日志添加失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteExceptionLogById(Long id) {
if (exceptionLogMapper.deleteExceptionLogById(id) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/FriendServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/FriendServiceImpl.java
index 1ef24498c..70b976a13 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/FriendServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/FriendServiceImpl.java
@@ -3,7 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.Friend;
import top.naccl.entity.SiteSetting;
import top.naccl.exception.PersistenceException;
@@ -41,7 +41,7 @@ public List getFriendVOList() {
return friendMapper.getFriendVOList();
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateFriendPublishedById(Long friendId, Boolean published) {
if (friendMapper.updateFriendPublishedById(friendId, published) != 1) {
@@ -49,7 +49,7 @@ public void updateFriendPublishedById(Long friendId, Boolean published) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveFriend(Friend friend) {
friend.setViews(0);
@@ -59,7 +59,7 @@ public void saveFriend(Friend friend) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateFriend(top.naccl.model.dto.Friend friend) {
if (friendMapper.updateFriend(friend) != 1) {
@@ -67,7 +67,7 @@ public void updateFriend(top.naccl.model.dto.Friend friend) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteFriend(Long id) {
if (friendMapper.deleteFriend(id) != 1) {
@@ -75,7 +75,7 @@ public void deleteFriend(Long id) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateViewsByNickname(String nickname) {
if (friendMapper.updateViewsByNickname(nickname) != 1) {
@@ -85,7 +85,7 @@ public void updateViewsByNickname(String nickname) {
@Override
public FriendInfo getFriendInfo(boolean cache, boolean md) {
- String redisKey = RedisKeyConfig.FRIEND_INFO_MAP;
+ String redisKey = RedisKeyConstants.FRIEND_INFO_MAP;
if (cache) {
FriendInfo friendInfoFromRedis = redisService.getObjectByValue(redisKey, FriendInfo.class);
if (friendInfoFromRedis != null) {
@@ -115,7 +115,7 @@ public FriendInfo getFriendInfo(boolean cache, boolean md) {
return friendInfo;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateFriendInfoContent(String content) {
if (siteSettingMapper.updateFriendInfoContent(content) != 1) {
@@ -124,7 +124,7 @@ public void updateFriendInfoContent(String content) {
deleteFriendInfoRedisCache();
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateFriendInfoCommentEnabled(Boolean commentEnabled) {
if (siteSettingMapper.updateFriendInfoCommentEnabled(commentEnabled) != 1) {
@@ -137,6 +137,6 @@ public void updateFriendInfoCommentEnabled(Boolean commentEnabled) {
* 删除友链页面缓存
*/
private void deleteFriendInfoRedisCache() {
- redisService.deleteCacheByKey(RedisKeyConfig.FRIEND_INFO_MAP);
+ redisService.deleteCacheByKey(RedisKeyConstants.FRIEND_INFO_MAP);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/LoginLogServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/LoginLogServiceImpl.java
index 7eec8c51d..6f0add9dc 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/LoginLogServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/LoginLogServiceImpl.java
@@ -6,12 +6,12 @@
import top.naccl.entity.LoginLog;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.LoginLogMapper;
+import top.naccl.model.dto.UserAgentDTO;
import top.naccl.service.LoginLogService;
import top.naccl.util.IpAddressUtils;
import top.naccl.util.UserAgentUtils;
import java.util.List;
-import java.util.Map;
/**
* @Description: 登录日志业务层实现
@@ -30,22 +30,20 @@ public List getLoginLogListByDate(String startDate, String endDate) {
return loginLogMapper.getLoginLogListByDate(startDate, endDate);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveLoginLog(LoginLog log) {
String ipSource = IpAddressUtils.getCityInfo(log.getIp());
- Map userAgentMap = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
- String os = userAgentMap.get("os");
- String browser = userAgentMap.get("browser");
+ UserAgentDTO userAgentDTO = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
log.setIpSource(ipSource);
- log.setOs(os);
- log.setBrowser(browser);
+ log.setOs(userAgentDTO.getOs());
+ log.setBrowser(userAgentDTO.getBrowser());
if (loginLogMapper.saveLoginLog(log) != 1) {
throw new PersistenceException("日志添加失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteLoginLogById(Long id) {
if (loginLogMapper.deleteLoginLogById(id) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/MomentServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/MomentServiceImpl.java
index 1451bc556..8960965b4 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/MomentServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/MomentServiceImpl.java
@@ -48,7 +48,7 @@ public List getMomentVOList(Integer pageNum, boolean adminIdentity) {
return moments;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void addLikeByMomentId(Long momentId) {
if (momentMapper.addLikeByMomentId(momentId) != 1) {
@@ -56,7 +56,7 @@ public void addLikeByMomentId(Long momentId) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateMomentPublishedById(Long momentId, Boolean published) {
if (momentMapper.updateMomentPublishedById(momentId, published) != 1) {
@@ -73,7 +73,7 @@ public Moment getMomentById(Long id) {
return moment;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteMomentById(Long id) {
if (momentMapper.deleteMomentById(id) != 1) {
@@ -81,7 +81,7 @@ public void deleteMomentById(Long id) {
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveMoment(Moment moment) {
if (momentMapper.saveMoment(moment) != 1) {
@@ -89,6 +89,7 @@ public void saveMoment(Moment moment) {
}
}
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateMoment(Moment moment) {
if (momentMapper.updateMoment(moment) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/OperationLogServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/OperationLogServiceImpl.java
index 55bbb6248..6916a4955 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/OperationLogServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/OperationLogServiceImpl.java
@@ -6,12 +6,12 @@
import top.naccl.entity.OperationLog;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.OperationLogMapper;
+import top.naccl.model.dto.UserAgentDTO;
import top.naccl.service.OperationLogService;
import top.naccl.util.IpAddressUtils;
import top.naccl.util.UserAgentUtils;
import java.util.List;
-import java.util.Map;
/**
* @Description: 操作日志业务层实现
@@ -30,22 +30,20 @@ public List getOperationLogListByDate(String startDate, String end
return operationLogMapper.getOperationLogListByDate(startDate, endDate);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveOperationLog(OperationLog log) {
String ipSource = IpAddressUtils.getCityInfo(log.getIp());
- Map userAgentMap = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
- String os = userAgentMap.get("os");
- String browser = userAgentMap.get("browser");
+ UserAgentDTO userAgentDTO = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
log.setIpSource(ipSource);
- log.setOs(os);
- log.setBrowser(browser);
+ log.setOs(userAgentDTO.getOs());
+ log.setBrowser(userAgentDTO.getBrowser());
if (operationLogMapper.saveOperationLog(log) != 1) {
throw new PersistenceException("日志添加失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteOperationLogById(Long id) {
if (operationLogMapper.deleteOperationLogById(id) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/ScheduleJobServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/ScheduleJobServiceImpl.java
index 1026e6827..c80ee8ebb 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/ScheduleJobServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/ScheduleJobServiceImpl.java
@@ -52,8 +52,8 @@ public List getJobList() {
return schedulerJobMapper.getJobList();
}
+ @Transactional(rollbackFor = Exception.class)
@Override
- @Transactional
public void saveJob(ScheduleJob scheduleJob) {
if (schedulerJobMapper.saveJob(scheduleJob) != 1) {
throw new PersistenceException("添加失败");
@@ -61,8 +61,8 @@ public void saveJob(ScheduleJob scheduleJob) {
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
}
+ @Transactional(rollbackFor = Exception.class)
@Override
- @Transactional
public void updateJob(ScheduleJob scheduleJob) {
if (schedulerJobMapper.updateJob(scheduleJob) != 1) {
throw new PersistenceException("更新失败");
@@ -70,8 +70,8 @@ public void updateJob(ScheduleJob scheduleJob) {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
}
+ @Transactional(rollbackFor = Exception.class)
@Override
- @Transactional
public void deleteJobById(Long jobId) {
ScheduleUtils.deleteScheduleJob(scheduler, jobId);
if (schedulerJobMapper.deleteJobById(jobId) != 1) {
@@ -84,8 +84,8 @@ public void runJobById(Long jobId) {
ScheduleUtils.run(scheduler, schedulerJobMapper.getJobById(jobId));
}
+ @Transactional(rollbackFor = Exception.class)
@Override
- @Transactional
public void updateJobStatusById(Long jobId, Boolean status) {
if (status) {
ScheduleUtils.resumeJob(scheduler, jobId);
@@ -102,15 +102,15 @@ public List getJobLogListByDate(String startDate, String endDate
return scheduleJobLogMapper.getJobLogListByDate(startDate, endDate);
}
+ @Transactional(rollbackFor = Exception.class)
@Override
- @Transactional
public void saveJobLog(ScheduleJobLog jobLog) {
if (scheduleJobLogMapper.saveJobLog(jobLog) != 1) {
throw new PersistenceException("日志添加失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteJobLogByLogId(Long logId) {
if (scheduleJobLogMapper.deleteJobLogByLogId(logId) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/SiteSettingServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/SiteSettingServiceImpl.java
index 2342a1b18..ec0003644 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/SiteSettingServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/SiteSettingServiceImpl.java
@@ -3,13 +3,14 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
+import top.naccl.constant.SiteSettingConstants;
import top.naccl.entity.SiteSetting;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.SiteSettingMapper;
-import top.naccl.model.bean.Badge;
-import top.naccl.model.bean.Copyright;
-import top.naccl.model.bean.Favorite;
+import top.naccl.model.vo.Badge;
+import top.naccl.model.vo.Copyright;
+import top.naccl.model.vo.Favorite;
import top.naccl.model.vo.Introduction;
import top.naccl.service.RedisService;
import top.naccl.service.SiteSettingService;
@@ -40,19 +41,25 @@ public class SiteSettingServiceImpl implements SiteSettingService {
@Override
public Map> getList() {
List siteSettings = siteSettingMapper.getList();
- Map> map = new HashMap();
List type1 = new ArrayList();
List type2 = new ArrayList();
List type3 = new ArrayList();
for (SiteSetting s : siteSettings) {
- if (s.getType() == 1) {
- type1.add(s);
- } else if (s.getType() == 2) {
- type2.add(s);
- } else if (s.getType() == 3) {
- type3.add(s);
+ switch (s.getType()) {
+ case 1:
+ type1.add(s);
+ break;
+ case 2:
+ type2.add(s);
+ break;
+ case 3:
+ type3.add(s);
+ break;
+ default:
+ break;
}
}
+ Map> map = new HashMap(8);
map.put("type1", type1);
map.put("type2", type2);
map.put("type3", type3);
@@ -61,57 +68,78 @@ public Map> getList() {
@Override
public Map getSiteInfo() {
- String redisKey = RedisKeyConfig.SITE_INFO_MAP;
+ String redisKey = RedisKeyConstants.SITE_INFO_MAP;
Map siteInfoMapFromRedis = redisService.getMapByValue(redisKey);
if (siteInfoMapFromRedis != null) {
return siteInfoMapFromRedis;
}
List siteSettings = siteSettingMapper.getList();
- Map map = new HashMap();
- Map siteInfo = new HashMap();
+ Map siteInfo = new HashMap(2);
List badges = new ArrayList();
Introduction introduction = new Introduction();
List favorites = new ArrayList();
List rollTexts = new ArrayList();
for (SiteSetting s : siteSettings) {
- if (s.getType() == 1) {
- if ("copyright".equals(s.getNameEn())) {
- Copyright copyright = JacksonUtils.readValue(s.getValue(), Copyright.class);
- siteInfo.put(s.getNameEn(), copyright);
- } else {
- siteInfo.put(s.getNameEn(), s.getValue());
- }
- } else if (s.getType() == 2) {
- Badge badge = JacksonUtils.readValue(s.getValue(), Badge.class);
- badges.add(badge);
- } else if (s.getType() == 3) {
- if ("avatar".equals(s.getNameEn())) {
- introduction.setAvatar(s.getValue());
- } else if ("name".equals(s.getNameEn())) {
- introduction.setName(s.getValue());
- } else if ("github".equals(s.getNameEn())) {
- introduction.setGithub(s.getValue());
- } else if ("qq".equals(s.getNameEn())) {
- introduction.setQq(s.getValue());
- } else if ("bilibili".equals(s.getNameEn())) {
- introduction.setBilibili(s.getValue());
- } else if ("netease".equals(s.getNameEn())) {
- introduction.setNetease(s.getValue());
- } else if ("email".equals(s.getNameEn())) {
- introduction.setEmail(s.getValue());
- } else if ("favorite".equals(s.getNameEn())) {
- Favorite favorite = JacksonUtils.readValue(s.getValue(), Favorite.class);
- favorites.add(favorite);
- } else if ("rollText".equals(s.getNameEn())) {
- Matcher m = PATTERN.matcher(s.getValue());
- while (m.find()) {
- rollTexts.add(m.group(1));
+ switch (s.getType()) {
+ case 1:
+ if (SiteSettingConstants.COPYRIGHT.equals(s.getNameEn())) {
+ Copyright copyright = JacksonUtils.readValue(s.getValue(), Copyright.class);
+ siteInfo.put(s.getNameEn(), copyright);
+ } else {
+ siteInfo.put(s.getNameEn(), s.getValue());
}
- }
+ break;
+ case 2:
+ switch (s.getNameEn()) {
+ case SiteSettingConstants.AVATAR:
+ introduction.setAvatar(s.getValue());
+ break;
+ case SiteSettingConstants.NAME:
+ introduction.setName(s.getValue());
+ break;
+ case SiteSettingConstants.GITHUB:
+ introduction.setGithub(s.getValue());
+ break;
+ case SiteSettingConstants.TELEGRAM:
+ introduction.setTelegram(s.getValue());
+ break;
+ case SiteSettingConstants.QQ:
+ introduction.setQq(s.getValue());
+ break;
+ case SiteSettingConstants.BILIBILI:
+ introduction.setBilibili(s.getValue());
+ break;
+ case SiteSettingConstants.NETEASE:
+ introduction.setNetease(s.getValue());
+ break;
+ case SiteSettingConstants.EMAIL:
+ introduction.setEmail(s.getValue());
+ break;
+ case SiteSettingConstants.FAVORITE:
+ Favorite favorite = JacksonUtils.readValue(s.getValue(), Favorite.class);
+ favorites.add(favorite);
+ break;
+ case SiteSettingConstants.ROLL_TEXT:
+ Matcher m = PATTERN.matcher(s.getValue());
+ while (m.find()) {
+ rollTexts.add(m.group(1));
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case 3:
+ Badge badge = JacksonUtils.readValue(s.getValue(), Badge.class);
+ badges.add(badge);
+ break;
+ default:
+ break;
}
}
introduction.setFavorites(favorites);
introduction.setRollText(rollTexts);
+ Map map = new HashMap(8);
map.put("introduction", introduction);
map.put("siteInfo", siteInfo);
map.put("badges", badges);
@@ -124,37 +152,38 @@ public String getWebTitleSuffix() {
return siteSettingMapper.getWebTitleSuffix();
}
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateSiteSetting(List siteSettings, List deleteIds) {
- for (Integer id : deleteIds) {//删除
+ for (Integer id : deleteIds) {
+ //删除
deleteOneSiteSettingById(id);
}
for (LinkedHashMap s : siteSettings) {
SiteSetting siteSetting = JacksonUtils.convertValue(s, SiteSetting.class);
- if (siteSetting.getId() != null) {//修改
+ if (siteSetting.getId() != null) {
+ //修改
updateOneSiteSetting(siteSetting);
- } else {//添加
+ } else {
+ //添加
saveOneSiteSetting(siteSetting);
}
}
deleteSiteInfoRedisCache();
}
- @Transactional
public void saveOneSiteSetting(SiteSetting siteSetting) {
if (siteSettingMapper.saveSiteSetting(siteSetting) != 1) {
throw new PersistenceException("配置添加失败");
}
}
- @Transactional
public void updateOneSiteSetting(SiteSetting siteSetting) {
if (siteSettingMapper.updateSiteSetting(siteSetting) != 1) {
throw new PersistenceException("配置修改失败");
}
}
- @Transactional
public void deleteOneSiteSettingById(Integer id) {
if (siteSettingMapper.deleteSiteSettingById(id) != 1) {
throw new PersistenceException("配置删除失败");
@@ -165,6 +194,6 @@ public void deleteOneSiteSettingById(Integer id) {
* 删除站点信息缓存
*/
private void deleteSiteInfoRedisCache() {
- redisService.deleteCacheByKey(RedisKeyConfig.SITE_INFO_MAP);
+ redisService.deleteCacheByKey(RedisKeyConstants.SITE_INFO_MAP);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/TagServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/TagServiceImpl.java
index 3d6d86b97..1dafdf7dc 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/TagServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/TagServiceImpl.java
@@ -3,7 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.Tag;
import top.naccl.exception.NotFoundException;
import top.naccl.exception.PersistenceException;
@@ -32,7 +32,7 @@ public List getTagList() {
@Override
public List getTagListNotId() {
- String redisKey = RedisKeyConfig.TAG_CLOUD_LIST;
+ String redisKey = RedisKeyConstants.TAG_CLOUD_LIST;
List tagListFromRedis = redisService.getListByValue(redisKey);
if (tagListFromRedis != null) {
return tagListFromRedis;
@@ -47,13 +47,13 @@ public List getTagListByBlogId(Long blogId) {
return tagMapper.getTagListByBlogId(blogId);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveTag(Tag tag) {
if (tagMapper.saveTag(tag) != 1) {
throw new PersistenceException("标签添加失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.TAG_CLOUD_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.TAG_CLOUD_LIST);
}
@Override
@@ -70,23 +70,23 @@ public Tag getTagByName(String name) {
return tagMapper.getTagByName(name);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteTagById(Long id) {
if (tagMapper.deleteTagById(id) != 1) {
throw new PersistenceException("标签删除失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.TAG_CLOUD_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.TAG_CLOUD_LIST);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updateTag(Tag tag) {
if (tagMapper.updateTag(tag) != 1) {
throw new PersistenceException("标签更新失败");
}
- redisService.deleteCacheByKey(RedisKeyConfig.TAG_CLOUD_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.TAG_CLOUD_LIST);
//修改了标签名或颜色,可能有首页文章关联了标签,也要更新首页缓存
- redisService.deleteCacheByKey(RedisKeyConfig.HOME_BLOG_INFO_LIST);
+ redisService.deleteCacheByKey(RedisKeyConstants.HOME_BLOG_INFO_LIST);
}
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/UserServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/UserServiceImpl.java
index d6c31a557..17f04a4d8 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/UserServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/UserServiceImpl.java
@@ -5,10 +5,13 @@
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
-import top.naccl.mapper.UserMapper;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
import top.naccl.entity.User;
+import top.naccl.exception.NotFoundException;
+import top.naccl.mapper.UserMapper;
import top.naccl.service.UserService;
import top.naccl.util.HashUtils;
+import top.naccl.util.JwtUtils;
/**
* @Description: 用户业务层接口实现类
@@ -40,4 +43,24 @@ public User findUserByUsernameAndPassword(String username, String password) {
}
return user;
}
+
+ @Override
+ public User findUserById(Long id) {
+ User user = userMapper.findById(id);
+ if (user == null) {
+ throw new NotFoundException("用户不存在");
+ }
+ return user;
+ }
+
+ @Override
+ public boolean changeAccount(User user, String jwt) {
+ String username = JwtUtils.getTokenBody(jwt).getSubject();
+ user.setPassword(HashUtils.getBC(user.getPassword()));
+ if (userMapper.updateUserByUsername(username, user) != 1) {
+ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+ return false;
+ }
+ return true;
+ }
}
diff --git a/blog-api/src/main/java/top/naccl/service/impl/VisitLogServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/VisitLogServiceImpl.java
index 47735e2c4..bf8a56133 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/VisitLogServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/VisitLogServiceImpl.java
@@ -6,13 +6,13 @@
import top.naccl.entity.VisitLog;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.VisitLogMapper;
+import top.naccl.model.dto.UserAgentDTO;
import top.naccl.model.dto.VisitLogUuidTime;
import top.naccl.service.VisitLogService;
import top.naccl.util.IpAddressUtils;
import top.naccl.util.UserAgentUtils;
import java.util.List;
-import java.util.Map;
/**
* @Description: 访问日志业务层实现
@@ -36,22 +36,20 @@ public List getUUIDAndCreateTimeByYesterday() {
return visitLogMapper.getUUIDAndCreateTimeByYesterday();
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveVisitLog(VisitLog log) {
String ipSource = IpAddressUtils.getCityInfo(log.getIp());
- Map userAgentMap = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
- String os = userAgentMap.get("os");
- String browser = userAgentMap.get("browser");
+ UserAgentDTO userAgentDTO = userAgentUtils.parseOsAndBrowser(log.getUserAgent());
log.setIpSource(ipSource);
- log.setOs(os);
- log.setBrowser(browser);
+ log.setOs(userAgentDTO.getOs());
+ log.setBrowser(userAgentDTO.getBrowser());
if (visitLogMapper.saveVisitLog(log) != 1) {
throw new PersistenceException("日志添加失败");
}
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteVisitLogById(Long id) {
if (visitLogMapper.deleteVisitLogById(id) != 1) {
diff --git a/blog-api/src/main/java/top/naccl/service/impl/VisitRecordServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/VisitRecordServiceImpl.java
index 250412e11..5cc588431 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/VisitRecordServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/VisitRecordServiceImpl.java
@@ -2,6 +2,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import top.naccl.entity.VisitRecord;
import top.naccl.mapper.VisitRecordMapper;
import top.naccl.service.VisitRecordService;
@@ -16,6 +17,7 @@ public class VisitRecordServiceImpl implements VisitRecordService {
@Autowired
VisitRecordMapper visitRecordMapper;
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveVisitRecord(VisitRecord visitRecord) {
visitRecordMapper.saveVisitRecord(visitRecord);
diff --git a/blog-api/src/main/java/top/naccl/service/impl/VisitorServiceImpl.java b/blog-api/src/main/java/top/naccl/service/impl/VisitorServiceImpl.java
index 1ac77a343..3b532666a 100644
--- a/blog-api/src/main/java/top/naccl/service/impl/VisitorServiceImpl.java
+++ b/blog-api/src/main/java/top/naccl/service/impl/VisitorServiceImpl.java
@@ -3,10 +3,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.Visitor;
import top.naccl.exception.PersistenceException;
import top.naccl.mapper.VisitorMapper;
+import top.naccl.model.dto.UserAgentDTO;
import top.naccl.model.dto.VisitLogUuidTime;
import top.naccl.service.RedisService;
import top.naccl.service.VisitorService;
@@ -14,7 +15,6 @@
import top.naccl.util.UserAgentUtils;
import java.util.List;
-import java.util.Map;
/**
* @Description: 访客统计业务层实现
@@ -42,34 +42,33 @@ public List getNewVisitorIpSourceByYesterday() {
@Override
public boolean hasUUID(String uuid) {
- return visitorMapper.hasUUID(uuid) == 0 ? false : true;
+ return visitorMapper.hasUUID(uuid) != 0;
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void saveVisitor(Visitor visitor) {
String ipSource = IpAddressUtils.getCityInfo(visitor.getIp());
- Map userAgentMap = userAgentUtils.parseOsAndBrowser(visitor.getUserAgent());
- String os = userAgentMap.get("os");
- String browser = userAgentMap.get("browser");
+ UserAgentDTO userAgentDTO = userAgentUtils.parseOsAndBrowser(visitor.getUserAgent());
visitor.setIpSource(ipSource);
- visitor.setOs(os);
- visitor.setBrowser(browser);
+ visitor.setOs(userAgentDTO.getOs());
+ visitor.setBrowser(userAgentDTO.getBrowser());
if (visitorMapper.saveVisitor(visitor) != 1) {
throw new PersistenceException("访客添加失败");
}
}
+ @Transactional(rollbackFor = Exception.class)
@Override
public void updatePVAndLastTimeByUUID(VisitLogUuidTime dto) {
visitorMapper.updatePVAndLastTimeByUUID(dto);
}
- @Transactional
+ @Transactional(rollbackFor = Exception.class)
@Override
public void deleteVisitor(Long id, String uuid) {
//删除Redis中该访客的uuid
- redisService.deleteValueBySet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
+ redisService.deleteValueBySet(RedisKeyConstants.IDENTIFICATION_SET, uuid);
if (visitorMapper.deleteVisitorById(id) != 1) {
throw new PersistenceException("删除访客失败");
}
diff --git a/blog-api/src/main/java/top/naccl/task/RedisSyncScheduleTask.java b/blog-api/src/main/java/top/naccl/task/RedisSyncScheduleTask.java
index 37611f1cf..bb3e89a43 100644
--- a/blog-api/src/main/java/top/naccl/task/RedisSyncScheduleTask.java
+++ b/blog-api/src/main/java/top/naccl/task/RedisSyncScheduleTask.java
@@ -2,7 +2,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.service.BlogService;
import top.naccl.service.RedisService;
@@ -25,7 +25,7 @@ public class RedisSyncScheduleTask {
* 从Redis同步博客文章浏览量到数据库
*/
public void syncBlogViewsToDatabase() {
- String redisKey = RedisKeyConfig.BLOG_VIEWS_MAP;
+ String redisKey = RedisKeyConstants.BLOG_VIEWS_MAP;
Map blogViewsMap = redisService.getMapByHash(redisKey);
Set keys = blogViewsMap.keySet();
for (Integer key : keys) {
diff --git a/blog-api/src/main/java/top/naccl/task/VisitorSyncScheduleTask.java b/blog-api/src/main/java/top/naccl/task/VisitorSyncScheduleTask.java
index e3ecc7255..d6fab2591 100644
--- a/blog-api/src/main/java/top/naccl/task/VisitorSyncScheduleTask.java
+++ b/blog-api/src/main/java/top/naccl/task/VisitorSyncScheduleTask.java
@@ -3,7 +3,7 @@
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import top.naccl.config.RedisKeyConfig;
+import top.naccl.constant.RedisKeyConstants;
import top.naccl.entity.CityVisitor;
import top.naccl.entity.VisitRecord;
import top.naccl.model.dto.VisitLogUuidTime;
@@ -16,10 +16,8 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* @Description: 访客统计相关定时任务
@@ -40,46 +38,48 @@ public class VisitorSyncScheduleTask {
CityVisitorService cityVisitorService;
/**
- * 清空当天Redis访客标识
- * 记录当天的PV和UV
- * 更新当天所有访客的PV和最后访问时间
+ * 这个方法不应该被直接调用,应当作为定时任务的task,在每天0点执行
+ * 每日访问量很大时,这个任务可能很耗时
+ *
+ * 清空昨天Redis访客标识
+ * 记录昨天的PV和UV
+ * 更新昨天所有访客的PV和最后访问时间
* 更新城市新增访客UV数
*/
public void syncVisitInfoToDatabase() {
- //清空当天Redis的访客标识Set,以便统计每日UV
- redisService.deleteCacheByKey(RedisKeyConfig.IDENTIFICATION_SET);
+ //清空昨天Redis的访客标识Set,以便统计每日UV
+ redisService.deleteCacheByKey(RedisKeyConstants.IDENTIFICATION_SET);
//获取昨天的所有访问日志
- List yesterdayLogList = visitLogService.getUUIDAndCreateTimeByYesterday();
- //用Set去重得到当天所有访客的uuid
//为避免缓存击穿导致第二天的数据统计不准确,以数据库访问日志为准,而不从Redis中获取这个Set
//比如在这个定时任务执行期间,产生大量访客的请求,而这些访客的uuid都在任务执行结束后被清空了,没有被第二天的定时任务记录到
- Set uuidSet = new HashSet();
- Map PVMap = new HashMap();
- Map lastTimeMap = new HashMap();
+ List yesterdayLogList = visitLogService.getUUIDAndCreateTimeByYesterday();
+ //按每日UV 700的标准初始化map
+ //TODO 这里可以做个优化,根据近期每日UV数量做对应初始容量适配,避免访问量很大的站点执行此任务时调用过多的resize
+ Map PVMap = new HashMap(1024);
+ Map lastTimeMap = new HashMap(1024);
yesterdayLogList.forEach(log -> {
String uuid = log.getUuid();
Date createTime = log.getTime();
- //记录当天访客的uuid
- uuidSet.add(uuid);
//对应uuid的PV++
PVMap.merge(uuid, 1, Integer::sum);
//因为sql中order by create_time desc,直接put第一次出现的uuid的createTime,就是最后一次访问时间
lastTimeMap.putIfAbsent(uuid, createTime);
});
int pv = yesterdayLogList.size();
- int uv = uuidSet.size();
+ int uv = PVMap.size();
//获取昨天的日期字符串
String date = new SimpleDateFormat("MM-dd").format(DateUtils.addDays(new Date(), -1));
- //记录当天的PV和UV
+ //记录昨天的PV和UV
visitRecordService.saveVisitRecord(new VisitRecord(pv, uv, date));
- //更新当天所有访客的PV和最后访问时间到数据库
- uuidSet.forEach(uuid -> {
- VisitLogUuidTime uuidPVTimeDTO = new VisitLogUuidTime(uuid, lastTimeMap.get(uuid), PVMap.get(uuid));
+ //更新昨天所有访客的PV和最后访问时间到数据库
+ PVMap.forEach((uuid, views) -> {
+ VisitLogUuidTime uuidPVTimeDTO = new VisitLogUuidTime(uuid, lastTimeMap.get(uuid), views);
visitorService.updatePVAndLastTimeByUUID(uuidPVTimeDTO);
});
- //查询当天新增访客的ip来源
+ //查询昨天新增访客的ip来源
List ipSource = visitorService.getNewVisitorIpSourceByYesterday();
- Map cityVisitorMap = new HashMap();
+ //按每日新增UV 300的标准初始化map
+ Map cityVisitorMap = new HashMap(512);
ipSource.forEach(i -> {
if (i.startsWith("中国")) {
String[] split = i.split("\\|");
@@ -90,8 +90,6 @@ public void syncVisitInfoToDatabase() {
}
});
//更新城市新增访客UV数
- cityVisitorMap.forEach((k, v) -> {
- cityVisitorService.saveCityVisitor(new CityVisitor(k, v));
- });
+ cityVisitorMap.forEach((k, v) -> cityVisitorService.saveCityVisitor(new CityVisitor(k, v)));
}
}
diff --git a/blog-api/src/main/java/top/naccl/util/AopUtils.java b/blog-api/src/main/java/top/naccl/util/AopUtils.java
index d10958f88..d48896e14 100644
--- a/blog-api/src/main/java/top/naccl/util/AopUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/AopUtils.java
@@ -6,8 +6,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Set;
/**
* @Description: AOP工具类
@@ -15,6 +17,12 @@
* @Date: 2020年12月02日
*/
public class AopUtils {
+ private static Set ignoreParams = new HashSet() {
+ {
+ add("jwt");
+ }
+ };
+
/**
* 获取请求参数
*
@@ -26,7 +34,7 @@ public static Map getRequestParams(JoinPoint joinPoint) {
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) { - if (!isFilterObject(args[i])) { + if (!isIgnoreParams(parameterNames[i]) && !isFilterObject(args[i])) { map.put(parameterNames[i], args[i]); } } @@ -42,4 +50,14 @@ public static Map getRequestParams(JoinPoint joinPoint) {
private static boolean isFilterObject(final Object o) {
return o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof MultipartFile;
}
+
+ /**
+ * 判断是否忽略参数
+ *
+ * @param params
+ * @return
+ */
+ private static boolean isIgnoreParams(String params) {
+ return ignoreParams.contains(params);
+ }
}
diff --git a/blog-api/src/main/java/top/naccl/util/HashUtils.java b/blog-api/src/main/java/top/naccl/util/HashUtils.java
index 709f5f659..3e0d87840 100644
--- a/blog-api/src/main/java/top/naccl/util/HashUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/HashUtils.java
@@ -1,6 +1,6 @@
package top.naccl.util;
-import org.apache.commons.codec.digest.MurmurHash3;
+import cn.hutool.core.lang.hash.MurmurHash;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.DigestUtils;
@@ -17,7 +17,7 @@ public static String getMd5(CharSequence str) {
}
public static long getMurmurHash32(String str) {
- int i = MurmurHash3.hash32(str);
+ int i = MurmurHash.hash32(str);
long num = i < 0 ? Integer.MAX_VALUE - (long) i : i; return num; } diff --git a/blog-api/src/main/java/top/naccl/util/ImageUtils.java b/blog-api/src/main/java/top/naccl/util/ImageUtils.java deleted file mode 100644 index c8b2b1b8e..000000000 --- a/blog-api/src/main/java/top/naccl/util/ImageUtils.java +++ /dev/null @@ -1,123 +0,0 @@ -package top.naccl.util; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import top.naccl.exception.BadRequestException; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.UUID; - -/** - * @Description: 图片下载保存工具类 - * @Author: Naccl - * @Date: 2021-11-11 - */ -@Component -public class ImageUtils { - private static RestTemplate restTemplate = new RestTemplate(); - - //GitHub上传文件API - private static final String githubUploadApi = "https://api.github.com/repos/%s/%s/contents%s/%s"; - //GitHub上传文件API - private static final String cdnUrl4Github = "https://cdn.jsdelivr.net/gh/%s/%s%s/%s"; - - //服务访问地址,用于返回图片url - private static String serverUploadPath; - //服务器文件上传路径 - private static String serverUrl; - //GitHub token - private static String githubToken; - //GitHub用户名 - private static String githubUsername; - //GitHub仓库名 - private static String githubRepos; - //GitHub仓库路径 - private static String githubReposPath; - - @Value("${upload.path}") - public void setServerUploadPath(String serverUploadPath) { - this.serverUploadPath = serverUploadPath; - } - - @Value("${custom.url.api}") - public void setServerUrl(String serverUrl) { - this.serverUrl = serverUrl; - } - - @Value("${upload.github.token}") - public void setGithubToken(String githubToken) { - this.githubToken = githubToken; - } - - @Value("${upload.github.username}") - public void setGithubUsername(String githubUsername) { - this.githubUsername = githubUsername; - } - - @Value("${upload.github.repos}") - public void setGithubRepos(String githubRepos) { - this.githubRepos = githubRepos; - } - - @Value("${upload.github.repos.path}") - public void setGithubReposPath(String githubReposPath) { - this.githubReposPath = githubReposPath; - } - - @AllArgsConstructor - @Getter - static class ImageResource { - byte[] data; - //图片拓展名 jpg png - String type; - } - - public static ImageResource getImageByRequest(String url) { - ResponseEntity responseEntity = restTemplate.getForEntity(url, byte[].class);
- if ("image".equals(responseEntity.getHeaders().getContentType().getType())) {
- return new ImageResource(responseEntity.getBody(), responseEntity.getHeaders().getContentType().getSubtype());
- }
- throw new BadRequestException("response contentType unlike image");
- }
-
- public static String saveImage(ImageResource image) throws IOException {
- File folder = new File(serverUploadPath);
- if (!folder.exists()) {
- folder.mkdirs();
- }
- String fileName = UUID.randomUUID() + "." + image.getType();
- FileOutputStream fileOutputStream = new FileOutputStream(serverUploadPath + fileName);
- fileOutputStream.write(image.getData());
- fileOutputStream.close();
- return serverUrl + "/image/" + fileName;
- }
-
- public static String push2Github(ImageResource image) {
- String fileName = UUID.randomUUID() + "." + image.getType();
- String url = String.format(githubUploadApi, githubUsername, githubRepos, githubReposPath, fileName);
- String imgBase64 = Base64.getEncoder().encodeToString(image.getData());
-
- HttpHeaders headers = new HttpHeaders();
- headers.put("Authorization", Collections.singletonList("token " + githubToken));
-
- HashMap body = new HashMap();
- body.put("message", "Add files via NBlog");
- body.put("content", imgBase64);
-
- HttpEntity httpEntity = new HttpEntity(body, headers);
- restTemplate.put(url, httpEntity);
-
- return String.format(cdnUrl4Github, githubUsername, githubRepos, githubReposPath, fileName);
- }
-}
diff --git a/blog-api/src/main/java/top/naccl/util/IpAddressUtils.java b/blog-api/src/main/java/top/naccl/util/IpAddressUtils.java
index d17f186de..b3280bc9e 100644
--- a/blog-api/src/main/java/top/naccl/util/IpAddressUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/IpAddressUtils.java
@@ -2,10 +2,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
-import org.lionsoul.ip2region.DataBlock;
-import org.lionsoul.ip2region.DbConfig;
-import org.lionsoul.ip2region.DbSearcher;
-import org.lionsoul.ip2region.Util;
+import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
@@ -32,7 +29,10 @@ public class IpAddressUtils {
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
- String ip = request.getHeader("x-forwarded-for");
+ String ip = request.getHeader("X-Real-IP");
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("x-forwarded-for");
+ }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
@@ -61,26 +61,24 @@ public static String getIpAddress(HttpServletRequest request) {
return StringUtils.substringBefore(ip, ",");
}
- private static DbSearcher searcher;
+ private static Searcher searcher;
private static Method method;
/**
* 在服务启动时加载 ip2region.db 到内存中
* 解决打包jar后找不到 ip2region.db 的问题
+ *
+ * @throws Exception 出现异常应该直接抛出终止程序启动,避免后续invoke时出现更多错误
*/
@PostConstruct
- private void initIp2regionResource() {
- try {
- InputStream inputStream = new ClassPathResource("/ipdb/ip2region.db").getInputStream();
- //将 ip2region.db 转为 ByteArray
- byte[] dbBinStr = FileCopyUtils.copyToByteArray(inputStream);
- DbConfig dbConfig = new DbConfig();
- searcher = new DbSearcher(dbConfig, dbBinStr);
- //二进制方式初始化 DBSearcher,需要使用基于内存的查找算法 memorySearch
- method = searcher.getClass().getMethod("memorySearch", String.class);
- } catch (Exception e) {
- log.error("initIp2regionResource exception:", e);
- }
+ private void initIp2regionResource() throws Exception {
+ InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream();
+ //将 ip2region.db 转为 ByteArray
+ byte[] dbBinStr = FileCopyUtils.copyToByteArray(inputStream);
+ // 2、使用上述的 dbBinStr 创建一个完全基于内存的查询对象。
+ searcher = new Searcher(null, null, dbBinStr);
+ //二进制方式初始化 DBSearcher,需要使用基于内存的查找算法 memorySearch
+ method = searcher.getClass().getMethod("search", String.class);
}
/**
@@ -90,13 +88,8 @@ private void initIp2regionResource() {
* @return
*/
public static String getCityInfo(String ip) {
- if (ip == null || !Util.isIpAddress(ip)) {
- log.error("Error: Invalid ip address");
- return null;
- }
try {
- DataBlock dataBlock = (DataBlock) method.invoke(searcher, ip);
- String ipInfo = dataBlock.getRegion();
+ String ipInfo = (String) method.invoke(searcher, ip);
if (!StringUtils.isEmpty(ipInfo)) {
ipInfo = ipInfo.replace("|0", "");
ipInfo = ipInfo.replace("0|", "");
@@ -105,6 +98,12 @@ public static String getCityInfo(String ip) {
} catch (Exception e) {
log.error("getCityInfo exception:", e);
}
- return null;
+ return "";
+ }
+ public static void main(String[] args) throws Exception {
+ IpAddressUtils ipAddressUtils = new IpAddressUtils();
+ ipAddressUtils.initIp2regionResource();
+ String cityInfo = IpAddressUtils.getCityInfo("14.215.177.39");
+ System.out.println(cityInfo);
}
}
\ No newline at end of file
diff --git a/blog-api/src/main/java/top/naccl/util/JwtUtils.java b/blog-api/src/main/java/top/naccl/util/JwtUtils.java
index 8743d7b8b..9a895fa27 100644
--- a/blog-api/src/main/java/top/naccl/util/JwtUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/JwtUtils.java
@@ -22,12 +22,12 @@ public class JwtUtils {
@Value("${token.secretKey}")
public void setSecretKey(String secretKey) {
- this.secretKey = secretKey;
+ JwtUtils.secretKey = secretKey;
}
@Value("${token.expireTime}")
public void setExpireTime(long expireTime) {
- this.expireTime = expireTime;
+ JwtUtils.expireTime = expireTime;
}
/**
diff --git a/blog-api/src/main/java/top/naccl/util/MailUtils.java b/blog-api/src/main/java/top/naccl/util/MailUtils.java
index 04ab9b276..da1b32f34 100644
--- a/blog-api/src/main/java/top/naccl/util/MailUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/MailUtils.java
@@ -19,8 +19,8 @@
* @Author: Naccl
* @Date: 2020年10月10日
*/
-@Component
@EnableAsync
+@Component
public class MailUtils {
@Autowired
private JavaMailSender javaMailSender;
diff --git a/blog-api/src/main/java/top/naccl/util/QQInfoUtils.java b/blog-api/src/main/java/top/naccl/util/QQInfoUtils.java
index 1a268f701..d7343667f 100644
--- a/blog-api/src/main/java/top/naccl/util/QQInfoUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/QQInfoUtils.java
@@ -1,9 +1,10 @@
package top.naccl.util;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.client.RestTemplate;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import top.naccl.model.vo.QqResultVO;
+import top.naccl.model.vo.QqVO;
+import top.naccl.util.upload.UploadUtils;
/**
* @Description: 获取QQ昵称头像信息
@@ -12,52 +13,51 @@
*/
public class QQInfoUtils {
private static RestTemplate restTemplate = new RestTemplate();
- private static final String QQ_NICKNAME_URL = "https://r.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins={1}";
+ // 原接口半失效,需要提供cookie才可使用,暂时替换为备用接口,感谢 苏晓晴 大佬友情提供
+ private static final String QQ_NICKNAME_URL = "https://api.toubiec.cn/api/qqinfo_v4.php?qq={1}";
private static final String QQ_AVATAR_URL = "https://q.qlogo.cn/g?b=qq&nk=%s&s=100";
/**
* 获取QQ昵称
*
- * @param qq
- * @return
- * @throws UnsupportedEncodingException
+ * @param qq qq
*/
- public static String getQQNickname(String qq) throws UnsupportedEncodingException {
- String res = restTemplate.getForObject(QQ_NICKNAME_URL, String.class, qq);
- byte[] bytes = res.getBytes("iso-8859-1");
- String nickname = new String(bytes, "gb18030").split(",")[6].replace("\"", "");
- if ("".equals(nickname)) {
- return "nickname";
+ public static String getQQNickname(String qq) {
+ QqResultVO qqResultVO = restTemplate.getForObject(QQ_NICKNAME_URL, QqResultVO.class, qq);
+ if (qqResultVO != null) {
+ return new ObjectMapper().convertValue(qqResultVO.getData(), QqVO.class).getName();
}
- return nickname;
- }
-
- private static ImageUtils.ImageResource getImageResourceByQQ(String qq) {
- return ImageUtils.getImageByRequest(String.format(QQ_AVATAR_URL, qq));
+ return "nickname";
}
/**
- * 将QQ头像下载到本地,并返回访问本地图片的URL
+ * 从网络获取QQ头像数据
*
* @param qq
- * @return 访问本地图片的URL
- * @throws IOException
+ * @return
*/
- public static String getQQAvatarURLByServerUpload(String qq) throws IOException {
- return ImageUtils.saveImage(getImageResourceByQQ(qq));
+ private static UploadUtils.ImageResource getImageResourceByQQ(String qq) {
+ return UploadUtils.getImageByRequest(String.format(QQ_AVATAR_URL, qq));
}
/**
- * 将QQ头像上传至GitHub仓库,并返回CDN链接
+ * 获取QQ头像URL
*
* @param qq
- * @return 指向该图片的jsDelivr CDN链接
+ * @return
+ * @throws Exception
*/
- public static String getQQAvatarURLByGithubUpload(String qq) {
- return ImageUtils.push2Github(getImageResourceByQQ(qq));
+ public static String getQQAvatarUrl(String qq) throws Exception {
+ return UploadUtils.upload(getImageResourceByQQ(qq));
}
- public static boolean isQQNumber(String nickname) {
- return nickname.matches("^[1-9][0-9]{4,10}$");
+ /**
+ * 判断是否QQ号
+ *
+ * @param number
+ * @return
+ */
+ public static boolean isQQNumber(String number) {
+ return number.matches("^[1-9][0-9]{4,10}$");
}
-}
\ No newline at end of file
+}
diff --git a/blog-api/src/main/java/top/naccl/util/UserAgentUtils.java b/blog-api/src/main/java/top/naccl/util/UserAgentUtils.java
index cef34d955..47110e816 100644
--- a/blog-api/src/main/java/top/naccl/util/UserAgentUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/UserAgentUtils.java
@@ -3,9 +3,7 @@
import nl.basjes.parse.useragent.UserAgent;
import nl.basjes.parse.useragent.UserAgentAnalyzer;
import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
+import top.naccl.model.dto.UserAgentDTO;
/**
* @Description: UserAgent解析工具类
@@ -19,9 +17,11 @@ public class UserAgentUtils {
public UserAgentUtils() {
this.uaa = UserAgentAnalyzer
.newBuilder()
+ .useJava8CompatibleCaching()
+ .withCache(10000)
.hideMatcherLoadStats()
- .withField("OperatingSystemNameVersionMajor")
- .withField("AgentNameVersion")
+ .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION_MAJOR)
+ .withField(UserAgent.AGENT_NAME_VERSION)
.build();
}
@@ -31,13 +31,10 @@ public UserAgentUtils() {
* @param userAgent
* @return
*/
- public Map parseOsAndBrowser(String userAgent) {
+ public UserAgentDTO parseOsAndBrowser(String userAgent) {
UserAgent agent = uaa.parse(userAgent);
- String os = agent.getValue("OperatingSystemNameVersionMajor");
- String browser = agent.getValue("AgentNameVersion");
- Map map = new HashMap();
- map.put("os", os);
- map.put("browser", browser);
- return map;
+ String os = agent.getValue(UserAgent.OPERATING_SYSTEM_NAME_VERSION_MAJOR);
+ String browser = agent.getValue(UserAgent.AGENT_NAME_VERSION);
+ return new UserAgentDTO(os, browser);
}
}
diff --git a/blog-api/src/main/java/top/naccl/util/comment/CommentUtils.java b/blog-api/src/main/java/top/naccl/util/comment/CommentUtils.java
new file mode 100644
index 000000000..a6bdc5453
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/util/comment/CommentUtils.java
@@ -0,0 +1,318 @@
+package top.naccl.util.comment;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.stereotype.Component;
+import top.naccl.config.properties.BlogProperties;
+import top.naccl.constant.PageConstants;
+import top.naccl.constant.RedisKeyConstants;
+import top.naccl.entity.User;
+import top.naccl.model.dto.Comment;
+import top.naccl.model.vo.FriendInfo;
+import top.naccl.service.AboutService;
+import top.naccl.service.BlogService;
+import top.naccl.service.FriendService;
+import top.naccl.service.RedisService;
+import top.naccl.service.UserService;
+import top.naccl.util.HashUtils;
+import top.naccl.util.IpAddressUtils;
+import top.naccl.util.MailUtils;
+import top.naccl.util.QQInfoUtils;
+import top.naccl.util.StringUtils;
+import top.naccl.util.comment.channel.ChannelFactory;
+import top.naccl.util.comment.channel.CommentNotifyChannel;
+import top.naccl.enums.CommentOpenStateEnum;
+import top.naccl.enums.CommentPageEnum;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 评论工具类
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@Component
+@DependsOn("springContextUtils")
+public class CommentUtils {
+ @Autowired
+ private BlogProperties blogProperties;
+ @Autowired
+ private MailUtils mailUtils;
+ @Autowired
+ private AboutService aboutService;
+ @Autowired
+ private FriendService friendService;
+ @Autowired
+ private UserService userService;
+ @Autowired
+ private RedisService redisService;
+
+ private static BlogService blogService;
+
+ private CommentNotifyChannel notifyChannel;
+ /**
+ * 新评论是否默认公开
+ */
+ private Boolean commentDefaultOpen;
+
+ @Autowired
+ public void setBlogService(BlogService blogService) {
+ CommentUtils.blogService = blogService;
+ }
+
+ @Value("${comment.notify.channel}")
+ public void setNotifyChannel(String channelName) {
+ this.notifyChannel = ChannelFactory.getChannel(channelName);
+ }
+
+ @Value("${comment.default-open}")
+ public void setCommentDefaultOpen(Boolean commentDefaultOpen) {
+ this.commentDefaultOpen = commentDefaultOpen;
+ }
+
+ /**
+ * 判断是否发送提醒
+ * 6种情况:
+ * 1.我以父评论提交:不用提醒
+ * 2.我回复我自己:不用提醒
+ * 3.我回复访客的评论:只提醒该访客
+ * 4.访客以父评论提交:只提醒我自己
+ * 5.访客回复我的评论:只提醒我自己
+ * 6.访客回复访客的评论(即使是他自己先前的评论):提醒我自己和他回复的评论
+ *
+ * @param comment 当前收到的评论
+ * @param isVisitorComment 是否访客评论
+ * @param parentComment 父评论
+ */
+ public void judgeSendNotify(Comment comment, boolean isVisitorComment, top.naccl.entity.Comment parentComment) {
+ if (parentComment != null && !parentComment.getAdminComment() && parentComment.getNotice()) {
+ //我回复访客的评论,且对方接收提醒,邮件提醒对方(3)
+ //访客回复访客的评论(即使是他自己先前的评论),且对方接收提醒,邮件提醒对方(6)
+ sendMailToParentComment(parentComment, comment);
+ }
+ if (isVisitorComment) {
+ //访客以父评论提交,只提醒我自己(4)
+ //访客回复我的评论,提醒我自己(5)
+ //访客回复访客的评论,不管对方是否接收提醒,都要提醒我有新评论(6)
+ notifyMyself(comment);
+ }
+ }
+
+ /**
+ * 发送邮件提醒回复对象
+ *
+ * @param parentComment 父评论
+ * @param comment 当前收到的评论
+ */
+ private void sendMailToParentComment(top.naccl.entity.Comment parentComment, Comment comment) {
+ CommentPageEnum commentPageEnum = getCommentPageEnum(comment);
+ Map map = new HashMap(16);
+ map.put("parentNickname", parentComment.getNickname());
+ map.put("nickname", comment.getNickname());
+ map.put("title", commentPageEnum.getTitle());
+ map.put("time", comment.getCreateTime());
+ map.put("parentContent", parentComment.getContent());
+ map.put("content", comment.getContent());
+ map.put("url", blogProperties.getView() + commentPageEnum.getPath());
+ String toAccount = parentComment.getEmail();
+ String subject = "您在 " + blogProperties.getName() + " 的评论有了新回复";
+ mailUtils.sendHtmlTemplateMail(map, toAccount, subject, "guest.html");
+ }
+
+ /**
+ * 通过指定方式通知自己
+ *
+ * @param comment 当前收到的评论
+ */
+ private void notifyMyself(Comment comment) {
+ notifyChannel.notifyMyself(comment);
+ }
+
+ /**
+ * 获取评论对应的页面
+ *
+ * @param comment 当前收到的评论
+ * @return CommentPageEnum
+ */
+ public static CommentPageEnum getCommentPageEnum(Comment comment) {
+ CommentPageEnum commentPageEnum = CommentPageEnum.UNKNOWN;
+ switch (comment.getPage()) {
+ case 0:
+ //普通博客
+ commentPageEnum = CommentPageEnum.BLOG;
+ commentPageEnum.setTitle(blogService.getTitleByBlogId(comment.getBlogId()));
+ commentPageEnum.setPath("/blog/" + comment.getBlogId());
+ break;
+ case 1:
+ //关于我页面
+ commentPageEnum = CommentPageEnum.ABOUT;
+ break;
+ case 2:
+ //友链页面
+ commentPageEnum = CommentPageEnum.FRIEND;
+ break;
+ default:
+ break;
+ }
+ return commentPageEnum;
+ }
+
+ /**
+ * 查询对应页面评论是否开启
+ *
+ * @param page 页面分类(0普通文章,1关于我,2友链)
+ * @param blogId 如果page==0,需要博客id参数,校验文章是否公开状态
+ * @return CommentOpenStateEnum
+ */
+ public CommentOpenStateEnum judgeCommentState(Integer page, Long blogId) {
+ switch (page) {
+ case PageConstants.BLOG:
+ //普通博客
+ Boolean commentEnabled = blogService.getCommentEnabledByBlogId(blogId);
+ Boolean published = blogService.getPublishedByBlogId(blogId);
+ if (commentEnabled == null || published == null) {
+ //未查询到此博客
+ return CommentOpenStateEnum.NOT_FOUND;
+ } else if (!published) {
+ //博客未公开
+ return CommentOpenStateEnum.NOT_FOUND;
+ } else if (!commentEnabled) {
+ //博客评论已关闭
+ return CommentOpenStateEnum.CLOSE;
+ }
+ //判断文章是否存在密码
+ String password = blogService.getBlogPassword(blogId);
+ if (!StringUtils.isEmpty(password)) {
+ return CommentOpenStateEnum.PASSWORD;
+ }
+ break;
+ case PageConstants.ABOUT:
+ //关于我页面
+ if (!aboutService.getAboutCommentEnabled()) {
+ //页面评论已关闭
+ return CommentOpenStateEnum.CLOSE;
+ }
+ break;
+ case PageConstants.FRIEND:
+ //友链页面
+ FriendInfo friendInfo = friendService.getFriendInfo(true, false);
+ if (!friendInfo.getCommentEnabled()) {
+ //页面评论已关闭
+ return CommentOpenStateEnum.CLOSE;
+ }
+ break;
+ default:
+ break;
+ }
+ return CommentOpenStateEnum.OPEN;
+ }
+
+ /**
+ * 对于昵称不是QQ号的评论,根据昵称Hash设置头像
+ *
+ * @param comment 当前收到的评论
+ */
+ private void setCommentRandomAvatar(Comment comment) {
+ //设置随机头像
+ //根据评论昵称取Hash,保证每一个昵称对应一个头像
+ long nicknameHash = HashUtils.getMurmurHash32(comment.getNickname());
+ //计算对应的头像
+ long num = nicknameHash % 6 + 1;
+ String avatar = "/img/comment-avatar/" + num + ".jpg";
+ comment.setAvatar(avatar);
+ }
+
+ /**
+ * 通用博主评论属性
+ *
+ * @param comment 评论DTO
+ * @param admin 博主信息
+ */
+ private void setGeneralAdminComment(Comment comment, User admin) {
+ comment.setAdminComment(true);
+ comment.setCreateTime(new Date());
+ comment.setPublished(true);
+ comment.setAvatar(admin.getAvatar());
+ comment.setWebsite("/");
+ comment.setNickname(admin.getNickname());
+ comment.setEmail(admin.getEmail());
+ comment.setNotice(false);
+ }
+
+ /**
+ * 为[Telegram快捷回复]方式设置评论属性
+ *
+ * @param comment 评论DTO
+ */
+ public void setAdminCommentByTelegramAction(Comment comment) {
+ //查出博主信息,默认id为1的记录就是博主
+ User admin = userService.findUserById(1L);
+
+ setGeneralAdminComment(comment, admin);
+ comment.setIp("via Telegram");
+ }
+
+ /**
+ * 设置博主评论属性
+ *
+ * @param comment 当前收到的评论
+ * @param request 用于获取ip
+ * @param admin 博主信息
+ */
+ public void setAdminComment(Comment comment, HttpServletRequest request, User admin) {
+ setGeneralAdminComment(comment, admin);
+ comment.setIp(IpAddressUtils.getIpAddress(request));
+ }
+
+ /**
+ * 设置访客评论属性
+ *
+ * @param comment 当前收到的评论
+ * @param request 用于获取ip
+ */
+ public void setVisitorComment(Comment comment, HttpServletRequest request) {
+ comment.setNickname(comment.getNickname().trim());
+ setCommentRandomAvatar(comment);
+
+ //check website
+ if (!isValidUrl(comment.getWebsite())) {
+ comment.setWebsite("");
+ }
+ comment.setAdminComment(false);
+ comment.setCreateTime(new Date());
+ comment.setPublished(commentDefaultOpen);
+ comment.setEmail(comment.getEmail().trim());
+ comment.setIp(IpAddressUtils.getIpAddress(request));
+ }
+
+ /**
+ * 设置QQ头像,复用已上传过的QQ头像,不再重复上传
+ *
+ * @param comment 当前收到的评论
+ * @param qq QQ号
+ * @throws Exception 上传QQ头像时可能抛出的异常
+ */
+ private void setCommentQQAvatar(Comment comment, String qq) throws Exception {
+ String uploadAvatarUrl = (String) redisService.getValueByHashKey(RedisKeyConstants.QQ_AVATAR_URL_MAP, qq);
+ if (StringUtils.isEmpty(uploadAvatarUrl)) {
+ uploadAvatarUrl = QQInfoUtils.getQQAvatarUrl(qq);
+ redisService.saveKVToHash(RedisKeyConstants.QQ_AVATAR_URL_MAP, qq, uploadAvatarUrl);
+ }
+ comment.setAvatar(uploadAvatarUrl);
+ }
+
+ /**
+ * URL合法性校验
+ *
+ * @param url url
+ * @return 是否合法
+ */
+ private static boolean isValidUrl(String url) {
+ return url.matches("^https?://([^!@#$%^&*?.\\s-]([^!@#$%^&*?.\\s]{0,63}[^!@#$%^&*?.\\s])?\\.)+[a-z]{2,6}/?");
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/util/comment/channel/ChannelFactory.java b/blog-api/src/main/java/top/naccl/util/comment/channel/ChannelFactory.java
new file mode 100644
index 000000000..1d9b15faa
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/util/comment/channel/ChannelFactory.java
@@ -0,0 +1,27 @@
+package top.naccl.util.comment.channel;
+
+import top.naccl.constant.CommentConstants;
+import top.naccl.util.common.SpringContextUtils;
+
+/**
+ * 评论提醒方式
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+public class ChannelFactory {
+ /**
+ * 创建评论提醒方式
+ *
+ * @param channelName 方式名称
+ * @return
+ */
+ public static CommentNotifyChannel getChannel(String channelName) {
+ if (CommentConstants.TELEGRAM.equalsIgnoreCase(channelName)) {
+ return SpringContextUtils.getBean("telegramChannel", CommentNotifyChannel.class);
+ } else if (CommentConstants.MAIL.equalsIgnoreCase(channelName)) {
+ return SpringContextUtils.getBean("mailChannel", CommentNotifyChannel.class);
+ }
+ throw new RuntimeException("Unsupported value in [application.properties]: [comment.notify.channel]");
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/util/comment/channel/CommentNotifyChannel.java b/blog-api/src/main/java/top/naccl/util/comment/channel/CommentNotifyChannel.java
new file mode 100644
index 000000000..d93703843
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/util/comment/channel/CommentNotifyChannel.java
@@ -0,0 +1,18 @@
+package top.naccl.util.comment.channel;
+
+import top.naccl.model.dto.Comment;
+
+/**
+ * 评论提醒方式
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+public interface CommentNotifyChannel {
+ /**
+ * 通过指定方式通知自己
+ *
+ * @param comment 当前收到的评论
+ */
+ void notifyMyself(Comment comment);
+}
diff --git a/blog-api/src/main/java/top/naccl/util/comment/channel/MailChannel.java b/blog-api/src/main/java/top/naccl/util/comment/channel/MailChannel.java
new file mode 100644
index 000000000..2bd122041
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/util/comment/channel/MailChannel.java
@@ -0,0 +1,54 @@
+package top.naccl.util.comment.channel;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.mail.MailProperties;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import top.naccl.config.properties.BlogProperties;
+import top.naccl.model.dto.Comment;
+import top.naccl.util.MailUtils;
+import top.naccl.enums.CommentPageEnum;
+import top.naccl.util.comment.CommentUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 邮件提醒方式
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@Lazy
+@Component
+public class MailChannel implements CommentNotifyChannel {
+ @Autowired
+ private BlogProperties blogProperties;
+ @Autowired
+ private MailProperties mailProperties;
+ @Autowired
+ private MailUtils mailUtils;
+
+ /**
+ * 发送邮件提醒我自己
+ *
+ * @param comment 当前收到的评论
+ */
+ @Override
+ public void notifyMyself(Comment comment) {
+ CommentPageEnum commentPageEnum = CommentUtils.getCommentPageEnum(comment);
+ Map map = new HashMap(16);
+ map.put("title", commentPageEnum.getTitle());
+ map.put("time", comment.getCreateTime());
+ map.put("nickname", comment.getNickname());
+ map.put("content", comment.getContent());
+ map.put("ip", comment.getIp());
+ map.put("email", comment.getEmail());
+ map.put("status", comment.getPublished() ? "公开" : "待审核");
+ map.put("url", blogProperties.getView() + commentPageEnum.getPath());
+ map.put("manageUrl", blogProperties.getCms() + "/comments");
+ String toAccount = mailProperties.getUsername();
+ String subject = blogProperties.getName() + " 收到新评论";
+ mailUtils.sendHtmlTemplateMail(map, toAccount, subject, "owner.html");
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/util/comment/channel/TelegramChannel.java b/blog-api/src/main/java/top/naccl/util/comment/channel/TelegramChannel.java
new file mode 100644
index 000000000..c0581830d
--- /dev/null
+++ b/blog-api/src/main/java/top/naccl/util/comment/channel/TelegramChannel.java
@@ -0,0 +1,92 @@
+package top.naccl.util.comment.channel;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import top.naccl.config.properties.BlogProperties;
+import top.naccl.config.properties.TelegramProperties;
+import top.naccl.enums.CommentPageEnum;
+import top.naccl.model.dto.Comment;
+import top.naccl.util.StringUtils;
+import top.naccl.util.telegram.TelegramUtils;
+import top.naccl.util.comment.CommentUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Telegram提醒方式
+ *
+ * @author: Naccl
+ * @date: 2022年01月22日
+ */
+@Slf4j
+@Lazy
+@Component
+public class TelegramChannel implements CommentNotifyChannel {
+ private TelegramUtils telegramUtils;
+
+ private BlogProperties blogProperties;
+
+ private TelegramProperties telegramProperties;
+
+ private SimpleDateFormat simpleDateFormat;
+
+ public TelegramChannel(TelegramUtils telegramUtils, BlogProperties blogProperties, TelegramProperties telegramProperties) {
+ this.telegramUtils = telegramUtils;
+ this.blogProperties = blogProperties;
+ this.telegramProperties = telegramProperties;
+
+ this.simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+ this.simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+
+ log.info("TelegramChannel instantiating");
+ telegramUtils.setWebhook();
+ }
+
+ /**
+ * 发送Telegram消息提醒我自己
+ *
+ * @param comment 当前收到的评论
+ */
+ @Override
+ public void notifyMyself(Comment comment) {
+ String url = telegramProperties.getApi() + telegramProperties.getToken() + TelegramUtils.SEND_MESSAGE;
+ String content = getContent(comment);
+ Map messageBody = telegramUtils.getMessageBody(content);
+ telegramUtils.sendByAutoCheckReverseProxy(url, messageBody);
+ }
+
+ private String getContent(Comment comment) {
+ CommentPageEnum commentPageEnum = CommentUtils.getCommentPageEnum(comment);
+ return String.format(
+ "您的文章《%s》有了新的评论~\n" +
+ "\n" +
+ "%s 给您的评论:\n" +
+ "\n" +
+ "%s
\n" +
+ "\n" +
+ "其他信息:\n" +
+ "评论ID:%d\n" +
+ "IP:%s\n" +
+ "%s" +
+ "时间:%s\n" +
+ "邮箱:%s\n" +
+ "%s" +
+ "状态:%s [管理评论]\n",
+ blogProperties.getView() + commentPageEnum.getPath(),
+ commentPageEnum.getTitle(),
+ comment.getNickname(),
+ comment.getContent(),
+ comment.getId(),
+ comment.getIp(),
+ StringUtils.isEmpty(comment.getQq()) ? "" : "QQ:" + comment.getQq() + "\n",
+ simpleDateFormat.format(comment.getCreateTime()),
+ comment.getEmail(),
+ StringUtils.isEmpty(comment.getWebsite()) ? "" : "网站:" + comment.getWebsite() + "\n",
+ comment.getPublished() ? "公开" : "待审核",
+ blogProperties.getCms() + "/blog/comment/list"
+ );
+ }
+}
diff --git a/blog-api/src/main/java/top/naccl/util/markdown/MarkdownUtils.java b/blog-api/src/main/java/top/naccl/util/markdown/MarkdownUtils.java
index fc8f189c8..6d7702458 100644
--- a/blog-api/src/main/java/top/naccl/util/markdown/MarkdownUtils.java
+++ b/blog-api/src/main/java/top/naccl/util/markdown/MarkdownUtils.java
@@ -6,6 +6,7 @@
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
import org.commonmark.ext.task.list.items.TaskListItemsExtension;
+import org.commonmark.node.Image;
import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
@@ -70,6 +71,12 @@ public AttributeProvider create(AttributeProviderContext context) {
private static class CustomAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName, Map attributes) {
+ // 为懒加载提供属性
+ if (node instanceof Image) {
+ Image n = (Image) node;
+ attributes.put("data-src", n.getDestination());
+ attributes.remove("src");
+ }
//改变a标签的target属性为_blank
if (node instanceof Link) {
Link n = (Link) node;
diff --git a/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverDelimiterProcessor.java b/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverDelimiterProcessor.java
index e2ca99129..b8485c5ad 100644
--- a/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverDelimiterProcessor.java
+++ b/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverDelimiterProcessor.java
@@ -1,6 +1,8 @@
package top.naccl.util.markdown.ext.cover.internal;
import org.commonmark.node.Node;
+import org.commonmark.node.Nodes;
+import org.commonmark.node.SourceSpans;
import org.commonmark.node.Text;
import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
@@ -27,28 +29,29 @@ public int getMinLength() {
return 2;
}
- @Override
- public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
- if (opener.length()>= 2 && closer.length()>= 2) {
- // Use exactly two delimiters even if we have more, and don't care about internal openers/closers.
- return 2;
- } else {
- return 0;
- }
- }
+ @Override
+ public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
+ if (openingRun.length()>= 2 && closingRun.length()>= 2) {
+ // Use exactly two delimiters even if we have more, and don't care about internal openers/closers.
+ Text opener = openingRun.getOpener();
- @Override
- public void process(Text opener, Text closer, int delimiterCount) {
- // Wrap nodes between delimiters in cover.
- Node cover = new Cover();
-
- Node tmp = opener.getNext();
- while (tmp != null && tmp != closer) {
- Node next = tmp.getNext();
- cover.appendChild(tmp);
- tmp = next;
- }
-
- opener.insertAfter(cover);
- }
+ // Wrap nodes between delimiters in cover.
+ Node cover = new Cover();
+
+ SourceSpans sourceSpans = new SourceSpans();
+ sourceSpans.addAllFrom(openingRun.getOpeners(2));
+
+ for (Node node : Nodes.between(opener, closingRun.getCloser())) {
+ cover.appendChild(node);
+ sourceSpans.addAll(node.getSourceSpans());
+ }
+
+ sourceSpans.addAllFrom(closingRun.getClosers(2));
+ cover.setSourceSpans(sourceSpans.getSourceSpans());
+
+ opener.insertAfter(cover);
+ return 2;
+ }
+ return 0;
+ }
}
diff --git a/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverHtmlNodeRenderer.java b/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverHtmlNodeRenderer.java
index 7f947d732..b79fb7ccc 100644
--- a/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverHtmlNodeRenderer.java
+++ b/blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverHtmlNodeRenderer.java
@@ -23,7 +23,7 @@ public CoverHtmlNodeRenderer(HtmlNodeRendererContext context) {
@Override
public void render(Node node) {
- Map attributes = new HashMap();
+ Map attributes = new HashMap(2);
attributes.put("class", "m-text-cover");
html.tag("span", context.extendAttributes(node, "span", attributes));
renderChildren(node);
diff --git a/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuDelimiterProcessor.java b/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuDelimiterProcessor.java
index 22e274afd..ff5f9460a 100644
--- a/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuDelimiterProcessor.java
+++ b/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuDelimiterProcessor.java
@@ -1,6 +1,8 @@
package top.naccl.util.markdown.ext.heimu.internal;
import org.commonmark.node.Node;
+import org.commonmark.node.Nodes;
+import org.commonmark.node.SourceSpans;
import org.commonmark.node.Text;
import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
@@ -27,28 +29,29 @@ public int getMinLength() {
return 2;
}
- @Override
- public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
- if (opener.length()>= 2 && closer.length()>= 2) {
- // Use exactly two delimiters even if we have more, and don't care about internal openers/closers.
- return 2;
- } else {
- return 0;
- }
- }
+ @Override
+ public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
+ if (openingRun.length()>= 2 && closingRun.length()>= 2) {
+ // Use exactly two delimiters even if we have more, and don't care about internal openers/closers.
+ Text opener = openingRun.getOpener();
- @Override
- public void process(Text opener, Text closer, int delimiterCount) {
- // Wrap nodes between delimiters in heimu.
- Node heimu = new Heimu();
-
- Node tmp = opener.getNext();
- while (tmp != null && tmp != closer) {
- Node next = tmp.getNext();
- heimu.appendChild(tmp);
- tmp = next;
- }
-
- opener.insertAfter(heimu);
- }
+ // Wrap nodes between delimiters in heimu.
+ Node heimu = new Heimu();
+
+ SourceSpans sourceSpans = new SourceSpans();
+ sourceSpans.addAllFrom(openingRun.getOpeners(2));
+
+ for (Node node : Nodes.between(opener, closingRun.getCloser())) {
+ heimu.appendChild(node);
+ sourceSpans.addAll(node.getSourceSpans());
+ }
+
+ sourceSpans.addAllFrom(closingRun.getClosers(2));
+ heimu.setSourceSpans(sourceSpans.getSourceSpans());
+
+ opener.insertAfter(heimu);
+ return 2;
+ }
+ return 0;
+ }
}
diff --git a/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuHtmlNodeRenderer.java b/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuHtmlNodeRenderer.java
index 521538e66..27609eec3 100644
--- a/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuHtmlNodeRenderer.java
+++ b/blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuHtmlNodeRenderer.java
@@ -23,7 +23,7 @@ public HeimuHtmlNodeRenderer(HtmlNodeRendererContext context) {
@Override
public void render(Node node) {
- Map attributes = new HashMap