├── blog-view ├── src │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ ├── state.js │ │ └── mutations-types.js │ ├── assets │ │ ├── img │ │ │ └── loading.gif │ │ └── css │ │ │ └── icon │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ ├── iconfont.woff2 │ │ │ └── iconfont.json │ ├── api │ │ ├── about.js │ │ ├── archives.js │ │ ├── home.js │ │ ├── login.js │ │ ├── tag.js │ │ ├── category.js │ │ ├── index.js │ │ ├── friend.js │ │ ├── moment.js │ │ ├── comment.js │ │ └── blog.js │ ├── util │ │ ├── lazyload.js │ │ ├── get-page-title.js │ │ ├── dateTimeFormatUtils.js │ │ └── directive.js │ ├── App.vue │ ├── settings.js │ ├── common │ │ └── reg.js │ ├── components │ │ ├── blog │ │ │ └── BlogList.vue │ │ ├── sidebar │ │ │ └── Tags.vue │ │ └── comment │ │ │ ├── Pagination.vue │ │ │ └── CommentList.vue │ ├── plugins │ │ └── axios.js │ └── views │ │ ├── tag │ │ └── Tag.vue │ │ ├── category │ │ └── Category.vue │ │ └── about │ │ └── About.vue ├── public │ ├── favicon.ico │ ├── img │ │ ├── qr.png │ │ ├── error.png │ │ ├── avatar.jpg │ │ ├── reward.jpg │ │ ├── header │ │ │ ├── wave1.png │ │ │ └── wave2.png │ │ ├── paper-plane.png │ │ └── comment-avatar │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ └── 6.jpg │ └── index.html ├── babel.config.js ├── .gitignore ├── README.md ├── package.json └── vue.config.js ├── pic ├── NBlog.png ├── TelegramBot.png ├── TgBotPhone1.png └── TgBotPhone2.png ├── blog-api ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.properties │ │ │ ├── ipdb │ │ │ │ ├── ip2region.db │ │ │ │ └── ip2region.xdb │ │ │ ├── mapper │ │ │ │ ├── AboutMapper.xml │ │ │ │ ├── UserMapper.xml │ │ │ │ ├── CityVisitorMapper.xml │ │ │ │ └── VisitRecordMapper.xml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── top │ │ │ └── naccl │ │ │ ├── service │ │ │ ├── CityVisitorService.java │ │ │ ├── VisitRecordService.java │ │ │ ├── UserService.java │ │ │ ├── AboutService.java │ │ │ ├── LoginLogService.java │ │ │ ├── ExceptionLogService.java │ │ │ ├── OperationLogService.java │ │ │ ├── SiteSettingService.java │ │ │ ├── TagService.java │ │ │ ├── CategoryService.java │ │ │ ├── DashboardService.java │ │ │ ├── VisitLogService.java │ │ │ ├── MomentService.java │ │ │ ├── VisitorService.java │ │ │ ├── ScheduleJobService.java │ │ │ ├── FriendService.java │ │ │ ├── impl │ │ │ │ ├── CityVisitorServiceImpl.java │ │ │ │ └── VisitRecordServiceImpl.java │ │ │ ├── CommentService.java │ │ │ └── RedisService.java │ │ │ ├── constant │ │ │ ├── JwtConstants.java │ │ │ ├── CommentConstants.java │ │ │ ├── PageConstants.java │ │ │ ├── UploadConstants.java │ │ │ ├── SiteSettingConstants.java │ │ │ └── RedisKeyConstants.java │ │ │ ├── BlogApiApplication.java │ │ │ ├── util │ │ │ ├── comment │ │ │ │ └── channel │ │ │ │ │ ├── CommentNotifyChannel.java │ │ │ │ │ └── ChannelFactory.java │ │ │ ├── upload │ │ │ │ └── channel │ │ │ │ │ ├── FileUploadChannel.java │ │ │ │ │ ├── ChannelFactory.java │ │ │ │ │ ├── UpyunChannel.java │ │ │ │ │ └── LocalChannel.java │ │ │ ├── markdown │ │ │ │ └── ext │ │ │ │ │ ├── cover │ │ │ │ │ ├── internal │ │ │ │ │ │ ├── AbstractCoverNodeRenderer.java │ │ │ │ │ │ ├── CoverTextContentNodeRenderer.java │ │ │ │ │ │ └── CoverHtmlNodeRenderer.java │ │ │ │ │ └── Cover.java │ │ │ │ │ └── heimu │ │ │ │ │ ├── internal │ │ │ │ │ ├── AbstractHeimuNodeRenderer.java │ │ │ │ │ ├── HeimuTextContentNodeRenderer.java │ │ │ │ │ └── HeimuHtmlNodeRenderer.java │ │ │ │ │ └── Heimu.java │ │ │ ├── common │ │ │ │ ├── ValidatorUtils.java │ │ │ │ └── SpringContextUtils.java │ │ │ ├── HashUtils.java │ │ │ ├── UserAgentUtils.java │ │ │ └── quartz │ │ │ │ └── ScheduleRunnable.java │ │ │ ├── enums │ │ │ ├── CommentOpenStateEnum.java │ │ │ ├── CommentPageEnum.java │ │ │ └── VisitBehavior.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── BlogView.java │ │ │ │ ├── LoginInfo.java │ │ │ │ ├── BlogPassword.java │ │ │ │ ├── UserAgentDTO.java │ │ │ │ ├── VisitLogUuidTime.java │ │ │ │ ├── Friend.java │ │ │ │ ├── VisitLogRemark.java │ │ │ │ ├── BlogVisibility.java │ │ │ │ ├── Comment.java │ │ │ │ ├── TgMessage.java │ │ │ │ └── Blog.java │ │ │ └── vo │ │ │ │ ├── Favorite.java │ │ │ │ ├── QqResultVO.java │ │ │ │ ├── Copyright.java │ │ │ │ ├── FriendInfo.java │ │ │ │ ├── BlogIdAndTitle.java │ │ │ │ ├── SearchBlog.java │ │ │ │ ├── TagBlogCount.java │ │ │ │ ├── CategoryBlogCount.java │ │ │ │ ├── NewBlog.java │ │ │ │ ├── QqVO.java │ │ │ │ ├── Friend.java │ │ │ │ ├── Badge.java │ │ │ │ ├── ArchiveBlog.java │ │ │ │ ├── PageResult.java │ │ │ │ ├── RandomBlog.java │ │ │ │ ├── Introduction.java │ │ │ │ ├── PageComment.java │ │ │ │ ├── BlogInfo.java │ │ │ │ ├── BlogDetail.java │ │ │ │ └── Result.java │ │ │ ├── entity │ │ │ ├── About.java │ │ │ ├── CityVisitor.java │ │ │ ├── Category.java │ │ │ ├── Moment.java │ │ │ ├── Tag.java │ │ │ ├── SiteSetting.java │ │ │ ├── VisitRecord.java │ │ │ ├── Friend.java │ │ │ ├── ScheduleJobLog.java │ │ │ ├── ScheduleJob.java │ │ │ ├── Visitor.java │ │ │ ├── LoginLog.java │ │ │ ├── ExceptionLog.java │ │ │ ├── Comment.java │ │ │ ├── Blog.java │ │ │ ├── OperationLog.java │ │ │ └── VisitLog.java │ │ │ ├── exception │ │ │ ├── NotFoundException.java │ │ │ ├── BadRequestException.java │ │ │ └── PersistenceException.java │ │ │ ├── mapper │ │ │ ├── UserMapper.java │ │ │ ├── CityVisitorMapper.java │ │ │ ├── AboutMapper.java │ │ │ ├── VisitRecordMapper.java │ │ │ ├── LoginLogMapper.java │ │ │ ├── ExceptionLogMapper.java │ │ │ ├── OperationLogMapper.java │ │ │ ├── ScheduleJobLogMapper.java │ │ │ ├── ScheduleJobMapper.java │ │ │ ├── MomentMapper.java │ │ │ ├── CategoryMapper.java │ │ │ ├── VisitLogMapper.java │ │ │ ├── FriendMapper.java │ │ │ ├── VisitorMapper.java │ │ │ ├── TagMapper.java │ │ │ ├── SiteSettingMapper.java │ │ │ └── CommentMapper.java │ │ │ ├── annotation │ │ │ ├── OperationLogger.java │ │ │ ├── VisitLogger.java │ │ │ └── AccessLimit.java │ │ │ ├── config │ │ │ ├── properties │ │ │ │ ├── UploadProperties.java │ │ │ │ ├── BlogProperties.java │ │ │ │ ├── GithubProperties.java │ │ │ │ ├── UpyunProperties.java │ │ │ │ ├── ProxyProperties.java │ │ │ │ └── TelegramProperties.java │ │ │ ├── RedisSerializeConfig.java │ │ │ ├── MyAuthenticationEntryPoint.java │ │ │ └── RestTemplateConfig.java │ │ │ ├── controller │ │ │ ├── AboutController.java │ │ │ ├── ArchiveController.java │ │ │ ├── admin │ │ │ │ ├── AccountAdminController.java │ │ │ │ └── AboutAdminController.java │ │ │ ├── TagController.java │ │ │ ├── CategoryController.java │ │ │ └── LoginController.java │ │ │ └── task │ │ │ └── RedisSyncScheduleTask.java │ └── test │ │ └── java │ │ └── top │ │ └── naccl │ │ └── BlogApiApplicationTests.java ├── .gitignore └── cfworker-tg-api-open.js ├── blog-cms ├── public │ ├── favicon.ico │ ├── img │ │ ├── avatar.jpg │ │ ├── visitor.jpg │ │ └── comment-avatar │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ └── 6.jpg │ └── index.html ├── babel.config.js ├── src │ ├── assets │ │ ├── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ └── styles │ │ │ ├── mixin.scss │ │ │ ├── variables.scss │ │ │ ├── element-ui.scss │ │ │ ├── transition.scss │ │ │ └── index.scss │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── settings.js │ │ │ └── app.js │ ├── api │ │ ├── dashboard.js │ │ ├── login.js │ │ ├── account.js │ │ ├── about.js │ │ ├── loginLog.js │ │ ├── visitLog.js │ │ ├── visitor.js │ │ ├── ExceptionLog.js │ │ ├── operationLog.js │ │ ├── siteSetting.js │ │ ├── tag.js │ │ ├── category.js │ │ ├── comment.js │ │ ├── moment.js │ │ ├── schedule.js │ │ ├── friend.js │ │ └── blog.js │ ├── layout │ │ ├── components │ │ │ ├── index.js │ │ │ ├── Sidebar │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Link.vue │ │ │ │ └── Item.vue │ │ │ └── AppMain.vue │ │ └── mixin │ │ │ └── ResizeHandler.js │ ├── App.vue │ ├── util │ │ ├── dateTimeFormatUtils.js │ │ ├── uuid.js │ │ ├── get-page-title.js │ │ ├── copy.js │ │ ├── validate.js │ │ ├── task-queue.js │ │ ├── directive.js │ │ └── request.js │ ├── icons │ │ ├── index.js │ │ ├── svgo.yml │ │ └── svg │ │ │ ├── user.svg │ │ │ ├── logout.svg │ │ │ ├── password.svg │ │ │ ├── eye.svg │ │ │ ├── markdown.svg │ │ │ ├── pinglun-blue.svg │ │ │ ├── eye-open.svg │ │ │ ├── article.svg │ │ │ └── friend.svg │ ├── settings.js │ ├── main.js │ ├── components │ │ └── Hamburger │ │ │ └── index.vue │ └── views │ │ └── system │ │ └── Account.vue ├── .gitignore ├── README.md └── package.json ├── .gitignore └── LICENSE /blog-view/src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } -------------------------------------------------------------------------------- /pic/NBlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/pic/NBlog.png -------------------------------------------------------------------------------- /blog-api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev -------------------------------------------------------------------------------- /pic/TelegramBot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/pic/TelegramBot.png -------------------------------------------------------------------------------- /pic/TgBotPhone1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/pic/TgBotPhone1.png -------------------------------------------------------------------------------- /pic/TgBotPhone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/pic/TgBotPhone2.png -------------------------------------------------------------------------------- /blog-cms/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/favicon.ico -------------------------------------------------------------------------------- /blog-view/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/favicon.ico -------------------------------------------------------------------------------- /blog-view/public/img/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/qr.png -------------------------------------------------------------------------------- /blog-cms/public/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/avatar.jpg -------------------------------------------------------------------------------- /blog-view/public/img/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/error.png -------------------------------------------------------------------------------- /blog-cms/public/img/visitor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/visitor.jpg -------------------------------------------------------------------------------- /blog-view/public/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/avatar.jpg -------------------------------------------------------------------------------- /blog-view/public/img/reward.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/reward.jpg -------------------------------------------------------------------------------- /blog-cms/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /blog-cms/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/src/assets/404_images/404.png -------------------------------------------------------------------------------- /blog-view/public/img/header/wave1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/header/wave1.png -------------------------------------------------------------------------------- /blog-view/public/img/header/wave2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/header/wave2.png -------------------------------------------------------------------------------- /blog-view/public/img/paper-plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/paper-plane.png -------------------------------------------------------------------------------- /blog-view/src/assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/src/assets/img/loading.gif -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/1.jpg -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/2.jpg -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/3.jpg -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/4.jpg -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/5.jpg -------------------------------------------------------------------------------- /blog-cms/public/img/comment-avatar/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/public/img/comment-avatar/6.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/1.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/2.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/3.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/4.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/5.jpg -------------------------------------------------------------------------------- /blog-view/public/img/comment-avatar/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/public/img/comment-avatar/6.jpg -------------------------------------------------------------------------------- /blog-view/src/assets/css/icon/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/src/assets/css/icon/iconfont.eot -------------------------------------------------------------------------------- /blog-view/src/assets/css/icon/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/src/assets/css/icon/iconfont.ttf -------------------------------------------------------------------------------- /blog-view/src/assets/css/icon/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/src/assets/css/icon/iconfont.woff -------------------------------------------------------------------------------- /blog-api/src/main/resources/ipdb/ip2region.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-api/src/main/resources/ipdb/ip2region.db -------------------------------------------------------------------------------- /blog-cms/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-cms/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /blog-view/src/assets/css/icon/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-view/src/assets/css/icon/iconfont.woff2 -------------------------------------------------------------------------------- /blog-api/src/main/resources/ipdb/ip2region.xdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naccl/NBlog/HEAD/blog-api/src/main/resources/ipdb/ip2region.xdb -------------------------------------------------------------------------------- /blog-cms/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | } 5 | export default getters 6 | -------------------------------------------------------------------------------- /blog-view/src/api/about.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getAbout() { 4 | return axios({ 5 | url: 'about', 6 | method: 'GET' 7 | }) 8 | } -------------------------------------------------------------------------------- /blog-cms/src/api/dashboard.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getDashboard() { 4 | return axios({ 5 | url: 'dashboard', 6 | method: 'GET' 7 | }) 8 | } -------------------------------------------------------------------------------- /blog-view/src/api/archives.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getArchives() { 4 | return axios({ 5 | url: 'archives', 6 | method: 'GET' 7 | }) 8 | } -------------------------------------------------------------------------------- /blog-view/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ [ '@vue/app', { useBuiltIns: 'entry' } ] ] 3 | // presets: [ 4 | // '@vue/cli-plugin-babel/preset' 5 | // ] 6 | } 7 | -------------------------------------------------------------------------------- /blog-cms/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /blog-cms/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /blog-cms/src/util/dateTimeFormatUtils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | 4 | Vue.filter('dateFormat', function (value, format = 'YYYY-MM-DD HH:mm:ss') { 5 | return moment(value).format(format) 6 | }) -------------------------------------------------------------------------------- /blog-view/src/api/home.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getBlogList(pageNum) { 4 | return axios({ 5 | url: 'blogs', 6 | method: 'GET', 7 | params: { 8 | pageNum 9 | } 10 | }) 11 | } -------------------------------------------------------------------------------- /blog-view/src/util/lazyload.js: -------------------------------------------------------------------------------- 1 | export const letsLazyload = (content) => { 2 | return content.replace(/(]*)(src=")([^"]*")([^>]*)(\>)/g, (match, p1, p2, p3, p4, p5, p6) => p1 + p2 + 'data-src="' + p4 + p5 + p6) 3 | } 4 | -------------------------------------------------------------------------------- /blog-cms/src/api/login.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function login(loginForm) { 4 | return axios({ 5 | url: 'login', 6 | method: 'POST', 7 | data: { 8 | ...loginForm 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /blog-view/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /blog-view/src/api/login.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function login(loginForm) { 4 | return axios({ 5 | url: 'login', 6 | method: 'POST', 7 | data: { 8 | ...loginForm 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/CityVisitorService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.CityVisitor; 4 | 5 | public interface CityVisitorService { 6 | void saveCityVisitor(CityVisitor cityVisitor); 7 | } 8 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/VisitRecordService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.VisitRecord; 4 | 5 | public interface VisitRecordService { 6 | void saveVisitRecord(VisitRecord visitRecord); 7 | } 8 | -------------------------------------------------------------------------------- /blog-cms/src/api/account.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function changeAccount(account) { 4 | return axios({ 5 | url: 'account', 6 | method: 'POST', 7 | data: { 8 | ...account 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /blog-cms/src/util/uuid.js: -------------------------------------------------------------------------------- 1 | export function randomUUID() { 2 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 3 | let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8) 4 | return v.toString(16) 5 | }) 6 | } -------------------------------------------------------------------------------- /blog-view/src/api/tag.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getBlogListByTagName(tagName, pageNum) { 4 | return axios({ 5 | url: 'tag', 6 | method: 'GET', 7 | params: { 8 | tagName, 9 | pageNum 10 | } 11 | }) 12 | } -------------------------------------------------------------------------------- /blog-cms/src/util/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /blog-view/src/api/category.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getBlogListByCategoryName(categoryName, pageNum) { 4 | return axios({ 5 | url: 'category', 6 | method: 'GET', 7 | params: { 8 | categoryName, 9 | pageNum 10 | } 11 | }) 12 | } -------------------------------------------------------------------------------- /blog-view/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getHitokoto() { 4 | return axios({ 5 | url: 'https://v1.hitokoto.cn/?c=a', 6 | method: 'GET' 7 | }) 8 | } 9 | 10 | export function getSite() { 11 | return axios({ 12 | url: 'site', 13 | method: 'GET' 14 | }) 15 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/JwtConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * JWT常量 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-23 8 | */ 9 | public class JwtConstants { 10 | /** 11 | * 博主token前缀 12 | */ 13 | public static final String ADMIN_PREFIX = "admin:"; 14 | } 15 | -------------------------------------------------------------------------------- /blog-cms/src/util/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通过生成DOM节点来复制内容至剪贴板 3 | * @param {string} 需要复制的内容 4 | */ 5 | export function copy(copyCont) { 6 | let oInput = document.createElement('input') 7 | oInput.value = copyCont 8 | document.body.appendChild(oInput) 9 | oInput.select() 10 | document.execCommand('Copy') 11 | oInput.remove() 12 | } -------------------------------------------------------------------------------- /blog-view/src/util/get-page-title.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | export default function getPageTitle(pageTitle) { 4 | const title = store.state.siteInfo.webTitleSuffix 5 | 6 | if (pageTitle) { 7 | if (title) { 8 | return `${pageTitle}${title}` 9 | } 10 | return pageTitle 11 | } 12 | return title 13 | } 14 | -------------------------------------------------------------------------------- /blog-cms/src/api/about.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getAbout() { 4 | return axios({ 5 | url: 'about', 6 | method: 'GET' 7 | }) 8 | } 9 | 10 | export function updateAbout(form) { 11 | return axios({ 12 | url: 'about', 13 | method: 'PUT', 14 | data: { 15 | ...form 16 | } 17 | }) 18 | } -------------------------------------------------------------------------------- /blog-cms/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import app from './modules/app' 5 | import settings from './modules/settings' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | app, 12 | settings, 13 | }, 14 | getters 15 | }) 16 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/UserService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.User; 4 | 5 | public interface UserService { 6 | User findUserByUsernameAndPassword(String username, String password); 7 | 8 | User findUserById(Long id); 9 | 10 | boolean changeAccount(User user, String jwt); 11 | } 12 | -------------------------------------------------------------------------------- /blog-cms/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /blog-cms/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /blog-view/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/AboutService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import java.util.Map; 4 | 5 | public interface AboutService { 6 | Map getAboutInfo(); 7 | 8 | Map getAboutSetting(); 9 | 10 | void updateAbout(Map map); 11 | 12 | boolean getAboutCommentEnabled(); 13 | } 14 | -------------------------------------------------------------------------------- /blog-view/src/api/friend.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getData() { 4 | return axios({ 5 | url: 'friends', 6 | method: 'GET' 7 | }) 8 | } 9 | 10 | export function addViewsByNickname(nickname) { 11 | return axios({ 12 | url: 'friend', 13 | method: 'POST', 14 | params: { 15 | nickname 16 | } 17 | }) 18 | } -------------------------------------------------------------------------------- /blog-view/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import state from './state' 4 | import actions from './actions' 5 | import mutations from './mutations' 6 | import getters from './getters' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | state, 12 | actions, 13 | mutations, 14 | getters 15 | }) 16 | -------------------------------------------------------------------------------- /blog-api/src/test/java/top/naccl/BlogApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package top.naccl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BlogApiApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | @Test 14 | void test() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /blog-cms/README.md: -------------------------------------------------------------------------------- 1 | # blog-cms 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /blog-view/README.md: -------------------------------------------------------------------------------- 1 | # blog-view 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /blog-view/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * @type {string} 4 | * @description 首页三张背景图 5 | */ 6 | bg1: 'https://cdn.naccl.top/blog/img/bg1.jpg', 7 | bg2: 'https://cdn.naccl.top/blog/img/bg2.jpg', 8 | bg3: 'https://cdn.naccl.top/blog/img/bg3.jpg', 9 | 10 | /** 11 | * @type {string} 12 | * @description 首页故障风文字 13 | */ 14 | malfunctionText: 'Naccl\'s Blog' 15 | } 16 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/BlogApiApplication.java: -------------------------------------------------------------------------------- 1 | package top.naccl; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class BlogApiApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(BlogApiApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/CommentConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * 评论相关常量 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-22 8 | */ 9 | public class CommentConstants { 10 | /** 11 | * 评论提醒方式-Telegram 12 | */ 13 | public static final String TELEGRAM = "tg"; 14 | 15 | /** 16 | * 评论提醒方式-邮件 17 | */ 18 | public static final String MAIL = "mail"; 19 | } 20 | -------------------------------------------------------------------------------- /blog-cms/src/api/loginLog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getLoginLogList(queryInfo) { 4 | return axios({ 5 | url: 'loginLogs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteLoginLogById(id) { 14 | return axios({ 15 | url: 'loginLog', 16 | method: 'DELETE', 17 | params: { 18 | id 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /blog-cms/src/api/visitLog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getVisitLogList(queryInfo) { 4 | return axios({ 5 | url: 'visitLogs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteVisitLogById(id) { 14 | return axios({ 15 | url: 'visitLog', 16 | method: 'DELETE', 17 | params: { 18 | id 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /blog-cms/src/api/visitor.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getVisitorList(queryInfo) { 4 | return axios({ 5 | url: 'visitors', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteVisitor(id, uuid) { 14 | return axios({ 15 | url: 'visitor', 16 | method: 'DELETE', 17 | params: { 18 | id, 19 | uuid 20 | } 21 | }) 22 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/comment/channel/CommentNotifyChannel.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.comment.channel; 2 | 3 | import top.naccl.model.dto.Comment; 4 | 5 | /** 6 | * 评论提醒方式 7 | * 8 | * @author: Naccl 9 | * @date: 2022-01-22 10 | */ 11 | public interface CommentNotifyChannel { 12 | /** 13 | * 通过指定方式通知自己 14 | * 15 | * @param comment 当前收到的评论 16 | */ 17 | void notifyMyself(Comment comment); 18 | } 19 | -------------------------------------------------------------------------------- /blog-cms/src/api/ExceptionLog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getExceptionLogList(queryInfo) { 4 | return axios({ 5 | url: 'exceptionLogs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteExceptionLogById(id) { 14 | return axios({ 15 | url: 'exceptionLog', 16 | method: 'DELETE', 17 | params: { 18 | id 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /blog-cms/src/api/operationLog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getOperationLogList(queryInfo) { 4 | return axios({ 5 | url: 'operationLogs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteOperationLogById(id) { 14 | return axios({ 15 | url: 'operationLog', 16 | method: 'DELETE', 17 | params: { 18 | id 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/enums/CommentOpenStateEnum.java: -------------------------------------------------------------------------------- 1 | package top.naccl.enums; 2 | 3 | /** 4 | * 评论开放状态枚举类 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-23 8 | */ 9 | public enum CommentOpenStateEnum { 10 | /** 11 | * 博客不存在,或博客未公开 12 | */ 13 | NOT_FOUND, 14 | /** 15 | * 评论正常开放 16 | */ 17 | OPEN, 18 | /** 19 | * 评论已关闭 20 | */ 21 | CLOSE, 22 | /** 23 | * 评论所在页面需要密码 24 | */ 25 | PASSWORD, 26 | } 27 | -------------------------------------------------------------------------------- /blog-view/src/api/moment.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getMomentListByPageNum(token, pageNum) { 4 | return axios({ 5 | url: 'moments', 6 | method: 'GET', 7 | headers: { 8 | Authorization: token, 9 | }, 10 | params: { 11 | pageNum 12 | } 13 | }) 14 | } 15 | 16 | export function likeMoment(id) { 17 | return axios({ 18 | url: `moment/like/${id}`, 19 | method: 'POST', 20 | }) 21 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/PageConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * 页面相关常量 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-23 8 | */ 9 | public class PageConstants { 10 | /** 11 | * 普通博客文章页面 12 | */ 13 | public static final int BLOG = 0; 14 | /** 15 | * 关于我页面 16 | */ 17 | public static final int ABOUT = 1; 18 | /** 19 | * 友链页面 20 | */ 21 | public static final int FRIEND = 2; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/BlogView.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 博客浏览量 10 | * @Author: Naccl 11 | * @Date: 2020-10-06 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlogView { 18 | private Long id; 19 | private Integer views; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/LoginLogService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import top.naccl.entity.LoginLog; 5 | 6 | import java.util.List; 7 | 8 | public interface LoginLogService { 9 | List getLoginLogListByDate(String startDate, String endDate); 10 | 11 | @Async 12 | void saveLoginLog(LoginLog log); 13 | 14 | void deleteLoginLogById(Long id); 15 | } 16 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Favorite.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 自定义爱好 10 | * @Author: Naccl 11 | * @Date: 2020-08-09 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Favorite { 18 | private String title; 19 | private String content; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/QqResultVO.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author raxcl 9 | * @date 2024-01-19 9:54:53 10 | */ 11 | @Data 12 | public class QqResultVO { 13 | private String success; 14 | 15 | private String msg; 16 | 17 | private Map data; 18 | 19 | private String time; 20 | 21 | private String api_vers; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Copyright.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: copyright 10 | * @Author: Naccl 11 | * @Date: 2020-08-09 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Copyright { 18 | private String title; 19 | private String siteName; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/LoginInfo.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 登录账号密码 10 | * @Author: Naccl 11 | * @Date: 2020-09-02 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class LoginInfo { 18 | private String username; 19 | private String password; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/BlogPassword.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 受保护文章密码DTO 10 | * @Author: Naccl 11 | * @Date: 2020-09-05 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlogPassword { 18 | private Long blogId; 19 | private String password; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/FriendInfo.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 友链页面信息 10 | * @Author: Naccl 11 | * @Date: 2020-09-09 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class FriendInfo { 18 | private String content; 19 | private Boolean commentEnabled; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/BlogIdAndTitle.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 评论管理页面按博客title查询评论 10 | * @Author: Naccl 11 | * @Date: 2020-08-03 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlogIdAndTitle { 18 | private Long id; 19 | private String title; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/ExceptionLogService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import top.naccl.entity.ExceptionLog; 5 | 6 | import java.util.List; 7 | 8 | public interface ExceptionLogService { 9 | List getExceptionLogListByDate(String startDate, String endDate); 10 | 11 | @Async 12 | void saveExceptionLog(ExceptionLog log); 13 | 14 | void deleteExceptionLogById(Long id); 15 | } 16 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/OperationLogService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import top.naccl.entity.OperationLog; 5 | 6 | import java.util.List; 7 | 8 | public interface OperationLogService { 9 | List getOperationLogListByDate(String startDate, String endDate); 10 | 11 | @Async 12 | void saveOperationLog(OperationLog log); 13 | 14 | void deleteOperationLogById(Long id); 15 | } 16 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/SearchBlog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 关键字搜索博客 10 | * @Author: Naccl 11 | * @Date: 2020-09-06 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class SearchBlog { 18 | private Long id; 19 | private String title; 20 | private String content; 21 | } 22 | -------------------------------------------------------------------------------- /blog-cms/src/util/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} path 3 | * @returns {Boolean} 4 | */ 5 | export function isExternal(path) { 6 | return /^(https?:|mailto:|tel:)/.test(path) 7 | } 8 | 9 | /** 10 | * https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img 11 | * @param {string} fileName 12 | * @returns {Boolean} 13 | */ 14 | export function isImgExt(fileName) { 15 | return /\.(apng|avif|bmp|gif|ico|cur|jpg|jpeg|jfif|pjpeg|pjp|png|svg|tif|tiff|webp)$/i.test(fileName) 16 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/About.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 关于我 10 | * @Author: Naccl 11 | * @Date: 2020-08-31 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class About { 18 | private Long id; 19 | private String nameEn; 20 | private String nameZh; 21 | private String value; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package top.naccl.exception; 2 | 3 | /** 4 | * @Description: 404异常 5 | * @Author: Naccl 6 | * @Date: 2020-08-14 7 | */ 8 | 9 | public class NotFoundException extends RuntimeException { 10 | public NotFoundException() { 11 | } 12 | 13 | public NotFoundException(String message) { 14 | super(message); 15 | } 16 | 17 | public NotFoundException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/TagBlogCount.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 标签和博客数量 10 | * @Author: Naccl 11 | * @Date: 2020-10-08 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class TagBlogCount { 18 | private Long id; 19 | private String name;//标签名 20 | private Integer value;//标签下博客数量 21 | } 22 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/upload/channel/FileUploadChannel.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.upload.channel; 2 | 3 | import top.naccl.util.upload.UploadUtils; 4 | 5 | /** 6 | * 文件上传方式 7 | * 8 | * @author: Naccl 9 | * @date: 2022-01-23 10 | */ 11 | public interface FileUploadChannel { 12 | /** 13 | * 通过指定方式上传文件 14 | * 15 | * @param image 需要保存的图片 16 | * @return 访问图片的URL 17 | * @throws Exception 18 | */ 19 | String upload(UploadUtils.ImageResource image) throws Exception; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package top.naccl.exception; 2 | 3 | /** 4 | * @Description: 非法请求异常 5 | * @Author: Naccl 6 | * @Date: 2020-07-23 7 | */ 8 | 9 | public class BadRequestException extends RuntimeException { 10 | public BadRequestException() { 11 | } 12 | 13 | public BadRequestException(String message) { 14 | super(message); 15 | } 16 | 17 | public BadRequestException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/exception/PersistenceException.java: -------------------------------------------------------------------------------- 1 | package top.naccl.exception; 2 | 3 | /** 4 | * @Description: 持久化异常 5 | * @Author: Naccl 6 | * @Date: 2020-08-14 7 | */ 8 | 9 | public class PersistenceException extends RuntimeException { 10 | public PersistenceException() { 11 | } 12 | 13 | public PersistenceException(String message) { 14 | super(message); 15 | } 16 | 17 | public PersistenceException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/CategoryBlogCount.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 分类和博客数量 10 | * @Author: Naccl 11 | * @Date: 2020-10-08 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class CategoryBlogCount { 18 | private Long id; 19 | private String name;//分类名 20 | private Integer value;//分类下博客数量 21 | } 22 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/NewBlog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 最新推荐博客 10 | * @Author: Naccl 11 | * @Date: 2020-09-05 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class NewBlog { 18 | private Long id; 19 | private String title; 20 | private String password; 21 | private Boolean privacy; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/SiteSettingService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.SiteSetting; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface SiteSettingService { 10 | Map> getList(); 11 | 12 | Map getSiteInfo(); 13 | 14 | String getWebTitleSuffix(); 15 | 16 | void updateSiteSetting(List siteSettings, List deleteIds); 17 | } 18 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/TagService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.Tag; 4 | 5 | import java.util.List; 6 | 7 | public interface TagService { 8 | List getTagList(); 9 | 10 | List getTagListNotId(); 11 | 12 | List getTagListByBlogId(Long blogId); 13 | 14 | void saveTag(Tag tag); 15 | 16 | Tag getTagById(Long id); 17 | 18 | Tag getTagByName(String name); 19 | 20 | void deleteTagById(Long id); 21 | 22 | void updateTag(Tag tag); 23 | } 24 | -------------------------------------------------------------------------------- /blog-view/src/util/dateTimeFormatUtils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | 4 | //设置moment国际化语言 5 | moment.locale('zh-cn') 6 | 7 | Vue.filter('dateFormat', function (value, format = 'YYYY-MM-DD HH:mm:ss') { 8 | return moment(value).format(format) 9 | }) 10 | 11 | Vue.filter('dateFromNow', function (value) { 12 | //相对时间大于一个月,显示详细时间 13 | if (moment().diff(moment(value)) > 2592000000) { 14 | return moment(value).format('YYYY-MM-DD HH:mm') 15 | } 16 | return moment(value).fromNow() 17 | }) -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/QqVO.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author raxcl 7 | * @date 2024-01-19 9:54:53 8 | */ 9 | @Data 10 | public class QqVO { 11 | /** 12 | * qq号 13 | */ 14 | private Long qq; 15 | 16 | /** 17 | * qq昵称 18 | */ 19 | private String name; 20 | 21 | /** 22 | * qq邮箱 23 | */ 24 | private String email; 25 | 26 | /** 27 | * qq头像 28 | */ 29 | private String avatar; 30 | } 31 | -------------------------------------------------------------------------------- /blog-cms/src/api/siteSetting.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getSiteSettingData() { 4 | return axios({ 5 | url: 'siteSettings', 6 | method: 'GET' 7 | }) 8 | } 9 | 10 | export function update(settings, deleteIds) { 11 | return axios({ 12 | url: 'siteSettings', 13 | method: 'POST', 14 | data: { 15 | settings, 16 | deleteIds 17 | } 18 | }) 19 | } 20 | 21 | export function getWebTitleSuffix() { 22 | return axios({ 23 | url: 'webTitleSuffix', 24 | method: 'GET' 25 | }) 26 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/CityVisitor.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * @Description: 城市访客数量 11 | * @Author: Naccl 12 | * @Date: 2021-02-26 13 | */ 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class CityVisitor { 20 | private String city;//城市名称 21 | private Integer uv;//独立访客数量 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Friend.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 友链VO 10 | * @Author: Naccl 11 | * @Date: 2020-09-08 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Friend { 18 | private String nickname;//昵称 19 | private String description;//描述 20 | private String website;//站点 21 | private String avatar;//头像 22 | } 23 | -------------------------------------------------------------------------------- /blog-view/src/api/comment.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getCommentListByQuery(token, query) { 4 | return axios({ 5 | url: 'comments', 6 | method: 'GET', 7 | headers: { 8 | Authorization: token, 9 | }, 10 | params: { 11 | ...query 12 | } 13 | }) 14 | } 15 | 16 | export function submitComment(token, form) { 17 | return axios({ 18 | url: 'comment', 19 | method: 'POST', 20 | headers: { 21 | Authorization: token, 22 | }, 23 | data: { 24 | ...form 25 | } 26 | }) 27 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.User; 6 | 7 | /** 8 | * @Description: 用户持久层接口 9 | * @Author: Naccl 10 | * @Date: 2020-07-19 11 | */ 12 | @Mapper 13 | @Repository 14 | public interface UserMapper { 15 | User findByUsername(String username); 16 | 17 | User findById(Long id); 18 | 19 | int updateUserByUsername(String username, User user); 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/UserAgentDTO.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * @Description: UserAgent解析DTO 11 | * @Author: Naccl 12 | * @Date: 2022-10-13 13 | */ 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class UserAgentDTO { 20 | private String os; 21 | private String browser; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Badge.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: GitHub徽标 10 | * @Author: Naccl 11 | * @Date: 2020-08-09 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Badge { 18 | private String title; 19 | private String url; 20 | private String subject; 21 | private String value; 22 | private String color; 23 | } 24 | -------------------------------------------------------------------------------- /blog-cms/src/assets/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/ArchiveBlog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 归档页面博客简要信息 10 | * @Author: Naccl 11 | * @Date: 2020-08-12 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class ArchiveBlog { 18 | private Long id; 19 | private String title; 20 | private String day; 21 | private String password; 22 | private Boolean privacy; 23 | } 24 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.Category; 4 | 5 | import java.util.List; 6 | 7 | public interface CategoryService { 8 | List getCategoryList(); 9 | 10 | List getCategoryNameList(); 11 | 12 | void saveCategory(Category category); 13 | 14 | Category getCategoryById(Long id); 15 | 16 | Category getCategoryByName(String name); 17 | 18 | void deleteCategoryById(Long id); 19 | 20 | void updateCategory(Category category); 21 | } 22 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/CityVisitorMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.CityVisitor; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 城市访客数量统计持久层接口 11 | * @Author: Naccl 12 | * @Date: 2021-02-26 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface CityVisitorMapper { 17 | List getCityVisitorList(); 18 | 19 | int saveCityVisitor(CityVisitor cityVisitor); 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/DashboardService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.CityVisitor; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public interface DashboardService { 9 | int countVisitLogByToday(); 10 | 11 | int getBlogCount(); 12 | 13 | int getCommentCount(); 14 | 15 | Map getCategoryBlogCountMap(); 16 | 17 | Map getTagBlogCountMap(); 18 | 19 | Map getVisitRecordMap(); 20 | 21 | List getCityVisitorList(); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/AboutMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.About; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 关于我持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-08-31 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface AboutMapper { 17 | List getList(); 18 | 19 | int updateAbout(String nameEn, String value); 20 | 21 | String getAboutCommentEnabled(); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/VisitRecordMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.VisitRecord; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 访问记录持久层接口 11 | * @Author: Naccl 12 | * @Date: 2021-02-23 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface VisitRecordMapper { 17 | List getVisitRecordListByLimit(Integer limit); 18 | 19 | int saveVisitRecord(VisitRecord visitRecord); 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/annotation/OperationLogger.java: -------------------------------------------------------------------------------- 1 | package top.naccl.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Description: 用于需要记录操作日志的方法 10 | * @Author: Naccl 11 | * @Date: 2020-11-29 12 | */ 13 | @Target(ElementType.METHOD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface OperationLogger { 16 | /** 17 | * 操作描述 18 | */ 19 | String value() default ""; 20 | } 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Category.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Description: 博客分类 13 | * @Author: Naccl 14 | * @Date: 2020-07-26 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | public class Category { 21 | private Long id; 22 | private String name;//分类名称 23 | private List blogs = new ArrayList<>();//该分类下的博客文章 24 | } 25 | -------------------------------------------------------------------------------- /blog-view/src/assets/css/icon/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1993827", 3 | "name": "blog-view", 4 | "font_family": "ali-iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "1139074", 10 | "name": "箭头下", 11 | "font_class": "down", 12 | "unicode": "e624", 13 | "unicode_decimal": 58916 14 | }, 15 | { 16 | "icon_id": "3176645", 17 | "name": "GitHub", 18 | "font_class": "github", 19 | "unicode": "ea0a", 20 | "unicode_decimal": 59914 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Moment.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 博客动态 12 | * @Author: Naccl 13 | * @Date: 2020-08-24 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class Moment { 20 | private Long id; 21 | private String content;//动态内容 22 | private Date createTime;//创建时间 23 | private Integer likes;//点赞数量 24 | private Boolean published;//是否公开 25 | } 26 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/VisitLogService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import top.naccl.entity.VisitLog; 5 | import top.naccl.model.dto.VisitLogUuidTime; 6 | 7 | import java.util.List; 8 | 9 | public interface VisitLogService { 10 | List getVisitLogListByUUIDAndDate(String uuid, String startDate, String endDate); 11 | 12 | List getUUIDAndCreateTimeByYesterday(); 13 | 14 | @Async 15 | void saveVisitLog(VisitLog log); 16 | 17 | void deleteVisitLogById(Long id); 18 | } 19 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/VisitLogUuidTime.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | import java.util.Date; 10 | 11 | /** 12 | * @Description: 访客更新DTO 13 | * @Author: Naccl 14 | * @Date: 2021-02-05 15 | */ 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Getter 19 | @Setter 20 | @ToString 21 | public class VisitLogUuidTime { 22 | private String uuid; 23 | private Date time; 24 | private Integer pv; 25 | } 26 | -------------------------------------------------------------------------------- /blog-cms/src/util/task-queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 任务队列,按时间间隔执行函数 3 | * 例: taskQueue(()=>{console.log(123)},1000) 4 | */ 5 | let queue = [] 6 | let timer = null 7 | 8 | function process() { 9 | if (queue.length === 0) { 10 | clearInterval(timer) 11 | timer = null 12 | return 13 | } 14 | let fn = queue.shift() 15 | fn() 16 | if (queue.length === 0) { 17 | clearInterval(timer) 18 | timer = null 19 | } 20 | } 21 | 22 | export function taskQueue(fn, timeout/*仅第一个任务的timeout有效*/) { 23 | queue.push(fn) 24 | if (!timer) { 25 | process() 26 | timer = setInterval(process, timeout) 27 | } 28 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/UploadConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * 上传文件相关常量 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-23 8 | */ 9 | public class UploadConstants { 10 | /** 11 | * 评论中QQ头像存储方式-本地 12 | */ 13 | public static final String LOCAL = "local"; 14 | /** 15 | * 评论中QQ头像存储方式-GitHub 16 | */ 17 | public static final String GITHUB = "github"; 18 | /** 19 | * 评论中QQ头像存储方式-又拍云 20 | */ 21 | public static final String UPYUN = "upyun"; 22 | /** 23 | * 图片ContentType 24 | */ 25 | public static final String IMAGE = "image"; 26 | } 27 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/LoginLogMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.LoginLog; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 登录日志持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-12-03 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface LoginLogMapper { 17 | List getLoginLogListByDate(String startDate, String endDate); 18 | 19 | int saveLoginLog(LoginLog log); 20 | 21 | int deleteLoginLogById(Long id); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/Friend.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 友链DTO 10 | * @Author: Naccl 11 | * @Date: 2020-09-08 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Friend { 18 | private Long id; 19 | private String nickname;//昵称 20 | private String description;//描述 21 | private String website;//站点 22 | private String avatar;//头像 23 | private Boolean published;//公开或隐藏 24 | } 25 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/VisitLogRemark.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * 访问日志备注 11 | * 12 | * @author: Naccl 13 | * @date: 2022-01-08 14 | */ 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | public class VisitLogRemark { 21 | /** 22 | * 访问内容 23 | */ 24 | private String content; 25 | 26 | /** 27 | * 备注 28 | */ 29 | private String remark; 30 | } 31 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/AbstractCoverNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.cover.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.NodeRenderer; 5 | import top.naccl.util.markdown.ext.cover.Cover; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | abstract class AbstractCoverNodeRenderer implements NodeRenderer { 11 | @Override 12 | public Set> getNodeTypes() { 13 | return Collections.>singleton(Cover.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/AbstractHeimuNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.heimu.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.NodeRenderer; 5 | import top.naccl.util.markdown.ext.heimu.Heimu; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | abstract class AbstractHeimuNodeRenderer implements NodeRenderer { 11 | @Override 12 | public Set> getNodeTypes() { 13 | return Collections.>singleton(Heimu.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | .DS_Store 36 | /blog-cms-tmp 37 | package-lock.json 38 | -------------------------------------------------------------------------------- /blog-view/src/common/reg.js: -------------------------------------------------------------------------------- 1 | export const checkEmail = (rule, value, callback) => { 2 | const reg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 3 | if (reg.test(value)) { 4 | return callback() 5 | } 6 | callback(new Error('请输入合法的邮箱')) 7 | } 8 | 9 | export const checkUrl = (rule, value, callback) => { 10 | const reg = /^https?:\/\/([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/ 11 | if (reg.test(value)) { 12 | return callback() 13 | } 14 | callback(new Error('请输入合法的 URL')) 15 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Tag.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Description: 博客标签 13 | * @Author: Naccl 14 | * @Date: 2020-07-27 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | public class Tag { 21 | private Long id; 22 | private String name;//标签名称 23 | private String color;//标签颜色(与Semantic UI提供的颜色对应,可选) 24 | private List blogs = new ArrayList<>();//该标签下的博客文章 25 | } 26 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/MomentService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.Moment; 4 | 5 | import java.util.List; 6 | 7 | public interface MomentService { 8 | List getMomentList(); 9 | 10 | List getMomentVOList(Integer pageNum, boolean adminIdentity); 11 | 12 | void addLikeByMomentId(Long momentId); 13 | 14 | void updateMomentPublishedById(Long momentId, Boolean published); 15 | 16 | Moment getMomentById(Long id); 17 | 18 | void deleteMomentById(Long id); 19 | 20 | void saveMoment(Moment moment); 21 | 22 | void updateMoment(Moment moment); 23 | } 24 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/logout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/annotation/VisitLogger.java: -------------------------------------------------------------------------------- 1 | package top.naccl.annotation; 2 | 3 | import top.naccl.enums.VisitBehavior; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @Description: 用于需要记录访客访问日志的方法 12 | * @Author: Naccl 13 | * @Date: 2020-12-04 14 | */ 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface VisitLogger { 18 | /** 19 | * 访问行为枚举 20 | */ 21 | VisitBehavior value() default VisitBehavior.UNKNOWN; 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/ExceptionLogMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.ExceptionLog; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 异常日志持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-12-03 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface ExceptionLogMapper { 17 | List getExceptionLogListByDate(String startDate, String endDate); 18 | 19 | int saveExceptionLog(ExceptionLog log); 20 | 21 | int deleteExceptionLogById(Long id); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/OperationLogMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.OperationLog; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 操作日志持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-11-30 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface OperationLogMapper { 17 | List getOperationLogListByDate(String startDate, String endDate); 18 | 19 | int saveOperationLog(OperationLog log); 20 | 21 | int deleteOperationLogById(Long id); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/ScheduleJobLogMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.ScheduleJobLog; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 定时任务日志持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-11-01 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface ScheduleJobLogMapper { 17 | List getJobLogListByDate(String startDate, String endDate); 18 | 19 | int saveJobLog(ScheduleJobLog jobLog); 20 | 21 | int deleteJobLogByLogId(Long logId); 22 | } 23 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | log 36 | application-test.properties 37 | application-prod.properties 38 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/PageResult.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Description: 分页结果 12 | * @Author: Naccl 13 | * @Date: 2020-08-08 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class PageResult { 20 | private Integer totalPage;//总页数 21 | private List list;//数据 22 | 23 | public PageResult(Integer totalPage, List list) { 24 | this.totalPage = totalPage; 25 | this.list = list; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/SiteSetting.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * @Description: 站点设置 11 | * @Author: Naccl 12 | * @Date: 2020-08-09 13 | */ 14 | @NoArgsConstructor 15 | @Getter 16 | @Setter 17 | @ToString 18 | @JsonIgnoreProperties(ignoreUnknown = true) 19 | public class SiteSetting { 20 | private Long id; 21 | private String nameEn; 22 | private String nameZh; 23 | private String value; 24 | private Integer type; 25 | } 26 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/BlogVisibility.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 博客可见性DTO 10 | * @Author: Naccl 11 | * @Date: 2020-09-04 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlogVisibility { 18 | private Boolean appreciation;//赞赏开关 19 | private Boolean recommend;//推荐开关 20 | private Boolean commentEnabled;//评论开关 21 | private Boolean top;//是否置顶 22 | private Boolean published;//公开或私密 23 | private String password;//密码保护 24 | } 25 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/RandomBlog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 随机博客 12 | * @Author: Naccl 13 | * @Date: 2020-08-17 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class RandomBlog { 20 | private Long id; 21 | private String title;//文章标题 22 | private String firstPicture;//文章首图,用于随机文章展示 23 | private Date createTime;//创建时间 24 | private String password;//文章密码 25 | private Boolean privacy;//是否私密文章 26 | } 27 | -------------------------------------------------------------------------------- /blog-view/src/api/blog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/plugins/axios' 2 | 3 | export function getBlogById(token, id) { 4 | return axios({ 5 | url: 'blog', 6 | method: 'GET', 7 | headers: { 8 | Authorization: token, 9 | }, 10 | params: { 11 | id 12 | } 13 | }) 14 | } 15 | 16 | export function checkBlogPassword(blogPasswordForm) { 17 | return axios({ 18 | url: 'checkBlogPassword', 19 | method: 'POST', 20 | data: { 21 | ...blogPasswordForm 22 | } 23 | }) 24 | } 25 | 26 | export function getSearchBlogList(query) { 27 | return axios({ 28 | url: 'searchBlog', 29 | method: 'GET', 30 | params: { 31 | query 32 | } 33 | }) 34 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/VisitRecord.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 访问记录 10 | * @Author: Naccl 11 | * @Date: 2021-02-23 12 | */ 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class VisitRecord { 18 | private Long id; 19 | private Integer pv;//访问量 20 | private Integer uv;//独立用户 21 | private String date;//日期"02-23" 22 | 23 | public VisitRecord(Integer pv, Integer uv, String date) { 24 | this.pv = pv; 25 | this.uv = uv; 26 | this.date = date; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/VisitorService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import top.naccl.entity.Visitor; 5 | import top.naccl.model.dto.VisitLogUuidTime; 6 | 7 | import java.util.List; 8 | 9 | public interface VisitorService { 10 | List getVisitorListByDate(String startDate, String endDate); 11 | 12 | List getNewVisitorIpSourceByYesterday(); 13 | 14 | boolean hasUUID(String uuid); 15 | 16 | @Async 17 | void saveVisitor(Visitor visitor); 18 | 19 | void updatePVAndLastTimeByUUID(VisitLogUuidTime dto); 20 | 21 | void deleteVisitor(Long id, String uuid); 22 | } 23 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/cover/Cover.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.cover; 2 | 3 | import org.commonmark.node.CustomNode; 4 | import org.commonmark.node.Delimited; 5 | 6 | /** 7 | * @Description: A cover node containing text and other inline nodes nodes as children. 8 | * @Author: Naccl 9 | * @Date: 2020-05-13 10 | */ 11 | public class Cover extends CustomNode implements Delimited { 12 | private static final String DELIMITER = "%%"; 13 | 14 | @Override 15 | public String getOpeningDelimiter() { 16 | return DELIMITER; 17 | } 18 | 19 | @Override 20 | public String getClosingDelimiter() { 21 | return DELIMITER; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/Heimu.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.heimu; 2 | 3 | import org.commonmark.node.CustomNode; 4 | import org.commonmark.node.Delimited; 5 | 6 | /** 7 | * @Description: A heimu node containing text and other inline nodes nodes as children. 8 | * @Author: Naccl 9 | * @Date: 2020-05-13 10 | */ 11 | public class Heimu extends CustomNode implements Delimited { 12 | private static final String DELIMITER = "@@"; 13 | 14 | @Override 15 | public String getOpeningDelimiter() { 16 | return DELIMITER; 17 | } 18 | 19 | @Override 20 | public String getClosingDelimiter() { 21 | return DELIMITER; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/annotation/AccessLimit.java: -------------------------------------------------------------------------------- 1 | package top.naccl.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Description: 访问控制 10 | * @Author: Naccl 11 | * @Date: 2021-04-04 12 | */ 13 | @Target(ElementType.METHOD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface AccessLimit { 16 | /** 17 | * 限制周期(秒) 18 | */ 19 | int seconds(); 20 | 21 | /** 22 | * 规定周期内限制次数 23 | */ 24 | int maxCount(); 25 | 26 | /** 27 | * 触发限制时的消息提示 28 | */ 29 | String msg() default "操作频率过高"; 30 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Friend.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 友链 12 | * @Author: Naccl 13 | * @Date: 2020-09-08 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class Friend { 20 | private Long id; 21 | private String nickname;//昵称 22 | private String description;//描述 23 | private String website;//站点 24 | private String avatar;//头像 25 | private Boolean published;//公开或隐藏 26 | private Integer views;//浏览次数 27 | private Date createTime;//创建时间 28 | } 29 | -------------------------------------------------------------------------------- /blog-cms/src/api/tag.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getData(queryInfo) { 4 | return axios({ 5 | url: 'tags', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function addTag(form) { 14 | return axios({ 15 | url: 'tag', 16 | method: 'POST', 17 | data: { 18 | ...form 19 | } 20 | }) 21 | } 22 | 23 | export function editTag(form) { 24 | return axios({ 25 | url: 'tag', 26 | method: 'PUT', 27 | data: { 28 | ...form 29 | } 30 | }) 31 | } 32 | 33 | export function deleteTagById(id) { 34 | return axios({ 35 | url: 'tag', 36 | method: 'DELETE', 37 | params: { 38 | id 39 | } 40 | }) 41 | } -------------------------------------------------------------------------------- /blog-cms/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 后台管理 - NBlog 9 | 10 | 11 | 12 | 13 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue. 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/ScheduleJobMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.ScheduleJob; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 定时任务持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-11-01 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface ScheduleJobMapper { 17 | List getJobList(); 18 | 19 | ScheduleJob getJobById(Long jobId); 20 | 21 | int saveJob(ScheduleJob scheduleJob); 22 | 23 | int updateJob(ScheduleJob scheduleJob); 24 | 25 | int deleteJobById(Long jobId); 26 | 27 | int updateJobStatusById(Long jobId, Boolean status); 28 | } 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/ScheduleJobService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.ScheduleJob; 4 | import top.naccl.entity.ScheduleJobLog; 5 | 6 | import java.util.List; 7 | 8 | public interface ScheduleJobService { 9 | List getJobList(); 10 | 11 | void saveJob(ScheduleJob scheduleJob); 12 | 13 | void updateJob(ScheduleJob scheduleJob); 14 | 15 | void deleteJobById(Long jobId); 16 | 17 | void runJobById(Long jobId); 18 | 19 | void updateJobStatusById(Long jobId, Boolean status); 20 | 21 | List getJobLogListByDate(String startDate, String endDate); 22 | 23 | void saveJobLog(ScheduleJobLog log); 24 | 25 | void deleteJobLogByLogId(Long logId); 26 | } 27 | -------------------------------------------------------------------------------- /blog-cms/src/api/category.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getData(queryInfo) { 4 | return axios({ 5 | url: 'categories', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function addCategory(form) { 14 | return axios({ 15 | url: 'category', 16 | method: 'POST', 17 | data: { 18 | ...form 19 | } 20 | }) 21 | } 22 | 23 | export function editCategory(form) { 24 | return axios({ 25 | url: 'category', 26 | method: 'PUT', 27 | data: { 28 | ...form 29 | } 30 | }) 31 | } 32 | 33 | export function deleteCategoryById(id) { 34 | return axios({ 35 | url: 'category', 36 | method: 'DELETE', 37 | params: { 38 | id 39 | } 40 | }) 41 | } -------------------------------------------------------------------------------- /blog-api/src/main/resources/mapper/AboutMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | select * from about 7 | 8 | 9 | 10 | 11 | update about set value=#{value} where name_en=#{nameEn} 12 | 13 | 14 | 15 | 16 | select value from about where name_en="commentEnabled" 17 | 18 | 19 | -------------------------------------------------------------------------------- /blog-cms/src/assets/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:#409EFF; 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#304156; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 190px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /blog-cms/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * @type {string} 4 | * @description page title 5 | */ 6 | title: 'Naccl\'s Blog Admin', 7 | 8 | /** 9 | * @type {string} 10 | * @description logo URL 11 | */ 12 | logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png', 13 | 14 | /** 15 | * @type {boolean} true | false 16 | * @description Whether fix the header 17 | */ 18 | fixedHeader: true, 19 | 20 | /** 21 | * @type {boolean} true | false 22 | * @description Whether show the logo in sidebar 23 | */ 24 | sidebarLogo: true, 25 | 26 | /** 27 | * @type {Array} 28 | * @description 默认展开的父级菜单 29 | */ 30 | defaultOpeneds: ['/blog', '/page', '/pictureHosting', '/system', '/log', '/statistics'] 31 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/MomentMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Moment; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 博客动态持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-08-24 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface MomentMapper { 17 | List getMomentList(); 18 | 19 | int addLikeByMomentId(Long momentId); 20 | 21 | int updateMomentPublishedById(Long momentId, Boolean published); 22 | 23 | Moment getMomentById(Long id); 24 | 25 | int deleteMomentById(Long id); 26 | 27 | int saveMoment(Moment moment); 28 | 29 | int updateMoment(Moment moment); 30 | } 31 | -------------------------------------------------------------------------------- /blog-view/src/components/blog/BlogList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/ScheduleJobLog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 定时任务日志 12 | * @Author: Naccl 13 | * @Date: 2020-11-01 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class ScheduleJobLog { 20 | private Long logId;//日志id 21 | private Long jobId;//任务id 22 | private String beanName;//spring bean名称 23 | private String methodName;//方法名 24 | private String params;//参数 25 | private Boolean status;//任务执行结果 26 | private String error;//异常信息 27 | private Integer times;//耗时(单位:毫秒) 28 | private Date createTime;//创建时间 29 | } 30 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Category; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 博客分类持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-07-29 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface CategoryMapper { 17 | List getCategoryList(); 18 | 19 | List getCategoryNameList(); 20 | 21 | int saveCategory(Category category); 22 | 23 | Category getCategoryById(Long id); 24 | 25 | Category getCategoryByName(String name); 26 | 27 | int deleteCategoryById(Long id); 28 | 29 | int updateCategory(Category category); 30 | } 31 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/VisitLogMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.VisitLog; 6 | import top.naccl.model.dto.VisitLogUuidTime; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Description: 访问日志持久层接口 12 | * @Author: Naccl 13 | * @Date: 2020-12-04 14 | */ 15 | @Mapper 16 | @Repository 17 | public interface VisitLogMapper { 18 | List getVisitLogListByUUIDAndDate(String uuid, String startDate, String endDate); 19 | 20 | List getUUIDAndCreateTimeByYesterday(); 21 | 22 | int saveVisitLog(VisitLog log); 23 | 24 | int deleteVisitLogById(Long id); 25 | 26 | int countVisitLogByToday(); 27 | } 28 | -------------------------------------------------------------------------------- /blog-cms/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const {title, logo, fixedHeader, sidebarLogo, defaultOpeneds} = defaultSettings 4 | 5 | const state = { 6 | title: title, 7 | logo: logo, 8 | fixedHeader: fixedHeader, 9 | sidebarLogo: sidebarLogo, 10 | defaultOpeneds: defaultOpeneds, 11 | } 12 | 13 | const mutations = { 14 | CHANGE_SETTING: (state, {key, value}) => { 15 | // eslint-disable-next-line no-prototype-builtins 16 | if (state.hasOwnProperty(key)) { 17 | state[key] = value 18 | } 19 | } 20 | } 21 | 22 | const actions = { 23 | changeSetting({commit}, data) { 24 | commit('CHANGE_SETTING', data) 25 | } 26 | } 27 | 28 | export default { 29 | namespaced: true, 30 | state, 31 | mutations, 32 | actions 33 | } 34 | 35 | -------------------------------------------------------------------------------- /blog-cms/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/FriendMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Friend; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 友链持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-09-08 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface FriendMapper { 17 | List getFriendList(); 18 | 19 | List getFriendVOList(); 20 | 21 | int updateFriendPublishedById(Long id, Boolean published); 22 | 23 | int saveFriend(Friend friend); 24 | 25 | int updateFriend(top.naccl.model.dto.Friend friend); 26 | 27 | int deleteFriend(Long id); 28 | 29 | int updateViewsByNickname(String nickname); 30 | } 31 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/SiteSettingConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * 站点设置常量 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-28 8 | */ 9 | public class SiteSettingConstants { 10 | public static final String COPYRIGHT = "copyright"; 11 | public static final String AVATAR = "avatar"; 12 | public static final String NAME = "name"; 13 | public static final String GITHUB = "github"; 14 | public static final String TELEGRAM = "telegram"; 15 | public static final String QQ = "qq"; 16 | public static final String BILIBILI = "bilibili"; 17 | public static final String NETEASE = "netease"; 18 | public static final String EMAIL = "email"; 19 | public static final String FAVORITE = "favorite"; 20 | public static final String ROLL_TEXT = "rollText"; 21 | } 22 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/ScheduleJob.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 定时任务 12 | * @Author: Naccl 13 | * @Date: 2020-11-01 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class ScheduleJob { 20 | public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; //任务调度参数key 21 | 22 | private Long jobId;//任务id 23 | private String beanName;//spring bean名称 24 | private String methodName;//方法名 25 | private String params;//参数 26 | private String cron;//cron表达式 27 | private Boolean status;//任务状态 28 | private String remark;//备注 29 | private Date createTime;//创建时间 30 | } 31 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/VisitorMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Visitor; 6 | import top.naccl.model.dto.VisitLogUuidTime; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Description: 访客统计持久层接口 12 | * @Author: Naccl 13 | * @Date: 2021-01-31 14 | */ 15 | @Mapper 16 | @Repository 17 | public interface VisitorMapper { 18 | List getVisitorListByDate(String startDate, String endDate); 19 | 20 | List getNewVisitorIpSourceByYesterday(); 21 | 22 | int hasUUID(String uuid); 23 | 24 | int saveVisitor(Visitor visitor); 25 | 26 | int updatePVAndLastTimeByUUID(VisitLogUuidTime dto); 27 | 28 | int deleteVisitorById(Long id); 29 | } 30 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/enums/CommentPageEnum.java: -------------------------------------------------------------------------------- 1 | package top.naccl.enums; 2 | 3 | /** 4 | * 评论页面枚举类 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-22 8 | */ 9 | public enum CommentPageEnum { 10 | UNKNOWN("UNKNOWN", "UNKNOWN"), 11 | 12 | BLOG("", ""), 13 | ABOUT("关于我", "/about"), 14 | FRIEND("友人帐", "/friends"), 15 | ; 16 | 17 | private String title; 18 | private String path; 19 | 20 | CommentPageEnum(String title, String path) { 21 | this.title = title; 22 | this.path = path; 23 | } 24 | 25 | public String getTitle() { 26 | return title; 27 | } 28 | 29 | public void setTitle(String title) { 30 | this.title = title; 31 | } 32 | 33 | public String getPath() { 34 | return path; 35 | } 36 | 37 | public void setPath(String path) { 38 | this.path = path; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/FriendService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.Friend; 4 | import top.naccl.model.vo.FriendInfo; 5 | 6 | import java.util.List; 7 | 8 | public interface FriendService { 9 | List getFriendList(); 10 | 11 | List getFriendVOList(); 12 | 13 | void updateFriendPublishedById(Long friendId, Boolean published); 14 | 15 | void saveFriend(Friend friend); 16 | 17 | void updateFriend(top.naccl.model.dto.Friend friend); 18 | 19 | void deleteFriend(Long id); 20 | 21 | void updateViewsByNickname(String nickname); 22 | 23 | FriendInfo getFriendInfo(boolean cache, boolean md); 24 | 25 | void updateFriendInfoContent(String content); 26 | 27 | void updateFriendInfoCommentEnabled(Boolean commentEnabled); 28 | } 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/TagMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Tag; 6 | import top.naccl.model.vo.TagBlogCount; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Description: 博客标签持久层接口 12 | * @Author: Naccl 13 | * @Date: 2020-07-30 14 | */ 15 | @Mapper 16 | @Repository 17 | public interface TagMapper { 18 | List getTagList(); 19 | 20 | List getTagListNotId(); 21 | 22 | List getTagListByBlogId(Long blogId); 23 | 24 | int saveTag(Tag tag); 25 | 26 | Tag getTagById(Long id); 27 | 28 | Tag getTagByName(String name); 29 | 30 | int deleteTagById(Long id); 31 | 32 | int updateTag(Tag tag); 33 | 34 | List getTagBlogCount(); 35 | } 36 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Introduction.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Description: 侧边栏资料卡 13 | * @Author: Naccl 14 | * @Date: 2020-08-09 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | public class Introduction { 21 | private String avatar; 22 | private String name; 23 | private String github; 24 | private String telegram; 25 | private String qq; 26 | private String bilibili; 27 | private String netease; 28 | private String email; 29 | 30 | private List rollText = new ArrayList<>(); 31 | private List favorites = new ArrayList<>(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /blog-view/src/components/sidebar/Tags.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 标签云 5 | 6 | 7 | {{ tag.name }} 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | -------------------------------------------------------------------------------- /blog-api/src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | select * from user where username=#{username} limit 1 7 | 8 | 9 | 10 | 11 | select * from user where id=#{id} limit 1 12 | 13 | 14 | 15 | 16 | update user set username=#{user.username}, password=#{user.password}, update_time=now() where username=#{username} 17 | 18 | 19 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/SiteSettingMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.SiteSetting; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Description: 站点设置持久层接口 11 | * @Author: Naccl 12 | * @Date: 2020-08-03 13 | */ 14 | @Mapper 15 | @Repository 16 | public interface SiteSettingMapper { 17 | List getList(); 18 | 19 | List getFriendInfo(); 20 | 21 | String getWebTitleSuffix(); 22 | 23 | int updateSiteSetting(SiteSetting siteSetting); 24 | 25 | int deleteSiteSettingById(Integer id); 26 | 27 | int saveSiteSetting(SiteSetting siteSetting); 28 | 29 | int updateFriendInfoContent(String content); 30 | 31 | int updateFriendInfoCommentEnabled(Boolean commentEnabled); 32 | } 33 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/UploadProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * 静态文件上传访问路径配置(目前用于评论中QQ头像的本地存储) 12 | * 13 | * @author: Naccl 14 | * @date: 2022-01-23 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | @Configuration 21 | @ConfigurationProperties(prefix = "upload.file") 22 | public class UploadProperties { 23 | /** 24 | * 本地文件路径 25 | */ 26 | private String path; 27 | /** 28 | * 请求地址映射 29 | */ 30 | private String accessPath; 31 | /** 32 | * 本地文件路径映射 33 | */ 34 | private String resourcesLocations; 35 | } 36 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/impl/CityVisitorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import top.naccl.entity.CityVisitor; 7 | import top.naccl.mapper.CityVisitorMapper; 8 | import top.naccl.service.CityVisitorService; 9 | 10 | /** 11 | * @Description: 城市访客数量统计业务层实现 12 | * @Author: Naccl 13 | * @Date: 2021-02-26 14 | */ 15 | @Service 16 | public class CityVisitorServiceImpl implements CityVisitorService { 17 | @Autowired 18 | CityVisitorMapper cityVisitorMapper; 19 | 20 | @Transactional(rollbackFor = Exception.class) 21 | @Override 22 | public void saveCityVisitor(CityVisitor cityVisitor) { 23 | cityVisitorMapper.saveCityVisitor(cityVisitor); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/impl/VisitRecordServiceImpl.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import top.naccl.entity.VisitRecord; 7 | import top.naccl.mapper.VisitRecordMapper; 8 | import top.naccl.service.VisitRecordService; 9 | 10 | /** 11 | * @Description: 访问记录业务层实现 12 | * @Author: Naccl 13 | * @Date: 2021-02-23 14 | */ 15 | @Service 16 | public class VisitRecordServiceImpl implements VisitRecordService { 17 | @Autowired 18 | VisitRecordMapper visitRecordMapper; 19 | 20 | @Transactional(rollbackFor = Exception.class) 21 | @Override 22 | public void saveVisitRecord(VisitRecord visitRecord) { 23 | visitRecordMapper.saveVisitRecord(visitRecord); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /blog-cms/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 19 | 20 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /blog-view/src/store/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | siteInfo: '', 3 | introduction: { 4 | avatar: '', 5 | name: '', 6 | rollText: [], 7 | }, 8 | commentQuery: { 9 | //用于后端判断该评论所在页面类型(文章、友链、关于我) 10 | page: 0, 11 | blogId: null, 12 | pageNum: 1, 13 | pageSize: 5 14 | }, 15 | allComment: 0, 16 | closeComment: 0, 17 | commentTotalPage: 0, 18 | comments: [], 19 | parentCommentId: -1, 20 | commentForm: { 21 | content: '', 22 | nickname: '', 23 | email: '', 24 | website: '', 25 | notice: true 26 | }, 27 | //博客文章渲染完成的标记 28 | isBlogRenderComplete: false, 29 | //受保护文章密码对话框 30 | blogPasswordDialogVisible: false, 31 | blogPasswordForm: { 32 | blogId: 0, 33 | password: '' 34 | }, 35 | //专注模式 36 | focusMode: false, 37 | //文章页面路由到首页的标记 38 | isBlogToHome: false, 39 | //可视窗口大小 40 | clientSize: { 41 | clientHeight: 0, 42 | clientWidth: 1080 43 | } 44 | } -------------------------------------------------------------------------------- /blog-cms/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/BlogProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * 博客配置(目前用于评论提醒模板中的超链接) 12 | * 13 | * @author: Naccl 14 | * @date: 2022-01-22 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | @Configuration 21 | @ConfigurationProperties(prefix = "blog") 22 | public class BlogProperties { 23 | /** 24 | * 博客名称 25 | */ 26 | private String name; 27 | /** 28 | * 博客后端接口URL 29 | */ 30 | private String api; 31 | /** 32 | * 博客前端后台管理URL 33 | */ 34 | private String cms; 35 | /** 36 | * 博客前端前台页面URL 37 | */ 38 | private String view; 39 | } 40 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/AboutController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import top.naccl.annotation.VisitLogger; 7 | import top.naccl.enums.VisitBehavior; 8 | import top.naccl.model.vo.Result; 9 | import top.naccl.service.AboutService; 10 | 11 | /** 12 | * @Description: 关于我页面 13 | * @Author: Naccl 14 | * @Date: 2020-08-31 15 | */ 16 | @RestController 17 | public class AboutController { 18 | @Autowired 19 | AboutService aboutService; 20 | 21 | /** 22 | * 获取关于我页面信息 23 | * 24 | * @return 25 | */ 26 | @VisitLogger(VisitBehavior.ABOUT) 27 | @GetMapping("/about") 28 | public Result about() { 29 | return Result.ok("获取成功", aboutService.getAboutInfo()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /blog-api/src/main/resources/mapper/CityVisitorMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | select city, uv from city_visitor order by uv desc 13 | 14 | 15 | 16 | 17 | insert into city_visitor (city, uv) values (#{city}, #{uv}) 18 | on duplicate key update 19 | uv=uv+#{uv} 20 | 21 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/PageComment.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | /** 13 | * @Description: 页面评论 14 | * @Author: Naccl 15 | * @Date: 2020-08-15 16 | */ 17 | @NoArgsConstructor 18 | @Getter 19 | @Setter 20 | @ToString 21 | public class PageComment { 22 | private Long id; 23 | private String nickname;//昵称 24 | private String content;//评论内容 25 | private String avatar;//头像(图片路径) 26 | private Date createTime;//评论时间 27 | private String website;//个人网站 28 | private Boolean adminComment;//博主回复 29 | private String parentCommentId;//父评论id 30 | private String parentCommentNickname;//父评论昵称 31 | 32 | private List replyComments = new ArrayList<>();//回复该评论的评论 33 | } 34 | -------------------------------------------------------------------------------- /blog-cms/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /blog-cms/src/assets/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | 19 | // to fixed https://github.com/ElemeFE/element/issues/2461 20 | .el-dialog { 21 | transform: none; 22 | left: 0; 23 | position: relative; 24 | margin: 0 auto; 25 | } 26 | 27 | // refine element ui upload 28 | .upload-container { 29 | .el-upload { 30 | width: 100%; 31 | 32 | .el-upload-dragger { 33 | width: 100%; 34 | height: 200px; 35 | } 36 | } 37 | } 38 | 39 | // dropdown 40 | .el-dropdown-menu { 41 | a { 42 | display: block 43 | } 44 | } 45 | 46 | // to fix el-date-picker css style 47 | .el-range-separator { 48 | box-sizing: content-box; 49 | } 50 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/GithubProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * GitHub配置(目前用于评论中QQ头像的图床) 12 | * 13 | * @author: Naccl 14 | * @date: 2022-01-23 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | @Configuration 21 | @ConfigurationProperties(prefix = "upload.github") 22 | public class GithubProperties { 23 | /** 24 | * GitHub token 25 | */ 26 | private String token; 27 | /** 28 | * GitHub username 29 | */ 30 | private String username; 31 | /** 32 | * GitHub 仓库名 33 | */ 34 | private String repos; 35 | /** 36 | * GitHub 仓库路径 37 | */ 38 | private String reposPath; 39 | } 40 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-view/src/store/mutations-types.js: -------------------------------------------------------------------------------- 1 | export const SAVE_INTRODUCTION = 'saveIntroduction' 2 | export const SAVE_SITE_INFO = 'saveSiteInfo' 3 | export const SAVE_COMMENT_RESULT = 'saveCommentResult' 4 | export const SET_COMMENT_QUERY_PAGE = 'setCommentQueryPage' 5 | export const SET_COMMENT_QUERY_BLOG_ID = 'setCommentQueryBlogId' 6 | export const SET_COMMENT_QUERY_PAGE_NUM = 'setCommentQueryPageNum' 7 | export const SET_PARENT_COMMENT_ID = 'setParentCommentId' 8 | export const RESET_COMMENT_FORM = 'resetCommentForm' 9 | export const RESTORE_COMMENT_FORM = 'restoreCommentForm' 10 | export const SET_IS_BLOG_RENDER_COMPLETE = 'setIsBlogRenderComplete' 11 | export const SET_BLOG_PASSWORD_DIALOG_VISIBLE = 'setBlogPasswordDialogVisible' 12 | export const SET_BLOG_PASSWORD_FORM = 'setBlogPasswordForm' 13 | export const SET_FOCUS_MODE = 'setFocusMode' 14 | export const SET_IS_BLOG_TO_HOME = 'setIsBlogToHome' 15 | export const SAVE_CLIENT_SIZE = 'saveClientSize' -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.entity.Comment; 4 | import top.naccl.model.vo.PageComment; 5 | 6 | import java.util.List; 7 | 8 | public interface CommentService { 9 | List getListByPageAndParentCommentId(Integer page, Long blogId, Long parentCommentId); 10 | 11 | List getPageCommentList(Integer page, Long blogId, Long parentCommentId); 12 | 13 | Comment getCommentById(Long id); 14 | 15 | void updateCommentPublishedById(Long commentId, Boolean published); 16 | 17 | void updateCommentNoticeById(Long commentId, Boolean notice); 18 | 19 | void deleteCommentById(Long commentId); 20 | 21 | void deleteCommentsByBlogId(Long blogId); 22 | 23 | void updateComment(Comment comment); 24 | 25 | int countByPageAndIsPublished(Integer page, Long blogId, Boolean isPublished); 26 | 27 | void saveComment(top.naccl.model.dto.Comment comment); 28 | } 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/comment/channel/ChannelFactory.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.comment.channel; 2 | 3 | import top.naccl.constant.CommentConstants; 4 | import top.naccl.util.common.SpringContextUtils; 5 | 6 | /** 7 | * 评论提醒方式 8 | * 9 | * @author: Naccl 10 | * @date: 2022-01-22 11 | */ 12 | public class ChannelFactory { 13 | /** 14 | * 创建评论提醒方式 15 | * 16 | * @param channelName 方式名称 17 | * @return 18 | */ 19 | public static CommentNotifyChannel getChannel(String channelName) { 20 | if (CommentConstants.TELEGRAM.equalsIgnoreCase(channelName)) { 21 | return SpringContextUtils.getBean("telegramChannel", CommentNotifyChannel.class); 22 | } else if (CommentConstants.MAIL.equalsIgnoreCase(channelName)) { 23 | return SpringContextUtils.getBean("mailChannel", CommentNotifyChannel.class); 24 | } 25 | throw new RuntimeException("Unsupported value in [application.properties]: [comment.notify.channel]"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/markdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-cms/src/assets/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .25s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-10px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(10px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .25s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/upload/channel/ChannelFactory.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.upload.channel; 2 | 3 | import top.naccl.constant.UploadConstants; 4 | import top.naccl.util.common.SpringContextUtils; 5 | 6 | /** 7 | * 文件上传方式 8 | * 9 | * @author: Naccl 10 | * @date: 2022-01-23 11 | */ 12 | public class ChannelFactory { 13 | /** 14 | * 创建文件上传方式 15 | * 16 | * @param channelName 方式名称 17 | * @return 文件上传Channel 18 | */ 19 | public static FileUploadChannel getChannel(String channelName) { 20 | switch (channelName.toLowerCase()) { 21 | case UploadConstants.LOCAL: 22 | return SpringContextUtils.getBean(LocalChannel.class); 23 | case UploadConstants.GITHUB: 24 | return SpringContextUtils.getBean(GithubChannel.class); 25 | case UploadConstants.UPYUN: 26 | return SpringContextUtils.getBean(UpyunChannel.class); 27 | } 28 | throw new RuntimeException("Unsupported value in [application.properties]: [upload.channel]"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/UpyunProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * 又拍云配置(目前用于评论中QQ头像的图床) 12 | * 13 | * @author: Naccl 14 | * @date: 2022-05-26 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | @Configuration 21 | @ConfigurationProperties(prefix = "upload.upyun") 22 | public class UpyunProperties { 23 | /** 24 | * 又拍云存储空间名称 25 | */ 26 | private String bucketName; 27 | /** 28 | * 操作员名称 29 | */ 30 | private String username; 31 | /** 32 | * 操作员密码 33 | */ 34 | private String password; 35 | /** 36 | * 存储路径 37 | */ 38 | private String path; 39 | /** 40 | * CDN访问域名 41 | */ 42 | private String domain; 43 | } 44 | -------------------------------------------------------------------------------- /blog-cms/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | sidebar: { 3 | opened: true, 4 | withoutAnimation: false 5 | }, 6 | device: 'desktop' 7 | } 8 | 9 | const mutations = { 10 | TOGGLE_SIDEBAR: state => { 11 | state.sidebar.opened = !state.sidebar.opened 12 | state.sidebar.withoutAnimation = false 13 | }, 14 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 15 | state.sidebar.opened = false 16 | state.sidebar.withoutAnimation = withoutAnimation 17 | }, 18 | TOGGLE_DEVICE: (state, device) => { 19 | state.device = device 20 | } 21 | } 22 | 23 | const actions = { 24 | toggleSideBar({ commit }) { 25 | commit('TOGGLE_SIDEBAR') 26 | }, 27 | closeSideBar({ commit }, { withoutAnimation }) { 28 | commit('CLOSE_SIDEBAR', withoutAnimation) 29 | }, 30 | toggleDevice({ commit }, device) { 31 | commit('TOGGLE_DEVICE', device) 32 | } 33 | } 34 | 35 | export default { 36 | namespaced: true, 37 | state, 38 | mutations, 39 | actions 40 | } 41 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/enums/VisitBehavior.java: -------------------------------------------------------------------------------- 1 | package top.naccl.enums; 2 | 3 | /** 4 | * 访问行为枚举类 5 | * 6 | * @author: Naccl 7 | * @date: 2022-01-08 8 | */ 9 | public enum VisitBehavior { 10 | UNKNOWN("UNKNOWN", "UNKNOWN"), 11 | 12 | INDEX("访问页面", "首页"), 13 | ARCHIVE("访问页面", "归档"), 14 | MOMENT("访问页面", "动态"), 15 | FRIEND("访问页面", "友链"), 16 | ABOUT("访问页面", "关于我"), 17 | 18 | BLOG("查看博客", ""), 19 | CATEGORY("查看分类", ""), 20 | TAG("查看标签", ""), 21 | SEARCH("搜索博客", ""), 22 | CLICK_FRIEND("点击友链", ""), 23 | LIKE_MOMENT("点赞动态", ""), 24 | CHECK_PASSWORD("校验博客密码", ""), 25 | ; 26 | 27 | /** 28 | * 访问行为 29 | */ 30 | private String behavior; 31 | /** 32 | * 访问内容 33 | */ 34 | private String content; 35 | 36 | VisitBehavior(String behavior, String content) { 37 | this.behavior = behavior; 38 | this.content = content; 39 | } 40 | 41 | public String getBehavior() { 42 | return behavior; 43 | } 44 | 45 | public String getContent() { 46 | return content; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/Comment.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 评论DTO 12 | * @Author: Naccl 13 | * @Date: 2020-08-27 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class Comment { 20 | private Long id; 21 | private String nickname;//昵称 22 | private String email;//邮箱 23 | private String content;//评论内容 24 | private String avatar;//头像(图片路径) 25 | private Date createTime;//评论时间 26 | private String website;//个人网站 27 | private String ip;//评论者ip地址 28 | private Boolean published;//公开或回收站 29 | private Boolean adminComment;//博主回复 30 | private Integer page;//0普通文章,1关于我页面 31 | private Boolean notice;//接收邮件提醒 32 | private Long parentCommentId;//父评论id 33 | private Long blogId;//所属的文章id 34 | private String qq;//如果评论昵称为QQ号,则将昵称和头像置为QQ昵称和QQ头像,并将此字段置为QQ号备份 35 | } 36 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/BlogInfo.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import top.naccl.entity.Category; 8 | import top.naccl.entity.Tag; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | /** 15 | * @Description: 博客简要信息 16 | * @Author: Naccl 17 | * @Date: 2020-08-08 18 | */ 19 | @NoArgsConstructor 20 | @Getter 21 | @Setter 22 | @ToString 23 | public class BlogInfo { 24 | private Long id; 25 | private String title;//文章标题 26 | private String description;//描述 27 | private Date createTime;//创建时间 28 | private Integer views;//浏览次数 29 | private Integer words;//文章字数 30 | private Integer readTime;//阅读时长(分钟) 31 | private Boolean top;//是否置顶 32 | private String password;//文章密码 33 | private Boolean privacy;//是否私密文章 34 | 35 | private Category category;//文章分类 36 | private List tags = new ArrayList<>();//文章标签 37 | } 38 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Visitor.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 访客记录 12 | * @Author: Naccl 13 | * @Date: 2021-01-31 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class Visitor { 20 | private Long id; 21 | private String uuid;//访客标识码 22 | private String ip;//ip 23 | private String ipSource;//ip来源 24 | private String os;//操作系统 25 | private String browser;//浏览器 26 | private Date createTime;//首次访问时间 27 | private Date lastTime;//最后访问时间 28 | private Integer pv;//访问页数统计 29 | private String userAgent; 30 | 31 | public Visitor(String uuid, String ip, String userAgent) { 32 | this.uuid = uuid; 33 | this.ip = ip; 34 | Date date = new Date(); 35 | this.createTime = date; 36 | this.lastTime = date; 37 | this.pv = 0; 38 | this.userAgent = userAgent; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /blog-api/src/main/resources/mapper/VisitRecordMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | select pv, uv, date from visit_record order by id desc limit #{limit} 15 | 16 | 17 | 18 | 19 | insert into visit_record (pv, uv, date) values (#{pv}, #{uv}, #{date}) 20 | 21 | -------------------------------------------------------------------------------- /blog-view/src/components/comment/Pagination.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 27 | 28 | -------------------------------------------------------------------------------- /blog-view/src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | 5 | const request = axios.create({ 6 | baseURL: 'http://localhost:8090/', 7 | timeout: 10000, 8 | }) 9 | 10 | // 请求拦截 11 | request.interceptors.request.use( 12 | config => { 13 | NProgress.start() 14 | const identification = window.localStorage.getItem('identification') 15 | //identification存在,且是基于baseURL的请求 16 | if (identification && !(config.url.startsWith('http://') || config.url.startsWith('https://'))) { 17 | config.headers.identification = identification 18 | } 19 | return config 20 | } 21 | ) 22 | 23 | // 响应拦截 24 | request.interceptors.response.use( 25 | config => { 26 | NProgress.done() 27 | const identification = config.headers.identification 28 | if (identification) { 29 | //保存身份标识到localStorage 30 | window.localStorage.setItem('identification', identification) 31 | } 32 | return config.data 33 | } 34 | ) 35 | 36 | export default request -------------------------------------------------------------------------------- /blog-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-view", 3 | "version": "1.0.0", 4 | "description": "NBlog view", 5 | "author": "Naccl ", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build" 10 | }, 11 | "dependencies": { 12 | "axios": "0.24.0", 13 | "core-js": "3.6.5", 14 | "element-ui": "2.13.2", 15 | "moment": "2.27.0", 16 | "nprogress": "0.2.0", 17 | "sanitize-html": "2.3.3", 18 | "semantic-ui-css": "2.4.1", 19 | "v-viewer": "1.5.1", 20 | "vue": "2.6.11", 21 | "vue-lazyload": "1.3.5", 22 | "vue-router": "3.3.4", 23 | "vuex": "3.5.1" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "4.4.0", 27 | "@vue/cli-plugin-router": "4.4.0", 28 | "@vue/cli-plugin-vuex": "4.4.0", 29 | "@vue/cli-service": "4.4.0", 30 | "vue-template-compiler": "2.6.11" 31 | }, 32 | "browserslist": [ 33 | "> 1%", 34 | "last 2 versions", 35 | "not dead" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/ArchiveController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import top.naccl.annotation.VisitLogger; 7 | import top.naccl.enums.VisitBehavior; 8 | import top.naccl.model.vo.Result; 9 | import top.naccl.service.BlogService; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * @Description: 归档页面 15 | * @Author: Naccl 16 | * @Date: 2020-08-12 17 | */ 18 | @RestController 19 | public class ArchiveController { 20 | @Autowired 21 | BlogService blogService; 22 | 23 | /** 24 | * 按年月分组归档公开博客 统计公开博客总数 25 | * 26 | * @return 27 | */ 28 | @VisitLogger(VisitBehavior.ARCHIVE) 29 | @GetMapping("/archives") 30 | public Result archives() { 31 | Map archiveBlogMap = blogService.getArchiveBlogAndCountByIsPublished(); 32 | return Result.ok("请求成功", archiveBlogMap); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/ProxyProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 10 | 11 | /** 12 | * 代理配置(目前用于RestTemplate发送tg消息) 13 | * 14 | * @author: Naccl 15 | * @date: 2022-01-22 16 | */ 17 | @NoArgsConstructor 18 | @Getter 19 | @Setter 20 | @ToString 21 | @Configuration 22 | @ConfigurationProperties(prefix = "http.proxy.server") 23 | public class ProxyProperties { 24 | /** 25 | * 代理服务器地址 26 | */ 27 | private String host; 28 | /** 29 | * 代理服务器端口 30 | */ 31 | private Integer port; 32 | /** 33 | * 连接超时(单位毫秒),通常不应该为0,0为无限超时时间,-1为系统的默认超时时间 34 | * 35 | * @see SimpleClientHttpRequestFactory#setConnectTimeout(int) 36 | */ 37 | private Integer timeout; 38 | } 39 | -------------------------------------------------------------------------------- /blog-cms/src/util/directive.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /** 4 | * 防抖 单位时间只触发最后一次 5 | * 例:刷新 6 | * 简写:刷新 7 | */ 8 | Vue.directive('debounce', { 9 | inserted: function (el, binding) { 10 | let [fn, event = "click", time = 300] = binding.value 11 | let timer 12 | el.addEventListener(event, () => { 13 | timer && clearTimeout(timer) 14 | timer = setTimeout(() => fn(), time) 15 | }) 16 | } 17 | }) 18 | 19 | /** 20 | * 节流 每单位时间可触发一次 21 | * 例:刷新 22 | * 传递参数:刷新 23 | */ 24 | Vue.directive('throttle', { 25 | inserted: function (el, binding) { 26 | let [fn, event = "click", time = 300] = binding.value 27 | let now, preTime 28 | el.addEventListener(event, () => { 29 | now = new Date() 30 | if (!preTime || now - preTime > time) { 31 | preTime = now 32 | fn() 33 | } 34 | }) 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /blog-view/src/util/directive.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /** 4 | * 防抖 单位时间只触发最后一次 5 | * 例:刷新 6 | * 简写:刷新 7 | */ 8 | Vue.directive('debounce', { 9 | inserted: function (el, binding) { 10 | let [fn, event = "click", time = 300] = binding.value 11 | let timer 12 | el.addEventListener(event, () => { 13 | timer && clearTimeout(timer) 14 | timer = setTimeout(() => fn(), time) 15 | }) 16 | } 17 | }) 18 | 19 | /** 20 | * 节流 每单位时间可触发一次 21 | * 例:刷新 22 | * 传递参数:刷新 23 | */ 24 | Vue.directive('throttle', { 25 | inserted: function (el, binding) { 26 | let [fn, event = "click", time = 300] = binding.value 27 | let now, preTime 28 | el.addEventListener(event, () => { 29 | now = new Date() 30 | if (!preTime || now - preTime > time) { 31 | preTime = now 32 | fn() 33 | } 34 | }) 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /blog-view/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configureWebpack: { 3 | resolve: { 4 | alias: { 5 | 'assets': '@/assets', 6 | 'common': '@/common', 7 | 'components': '@/components', 8 | 'api': '@/api', 9 | 'views': '@/views', 10 | 'plugins': '@/plugins' 11 | } 12 | } 13 | }, 14 | optimization: { 15 | splitChunks: { 16 | cacheGroups: { 17 | vendor: { 18 | test: /[\\/]node_modules[\\/]/, 19 | name(module) { 20 | // get the name. E.g. node_modules/packageName/not/this/part.js 21 | // or node_modules/packageName 22 | const packageName = module.context.match( 23 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/ 24 | )[1]; 25 | // npm package names are URL-safe, but some servers don't like @ symbols 26 | return `npm.${packageName.replace("@", "")}`; 27 | }, 28 | chunks: "all", 29 | enforce: true, 30 | priority: 10, 31 | minSize: 50000, // 50KB 32 | maxSize: 200000, 33 | reuseExistingChunk: true, 34 | }, 35 | }, 36 | }, 37 | }, 38 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/LoginLog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 登录日志 12 | * @Author: Naccl 13 | * @Date: 2020-12-03 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class LoginLog { 20 | private Long id; 21 | private String username;//用户名称 22 | private String ip;//ip 23 | private String ipSource;//ip来源 24 | private String os;//操作系统 25 | private String browser;//浏览器 26 | private Boolean status;//登录状态 27 | private String description;//操作信息 28 | private Date createTime;//操作时间 29 | private String userAgent; 30 | 31 | public LoginLog(String username, String ip, boolean status, String description, String userAgent) { 32 | this.username = username; 33 | this.ip = ip; 34 | this.status = status; 35 | this.description = description; 36 | this.createTime = new Date(); 37 | this.userAgent = userAgent; 38 | } 39 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/BlogDetail.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import top.naccl.entity.Category; 8 | import top.naccl.entity.Tag; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | /** 15 | * @Description: 博客详情 16 | * @Author: Naccl 17 | * @Date: 2020-08-12 18 | */ 19 | @NoArgsConstructor 20 | @Getter 21 | @Setter 22 | @ToString 23 | public class BlogDetail { 24 | private Long id; 25 | private String title;//文章标题 26 | private String content;//文章正文 27 | private Boolean appreciation;//赞赏开关 28 | private Boolean commentEnabled;//评论开关 29 | private Boolean top;//是否置顶 30 | private Date createTime;//创建时间 31 | private Date updateTime;//更新时间 32 | private Integer views;//浏览次数 33 | private Integer words;//文章字数 34 | private Integer readTime;//阅读时长(分钟) 35 | private String password;//密码保护 36 | 37 | private Category category;//文章分类 38 | private List tags = new ArrayList<>();//文章标签 39 | } 40 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/task/RedisSyncScheduleTask.java: -------------------------------------------------------------------------------- 1 | package top.naccl.task; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | import top.naccl.constant.RedisKeyConstants; 6 | import top.naccl.service.BlogService; 7 | import top.naccl.service.RedisService; 8 | 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * @Description: Redis相关定时任务 14 | * @Author: Naccl 15 | * @Date: 2020-11-02 16 | */ 17 | @Component 18 | public class RedisSyncScheduleTask { 19 | @Autowired 20 | RedisService redisService; 21 | @Autowired 22 | BlogService blogService; 23 | 24 | /** 25 | * 从Redis同步博客文章浏览量到数据库 26 | */ 27 | public void syncBlogViewsToDatabase() { 28 | String redisKey = RedisKeyConstants.BLOG_VIEWS_MAP; 29 | Map blogViewsMap = redisService.getMapByHash(redisKey); 30 | Set keys = blogViewsMap.keySet(); 31 | for (Integer key : keys) { 32 | Integer views = (Integer) blogViewsMap.get(key); 33 | blogService.updateViews(key.longValue(), views); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/common/ValidatorUtils.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.common; 2 | 3 | import javax.validation.ConstraintViolation; 4 | import javax.validation.Validation; 5 | import javax.validation.Validator; 6 | import java.util.Set; 7 | 8 | /** 9 | * @Description: 10 | * @Author: Naccl 11 | * @Date: 2020-11-01 12 | */ 13 | public class ValidatorUtils { 14 | private static Validator validator; 15 | 16 | static { 17 | validator = Validation.buildDefaultValidatorFactory().getValidator(); 18 | } 19 | 20 | /** 21 | * 校验对象 22 | * 23 | * @param object 待校验对象 24 | * @param groups 待校验的组 25 | * @throws RuntimeException 校验不通过,则报BusinessException异常 26 | */ 27 | public static void validateEntity(Object object, Class>... groups) throws RuntimeException { 28 | Set> constraintViolations = validator.validate(object, groups); 29 | if (!constraintViolations.isEmpty()) { 30 | ConstraintViolation constraint = constraintViolations.iterator().next(); 31 | throw new RuntimeException(constraint.getMessage()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /blog-cms/src/api/comment.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getCommentListByQuery(queryInfo) { 4 | return axios({ 5 | url: 'comments', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function getBlogList() { 14 | return axios({ 15 | url: 'blogIdAndTitle', 16 | method: 'GET' 17 | }) 18 | } 19 | 20 | export function updatePublished(id, published) { 21 | return axios({ 22 | url: 'comment/published', 23 | method: 'PUT', 24 | params: { 25 | id, 26 | published 27 | } 28 | }) 29 | } 30 | 31 | export function updateNotice(id, notice) { 32 | return axios({ 33 | url: 'comment/notice', 34 | method: 'PUT', 35 | params: { 36 | id, 37 | notice 38 | } 39 | }) 40 | } 41 | 42 | export function deleteCommentById(id) { 43 | return axios({ 44 | url: 'comment', 45 | method: 'DELETE', 46 | params: { 47 | id 48 | } 49 | }) 50 | } 51 | 52 | export function editComment(form) { 53 | return axios({ 54 | url: 'comment', 55 | method: 'PUT', 56 | data: { 57 | ...form 58 | } 59 | }) 60 | } -------------------------------------------------------------------------------- /blog-cms/src/api/moment.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getMomentListByQuery(queryInfo) { 4 | return axios({ 5 | url: 'moments', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function updatePublished(id, published) { 14 | return axios({ 15 | url: 'moment/published', 16 | method: 'PUT', 17 | params: { 18 | id, 19 | published 20 | } 21 | }) 22 | } 23 | 24 | export function getMomentById(id) { 25 | return axios({ 26 | url: 'moment', 27 | method: 'GET', 28 | params: { 29 | id 30 | } 31 | }) 32 | } 33 | 34 | export function deleteMomentById(id) { 35 | return axios({ 36 | url: 'moment', 37 | method: 'DELETE', 38 | params: { 39 | id 40 | } 41 | }) 42 | } 43 | 44 | export function saveMoment(moment) { 45 | return axios({ 46 | url: 'moment', 47 | method: 'POST', 48 | data: { 49 | ...moment 50 | } 51 | }) 52 | } 53 | 54 | export function updateMoment(moment) { 55 | return axios({ 56 | url: 'moment', 57 | method: 'PUT', 58 | data: { 59 | ...moment 60 | } 61 | }) 62 | } -------------------------------------------------------------------------------- /blog-cms/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | //normalize.css 7 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 8 | //element-ui 9 | import Element from 'element-ui' 10 | import 'element-ui/lib/theme-chalk/index.css' 11 | //global css 12 | import '@/assets/styles/index.scss' 13 | //icon 14 | import '@/icons' 15 | 16 | //moment 17 | import './util/dateTimeFormatUtils.js' 18 | //mavonEditor 19 | import mavonEditor from 'mavon-editor' 20 | import 'mavon-editor/dist/css/index.css' 21 | //v-viewer 22 | import 'viewerjs/dist/viewer.css' 23 | import Viewer from 'v-viewer' 24 | // directive 25 | import './util/directive' 26 | 27 | Vue.use(mavonEditor) 28 | Vue.use(Element) 29 | Vue.use(Viewer) 30 | 31 | Vue.prototype.msgSuccess = function (msg) { 32 | this.$message.success(msg) 33 | } 34 | 35 | Vue.prototype.msgError = function (msg) { 36 | this.$message.error(msg) 37 | } 38 | 39 | Vue.config.productionTip = false 40 | 41 | new Vue({ 42 | router, 43 | store, 44 | render: h => h(App) 45 | }).$mount('#app') -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/properties/TelegramProperties.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * Telegram配置 12 | * 13 | * @author: Naccl 14 | * @date: 2022-01-22 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @ToString 20 | @Configuration 21 | @ConfigurationProperties(prefix = "tg.bot") 22 | public class TelegramProperties { 23 | /** 24 | * Telegram bot的api,默认是https://api.telegram.org/bot 25 | * 如果使用自己的反代,可以修改它 26 | */ 27 | private String api; 28 | /** 29 | * bot的token,可以从 @BotFather 处获取 30 | */ 31 | private String token; 32 | /** 33 | * 自己账号和bot的聊天会话id 34 | */ 35 | private String chatId; 36 | /** 37 | * 是否使用代理 38 | */ 39 | private Boolean useProxy; 40 | /** 41 | * 是否使用反向代理 42 | */ 43 | private Boolean useReverseProxy; 44 | /** 45 | * 反向代理URL 46 | */ 47 | private String reverseProxyUrl; 48 | } 49 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/RedisSerializeConfig.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 8 | 9 | /** 10 | * @Description: Redis序列化配置 11 | * @Author: Naccl 12 | * @Date: 2020-09-27 13 | */ 14 | @Configuration 15 | public class RedisSerializeConfig { 16 | 17 | /** 18 | * 使用JSON序列化方式 19 | * 20 | * @param redisConnectionFactory 21 | * @return 22 | */ 23 | @Bean 24 | public RedisTemplate jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) { 25 | RedisTemplate template = new RedisTemplate<>(); 26 | template.setConnectionFactory(redisConnectionFactory); 27 | Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); 28 | template.setDefaultSerializer(serializer); 29 | return template; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Naccl (https://naccl.top/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/ExceptionLog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 异常日志 12 | * @Author: Naccl 13 | * @Date: 2020-12-03 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class ExceptionLog { 20 | private Long id; 21 | private String uri;//请求接口 22 | private String method;//请求方式 23 | private String param;//请求参数 24 | private String description;//操作描述 25 | private String error;//异常信息 26 | private String ip;//ip 27 | private String ipSource;//ip来源 28 | private String os;//操作系统 29 | private String browser;//浏览器 30 | private Date createTime;//操作时间 31 | private String userAgent; 32 | 33 | public ExceptionLog(String uri, String method, String description, String error, String ip, String userAgent) { 34 | this.uri = uri; 35 | this.method = method; 36 | this.description = description; 37 | this.error = error; 38 | this.ip = ip; 39 | this.createTime = new Date(); 40 | this.userAgent = userAgent; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/admin/AccountAdminController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller.admin; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.PostMapping; 5 | import org.springframework.web.bind.annotation.RequestBody; 6 | import org.springframework.web.bind.annotation.RequestHeader; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import top.naccl.entity.User; 10 | import top.naccl.model.vo.Result; 11 | import top.naccl.service.UserService; 12 | 13 | /** 14 | * @Description: 账号后台管理 15 | * @Author: Naccl 16 | * @Date: 2023-01-31 17 | */ 18 | @RestController 19 | @RequestMapping("/admin") 20 | public class AccountAdminController { 21 | @Autowired 22 | UserService userService; 23 | 24 | /** 25 | * 账号密码修改 26 | */ 27 | @PostMapping("/account") 28 | public Result account(@RequestBody User user, @RequestHeader(value = "Authorization", defaultValue = "") String jwt) { 29 | boolean res = userService.changeAccount(user, jwt); 30 | return res ? Result.ok("修改成功") : Result.error("修改失败"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import top.naccl.model.vo.BlogIdAndTitle; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Date; 11 | import java.util.List; 12 | 13 | /** 14 | * @Description: 博客评论 15 | * @Author: Naccl 16 | * @Date: 2020-07-27 17 | */ 18 | @NoArgsConstructor 19 | @Getter 20 | @Setter 21 | @ToString 22 | public class Comment { 23 | private Long id; 24 | private String nickname;//昵称 25 | private String email;//邮箱 26 | private String content;//评论内容 27 | private String avatar;//头像(图片路径) 28 | private Date createTime;//评论时间 29 | private String website;//个人网站 30 | private String ip;//评论者ip地址 31 | private Boolean published;//公开或回收站 32 | private Boolean adminComment;//博主回复 33 | private Integer page;//0普通文章,1关于我页面 34 | private Boolean notice;//接收邮件提醒 35 | private Long parentCommentId;//父评论id 36 | private String qq;//如果评论昵称为QQ号,则将昵称和头像置为QQ昵称和QQ头像,并将此字段置为QQ号备份 37 | 38 | private BlogIdAndTitle blog;//所属的文章 39 | private List replyComments = new ArrayList<>();//回复该评论的评论 40 | } 41 | -------------------------------------------------------------------------------- /blog-api/cfworker-tg-api-open.js: -------------------------------------------------------------------------------- 1 | addEventListener("fetch", event => { 2 | let response = handleRequest(event.request) 3 | return event.respondWith(response) 4 | }) 5 | 6 | async function handleRequest(request) { 7 | const { method, headers } = request 8 | 9 | if (method === "POST" && headers.get("content-type") === "application/json") { 10 | const json = await request.json() 11 | const toUrl = json.to 12 | console.log(toUrl) 13 | const data = json.data 14 | let response = await fetch(toUrl, { 15 | method: "POST", 16 | headers, 17 | body: JSON.stringify(data) 18 | }) 19 | const results = await gatherResponse(response) 20 | return new Response(results, { 21 | headers: { 22 | "content-type": "application/json;charset=UTF-8", 23 | }, 24 | }) 25 | } 26 | return new Response("error", { status: 403 }) 27 | } 28 | 29 | async function gatherResponse(response) { 30 | const { headers } = response 31 | const contentType = headers.get("content-type") || "" 32 | if (contentType.includes("application/json")) { 33 | return JSON.stringify(await response.json()) 34 | } else { 35 | return response.text() 36 | } 37 | } -------------------------------------------------------------------------------- /blog-cms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-cms", 3 | "version": "2.0.0", 4 | "description": "NBlog cms", 5 | "author": "Naccl ", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build" 10 | }, 11 | "dependencies": { 12 | "axios": "0.24.0", 13 | "core-js": "3.6.5", 14 | "cos-js-sdk-v5": "^1.4.10", 15 | "echarts": "4.9.0", 16 | "element-ui": "2.13.2", 17 | "lodash": "4.17.19", 18 | "mavon-editor": "2.9.1", 19 | "moment": "2.27.0", 20 | "normalize.css": "7.0.0", 21 | "nprogress": "0.2.0", 22 | "v-viewer": "1.5.1", 23 | "vue": "2.6.11", 24 | "vue-router": "3.3.4", 25 | "vuex": "3.5.1" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "4.5.0", 29 | "@vue/cli-plugin-router": "4.5.0", 30 | "@vue/cli-plugin-vuex": "4.5.0", 31 | "@vue/cli-service": "4.5.0", 32 | "sass": "1.26.8", 33 | "sass-loader": "8.0.2", 34 | "svg-sprite-loader": "4.1.3", 35 | "svgo": "1.2.2", 36 | "vue-template-compiler": "2.6.11" 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "last 2 versions", 41 | "not dead" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/HashUtils.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util; 2 | 3 | import cn.hutool.core.lang.hash.MurmurHash; 4 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 5 | import org.springframework.util.DigestUtils; 6 | 7 | /** 8 | * @Description: Hash工具类 9 | * @Author: Naccl 10 | * @Date: 2020-11-17 11 | */ 12 | public class HashUtils { 13 | private static final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); 14 | 15 | public static String getMd5(CharSequence str) { 16 | return DigestUtils.md5DigestAsHex(str.toString().getBytes()); 17 | } 18 | 19 | public static long getMurmurHash32(String str) { 20 | int i = MurmurHash.hash32(str); 21 | long num = i < 0 ? Integer.MAX_VALUE - (long) i : i; 22 | return num; 23 | } 24 | 25 | public static String getBC(CharSequence rawPassword) { 26 | return bCryptPasswordEncoder.encode(rawPassword); 27 | } 28 | 29 | public static boolean matchBC(CharSequence rawPassword, String encodedPassword) { 30 | return bCryptPasswordEncoder.matches(rawPassword, encodedPassword); 31 | } 32 | 33 | public static void main(String[] args) { 34 | System.out.println(getBC("123456")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/Blog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | /** 13 | * @Description: 博客文章 14 | * @Author: Naccl 15 | * @Date: 2020-07-26 16 | */ 17 | @NoArgsConstructor 18 | @Getter 19 | @Setter 20 | @ToString 21 | public class Blog { 22 | private Long id; 23 | private String title;//文章标题 24 | private String firstPicture;//文章首图,用于随机文章展示 25 | private String content;//文章正文 26 | private String description;//描述 27 | private Boolean published;//公开或私密 28 | private Boolean recommend;//推荐开关 29 | private Boolean appreciation;//赞赏开关 30 | private Boolean commentEnabled;//评论开关 31 | private Boolean top;//是否置顶 32 | private Date createTime;//创建时间 33 | private Date updateTime;//更新时间 34 | private Integer views;//浏览次数 35 | private Integer words;//文章字数 36 | private Integer readTime;//阅读时长(分钟) 37 | private String password;//密码保护 38 | 39 | private User user;//文章作者(因为是个人博客,也可以不加作者字段,暂且加上) 40 | private Category category;//文章分类 41 | private List tags = new ArrayList<>();//文章标签 42 | } 43 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/MyAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | import top.naccl.model.vo.Result; 7 | import top.naccl.util.JacksonUtils; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | 15 | /** 16 | * @Description: 未登录 拒绝访问 17 | * @Author: Naccl 18 | * @Date: 2020-07-21 19 | */ 20 | @Component 21 | public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { 22 | @Override 23 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) 24 | throws IOException, ServletException { 25 | response.setContentType("application/json;charset=utf-8"); 26 | PrintWriter out = response.getWriter(); 27 | Result result = Result.create(403, "请登录"); 28 | out.write(JacksonUtils.writeValueAsString(result)); 29 | out.flush(); 30 | out.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/UserAgentUtils.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util; 2 | 3 | import nl.basjes.parse.useragent.UserAgent; 4 | import nl.basjes.parse.useragent.UserAgentAnalyzer; 5 | import org.springframework.stereotype.Component; 6 | import top.naccl.model.dto.UserAgentDTO; 7 | 8 | /** 9 | * @Description: UserAgent解析工具类 10 | * @Author: Naccl 11 | * @Date: 2020-11-30 12 | */ 13 | @Component 14 | public class UserAgentUtils { 15 | private UserAgentAnalyzer uaa; 16 | 17 | public UserAgentUtils() { 18 | this.uaa = UserAgentAnalyzer 19 | .newBuilder() 20 | .useJava8CompatibleCaching() 21 | .withCache(10000) 22 | .hideMatcherLoadStats() 23 | .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION_MAJOR) 24 | .withField(UserAgent.AGENT_NAME_VERSION) 25 | .build(); 26 | } 27 | 28 | /** 29 | * 从User-Agent解析客户端操作系统和浏览器版本 30 | * 31 | * @param userAgent 32 | * @return 33 | */ 34 | public UserAgentDTO parseOsAndBrowser(String userAgent) { 35 | UserAgent agent = uaa.parse(userAgent); 36 | String os = agent.getValue(UserAgent.OPERATING_SYSTEM_NAME_VERSION_MAJOR); 37 | String browser = agent.getValue(UserAgent.AGENT_NAME_VERSION); 38 | return new UserAgentDTO(os, browser); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /blog-view/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | NBlog 9 | 10 | 11 | 12 | 13 | 14 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue. 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /blog-view/src/components/comment/CommentList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 47 | 48 | -------------------------------------------------------------------------------- /blog-cms/src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | @import './mixin'; 3 | @import './transition'; 4 | @import './element-ui'; 5 | @import './sidebar'; 6 | @import './base.css'; 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | text-rendering: optimizeLegibility; 13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 14 | } 15 | 16 | label { 17 | font-weight: 700; 18 | } 19 | 20 | html { 21 | height: 100%; 22 | box-sizing: border-box; 23 | } 24 | 25 | #app { 26 | height: 100%; 27 | } 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | a:focus, 36 | a:active { 37 | outline: none; 38 | } 39 | 40 | a, 41 | a:focus, 42 | a:hover { 43 | cursor: pointer; 44 | color: inherit; 45 | text-decoration: none; 46 | } 47 | 48 | div:focus { 49 | outline: none; 50 | } 51 | 52 | .clearfix { 53 | &:after { 54 | visibility: hidden; 55 | display: block; 56 | font-size: 0; 57 | content: " "; 58 | clear: both; 59 | height: 0; 60 | } 61 | } 62 | 63 | // main-container global css 64 | .app-container { 65 | padding: 20px; 66 | } 67 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/pinglun-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/OperationLog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 操作日志 12 | * @Author: Naccl 13 | * @Date: 2020-11-30 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class OperationLog { 20 | private Long id; 21 | private String username;//操作者用户名 22 | private String uri;//请求接口 23 | private String method;//请求方式 24 | private String param;//请求参数 25 | private String description;//操作描述 26 | private String ip;//ip 27 | private String ipSource;//ip来源 28 | private String os;//操作系统 29 | private String browser;//浏览器 30 | private Integer times;//请求耗时(毫秒) 31 | private Date createTime;//操作时间 32 | private String userAgent; 33 | 34 | public OperationLog(String username, String uri, String method, String description, String ip, Integer times, String userAgent) { 35 | this.username = username; 36 | this.uri = uri; 37 | this.method = method; 38 | this.description = description; 39 | this.ip = ip; 40 | this.times = times; 41 | this.createTime = new Date(); 42 | this.userAgent = userAgent; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverTextContentNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.cover.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.text.TextContentNodeRendererContext; 5 | import org.commonmark.renderer.text.TextContentWriter; 6 | 7 | /** 8 | * @Description: 文本节点渲染 9 | * @Author: Naccl 10 | * @Date: 2020-05-13 11 | */ 12 | public class CoverTextContentNodeRenderer extends AbstractCoverNodeRenderer { 13 | private final TextContentNodeRendererContext context; 14 | private final TextContentWriter textContent; 15 | 16 | public CoverTextContentNodeRenderer(TextContentNodeRendererContext context) { 17 | this.context = context; 18 | this.textContent = context.getWriter(); 19 | } 20 | 21 | @Override 22 | public void render(Node node) { 23 | textContent.write('/'); 24 | renderChildren(node); 25 | textContent.write('/'); 26 | } 27 | 28 | private void renderChildren(Node parent) { 29 | Node node = parent.getFirstChild(); 30 | while (node != null) { 31 | Node next = node.getNext(); 32 | context.render(node); 33 | node = next; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuTextContentNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.heimu.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.text.TextContentNodeRendererContext; 5 | import org.commonmark.renderer.text.TextContentWriter; 6 | 7 | /** 8 | * @Description: 文本节点渲染 9 | * @Author: Naccl 10 | * @Date: 2020-05-13 11 | */ 12 | public class HeimuTextContentNodeRenderer extends AbstractHeimuNodeRenderer { 13 | private final TextContentNodeRendererContext context; 14 | private final TextContentWriter textContent; 15 | 16 | public HeimuTextContentNodeRenderer(TextContentNodeRendererContext context) { 17 | this.context = context; 18 | this.textContent = context.getWriter(); 19 | } 20 | 21 | @Override 22 | public void render(Node node) { 23 | textContent.write('/'); 24 | renderChildren(node); 25 | textContent.write('/'); 26 | } 27 | 28 | private void renderChildren(Node parent) { 29 | Node node = parent.getFirstChild(); 30 | while (node != null) { 31 | Node next = node.getNext(); 32 | context.render(node); 33 | node = next; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/TagController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import top.naccl.annotation.VisitLogger; 8 | import top.naccl.enums.VisitBehavior; 9 | import top.naccl.model.vo.BlogInfo; 10 | import top.naccl.model.vo.PageResult; 11 | import top.naccl.model.vo.Result; 12 | import top.naccl.service.BlogService; 13 | 14 | /** 15 | * @Description: 标签 16 | * @Author: Naccl 17 | * @Date: 2020-08-17 18 | */ 19 | @RestController 20 | public class TagController { 21 | @Autowired 22 | BlogService blogService; 23 | 24 | /** 25 | * 根据标签name分页查询公开博客列表 26 | * 27 | * @param tagName 标签name 28 | * @param pageNum 页码 29 | * @return 30 | */ 31 | @VisitLogger(VisitBehavior.TAG) 32 | @GetMapping("/tag") 33 | public Result tag(@RequestParam String tagName, 34 | @RequestParam(defaultValue = "1") Integer pageNum) { 35 | PageResult pageResult = blogService.getBlogInfoListByTagNameAndIsPublished(tagName, pageNum); 36 | return Result.ok("请求成功", pageResult); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/mapper/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package top.naccl.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.stereotype.Repository; 5 | import top.naccl.entity.Comment; 6 | import top.naccl.model.vo.PageComment; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Description: 博客评论持久层接口 12 | * @Author: Naccl 13 | * @Date: 2020-08-03 14 | */ 15 | @Mapper 16 | @Repository 17 | public interface CommentMapper { 18 | List getListByPageAndParentCommentId(Integer page, Long blogId, Long parentCommentId); 19 | 20 | List getListByParentCommentId(Long parentCommentId); 21 | 22 | List getPageCommentListByPageAndParentCommentId(Integer page, Long blogId, Long parentCommentId); 23 | 24 | Comment getCommentById(Long id); 25 | 26 | int updateCommentPublishedById(Long commentId, Boolean published); 27 | 28 | int updateCommentNoticeById(Long commentId, Boolean notice); 29 | 30 | int deleteCommentById(Long commentId); 31 | 32 | int deleteCommentsByBlogId(Long blogId); 33 | 34 | int updateComment(Comment comment); 35 | 36 | int countByPageAndIsPublished(Integer page, Long blogId, Boolean isPublished); 37 | 38 | int countComment(); 39 | 40 | int saveComment(top.naccl.model.dto.Comment comment); 41 | } 42 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/quartz/ScheduleRunnable.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.quartz; 2 | 3 | import org.springframework.util.ReflectionUtils; 4 | import org.springframework.util.StringUtils; 5 | import top.naccl.util.common.SpringContextUtils; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * @Description: 执行定时任务 11 | * @Author: Naccl 12 | * @Date: 2020-11-01 13 | */ 14 | public class ScheduleRunnable implements Runnable { 15 | private Object target; 16 | private Method method; 17 | private String params; 18 | 19 | public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException { 20 | this.target = SpringContextUtils.getBean(beanName); 21 | this.params = params; 22 | if (StringUtils.hasText(params)) { 23 | this.method = target.getClass().getDeclaredMethod(methodName, String.class); 24 | } else { 25 | this.method = target.getClass().getDeclaredMethod(methodName); 26 | } 27 | } 28 | 29 | @Override 30 | public void run() { 31 | try { 32 | ReflectionUtils.makeAccessible(method); 33 | if (StringUtils.hasText(params)) { 34 | method.invoke(target, params); 35 | } else { 36 | method.invoke(target); 37 | } 38 | } catch (Exception e) { 39 | throw new RuntimeException("执行定时任务失败", e); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import top.naccl.annotation.VisitLogger; 8 | import top.naccl.enums.VisitBehavior; 9 | import top.naccl.model.vo.BlogInfo; 10 | import top.naccl.model.vo.PageResult; 11 | import top.naccl.model.vo.Result; 12 | import top.naccl.service.BlogService; 13 | 14 | /** 15 | * @Description: 分类 16 | * @Author: Naccl 17 | * @Date: 2020-08-19 18 | */ 19 | @RestController 20 | public class CategoryController { 21 | @Autowired 22 | BlogService blogService; 23 | 24 | /** 25 | * 根据分类name分页查询公开博客列表 26 | * 27 | * @param categoryName 分类name 28 | * @param pageNum 页码 29 | * @return 30 | */ 31 | @VisitLogger(VisitBehavior.CATEGORY) 32 | @GetMapping("/category") 33 | public Result category(@RequestParam String categoryName, 34 | @RequestParam(defaultValue = "1") Integer pageNum) { 35 | PageResult pageResult = blogService.getBlogInfoListByCategoryNameAndIsPublished(categoryName, pageNum); 36 | return Result.ok("请求成功", pageResult); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/heimu/internal/HeimuHtmlNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.heimu.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.html.HtmlNodeRendererContext; 5 | import org.commonmark.renderer.html.HtmlWriter; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Description: HTML节点渲染 12 | * @Author: Naccl 13 | * @Date: 2020-05-13 14 | */ 15 | public class HeimuHtmlNodeRenderer extends AbstractHeimuNodeRenderer { 16 | private final HtmlNodeRendererContext context; 17 | private final HtmlWriter html; 18 | 19 | public HeimuHtmlNodeRenderer(HtmlNodeRendererContext context) { 20 | this.context = context; 21 | this.html = context.getWriter(); 22 | } 23 | 24 | @Override 25 | public void render(Node node) { 26 | Map attributes = new HashMap<>(4); 27 | attributes.put("class", "m-text-heimu"); 28 | attributes.put("title", "你知道的太多了"); 29 | html.tag("span", context.extendAttributes(node, "span", attributes)); 30 | renderChildren(node); 31 | html.tag("/span"); 32 | } 33 | 34 | private void renderChildren(Node parent) { 35 | Node node = parent.getFirstChild(); 36 | while (node != null) { 37 | Node next = node.getNext(); 38 | context.render(node); 39 | node = next; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/entity/VisitLog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @Description: 访问日志 12 | * @Author: Naccl 13 | * @Date: 2020-12-04 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class VisitLog { 20 | private Long id; 21 | private String uuid;//访客标识码 22 | private String uri;//请求接口 23 | private String method;//请求方式 24 | private String param;//请求参数 25 | private String behavior;//访问行为 26 | private String content;//访问内容 27 | private String remark;//备注 28 | private String ip;//ip 29 | private String ipSource;//ip来源 30 | private String os;//操作系统 31 | private String browser;//浏览器 32 | private Integer times;//请求耗时(毫秒) 33 | private Date createTime;//访问时间 34 | private String userAgent; 35 | 36 | public VisitLog(String uuid, String uri, String method, String behavior, String content, String remark, String ip, Integer times, String userAgent) { 37 | this.uuid = uuid; 38 | this.uri = uri; 39 | this.method = method; 40 | this.behavior = behavior; 41 | this.content = content; 42 | this.remark = remark; 43 | this.ip = ip; 44 | this.times = times; 45 | this.createTime = new Date(); 46 | this.userAgent = userAgent; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /blog-cms/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/vo/Result.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * @Description: 封装响应结果 10 | * @Author: Naccl 11 | * @Date: 2020-07-19 12 | */ 13 | 14 | @NoArgsConstructor 15 | @Getter 16 | @Setter 17 | @ToString 18 | public class Result { 19 | private Integer code; 20 | private String msg; 21 | private Object data; 22 | 23 | private Result(Integer code, String msg) { 24 | this.code = code; 25 | this.msg = msg; 26 | this.data = null; 27 | } 28 | 29 | private Result(Integer code, String msg, Object data) { 30 | this.code = code; 31 | this.msg = msg; 32 | this.data = data; 33 | } 34 | 35 | public static Result ok(String msg, Object data) { 36 | return new Result(200, msg, data); 37 | } 38 | 39 | public static Result ok(String msg) { 40 | return new Result(200, msg); 41 | } 42 | 43 | public static Result error(String msg) { 44 | return new Result(500, msg); 45 | } 46 | 47 | public static Result error() { 48 | return new Result(500, "异常错误"); 49 | } 50 | 51 | public static Result create(Integer code, String msg, Object data) { 52 | return new Result(code, msg, data); 53 | } 54 | 55 | public static Result create(Integer code, String msg) { 56 | return new Result(code, msg); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/upload/channel/UpyunChannel.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.upload.channel; 2 | 3 | import com.upyun.RestManager; 4 | import okhttp3.Response; 5 | import org.springframework.context.annotation.Lazy; 6 | import org.springframework.stereotype.Component; 7 | import top.naccl.config.properties.UpyunProperties; 8 | import top.naccl.util.upload.UploadUtils; 9 | 10 | import java.util.UUID; 11 | 12 | /** 13 | * 又拍云存储上传方式 14 | * 15 | * @author: Naccl 16 | * @date: 2022-05-26 17 | */ 18 | @Lazy 19 | @Component 20 | public class UpyunChannel implements FileUploadChannel { 21 | private RestManager manager; 22 | private UpyunProperties upyunProperties; 23 | 24 | public UpyunChannel(UpyunProperties upyunProperties) { 25 | this.upyunProperties = upyunProperties; 26 | this.manager = new RestManager(upyunProperties.getBucketName(), upyunProperties.getUsername(), upyunProperties.getPassword()); 27 | } 28 | 29 | @Override 30 | public String upload(UploadUtils.ImageResource image) throws Exception { 31 | String fileAbsolutePath = upyunProperties.getPath() + "/" + UUID.randomUUID() + "." + image.getType(); 32 | Response response = manager.writeFile(fileAbsolutePath, image.getData(), null); 33 | if (!response.isSuccessful()) { 34 | throw new RuntimeException("又拍云上传失败"); 35 | } 36 | return upyunProperties.getDomain() + fileAbsolutePath; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/admin/AboutAdminController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller.admin; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PutMapping; 6 | import org.springframework.web.bind.annotation.RequestBody; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import top.naccl.annotation.OperationLogger; 10 | import top.naccl.model.vo.Result; 11 | import top.naccl.service.AboutService; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * @Description: 关于我页面后台管理 17 | * @Author: Naccl 18 | * @Date: 2020-09-01 19 | */ 20 | @RestController 21 | @RequestMapping("/admin") 22 | public class AboutAdminController { 23 | @Autowired 24 | AboutService aboutService; 25 | 26 | /** 27 | * 获取关于我页面配置 28 | * 29 | * @return 30 | */ 31 | @GetMapping("/about") 32 | public Result about() { 33 | return Result.ok("请求成功", aboutService.getAboutSetting()); 34 | } 35 | 36 | /** 37 | * 修改关于我页面 38 | * 39 | * @param map 40 | * @return 41 | */ 42 | @OperationLogger("修改关于我页面") 43 | @PutMapping("/about") 44 | public Result updateAbout(@RequestBody Map map) { 45 | aboutService.updateAbout(map); 46 | return Result.ok("修改成功"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/markdown/ext/cover/internal/CoverHtmlNodeRenderer.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.markdown.ext.cover.internal; 2 | 3 | import org.commonmark.node.Node; 4 | import org.commonmark.renderer.html.HtmlNodeRendererContext; 5 | import org.commonmark.renderer.html.HtmlWriter; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Description: HTML节点渲染 12 | * @Author: Naccl 13 | * @Date: 2020-05-13 14 | */ 15 | public class CoverHtmlNodeRenderer extends AbstractCoverNodeRenderer { 16 | private final HtmlNodeRendererContext context; 17 | private final HtmlWriter html; 18 | 19 | public CoverHtmlNodeRenderer(HtmlNodeRendererContext context) { 20 | this.context = context; 21 | this.html = context.getWriter(); 22 | } 23 | 24 | @Override 25 | public void render(Node node) { 26 | Map attributes = new HashMap<>(2); 27 | attributes.put("class", "m-text-cover"); 28 | html.tag("span", context.extendAttributes(node, "span", attributes)); 29 | renderChildren(node); 30 | html.tag("/span"); 31 | } 32 | 33 | private void renderChildren(Node parent) { 34 | Node node = parent.getFirstChild(); 35 | while (node != null) { 36 | Node next = node.getNext(); 37 | context.render(node); 38 | node = next; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blog-cms/src/api/schedule.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getJobList(queryInfo) { 4 | return axios({ 5 | url: 'jobs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function updateJobStatus(jobId, status) { 14 | return axios({ 15 | url: 'job/status', 16 | method: 'PUT', 17 | params: { 18 | jobId, 19 | status 20 | } 21 | }) 22 | } 23 | 24 | export function runJobOnce(jobId) { 25 | return axios({ 26 | url: 'job/run', 27 | method: 'POST', 28 | params: { 29 | jobId 30 | } 31 | }) 32 | } 33 | 34 | export function deleteJobById(jobId) { 35 | return axios({ 36 | url: 'job', 37 | method: 'DELETE', 38 | params: { 39 | jobId 40 | } 41 | }) 42 | } 43 | 44 | export function addJob(job) { 45 | return axios({ 46 | url: 'job', 47 | method: 'POST', 48 | data: { 49 | ...job 50 | } 51 | }) 52 | } 53 | 54 | export function editJob(job) { 55 | return axios({ 56 | url: 'job', 57 | method: 'PUT', 58 | data: { 59 | ...job 60 | } 61 | }) 62 | } 63 | 64 | export function getJobLogList(queryInfo) { 65 | return axios({ 66 | url: 'job/logs', 67 | method: 'GET', 68 | params: { 69 | ...queryInfo 70 | } 71 | }) 72 | } 73 | 74 | export function deleteJobLogByLogId(logId) { 75 | return axios({ 76 | url: 'job/log', 77 | method: 'DELETE', 78 | params: { 79 | logId 80 | } 81 | }) 82 | } -------------------------------------------------------------------------------- /blog-cms/src/api/friend.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getFriendsByQuery(queryInfo) { 4 | return axios({ 5 | url: 'friends', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function updatePublished(id, published) { 14 | return axios({ 15 | url: 'friend/published', 16 | method: 'PUT', 17 | params: { 18 | id, 19 | published 20 | } 21 | }) 22 | } 23 | 24 | export function saveFriend(form) { 25 | return axios({ 26 | url: 'friend', 27 | method: 'POST', 28 | data: { 29 | ...form 30 | } 31 | }) 32 | } 33 | 34 | export function updateFriend(form) { 35 | return axios({ 36 | url: 'friend', 37 | method: 'PUT', 38 | data: { 39 | ...form 40 | } 41 | }) 42 | } 43 | 44 | export function deleteFriendById(id) { 45 | return axios({ 46 | url: 'friend', 47 | method: 'DELETE', 48 | params: { 49 | id 50 | } 51 | }) 52 | } 53 | 54 | export function getFriendInfo() { 55 | return axios({ 56 | url: 'friendInfo', 57 | method: 'GET' 58 | }) 59 | } 60 | 61 | export function updateCommentEnabled(commentEnabled) { 62 | return axios({ 63 | url: 'friendInfo/commentEnabled', 64 | method: 'PUT', 65 | params: { 66 | commentEnabled 67 | } 68 | }) 69 | } 70 | 71 | export function updateContent(content) { 72 | return axios({ 73 | url: 'friendInfo/content', 74 | method: 'PUT', 75 | data: { 76 | content 77 | } 78 | }) 79 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package top.naccl.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 7 | import org.springframework.web.client.RestTemplate; 8 | import top.naccl.config.properties.ProxyProperties; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.net.Proxy; 12 | 13 | /** 14 | * RestTemplate相关的Bean配置 15 | * 16 | * @author: Naccl 17 | * @date: 2022-01-22 18 | */ 19 | @Configuration 20 | public class RestTemplateConfig { 21 | @Autowired 22 | private ProxyProperties proxyProperties; 23 | 24 | /** 25 | * 默认的RestTemplate 26 | * 27 | * @return 28 | */ 29 | @Bean 30 | public RestTemplate restTemplate() { 31 | return new RestTemplate(); 32 | } 33 | 34 | /** 35 | * 配置了代理和超时时间的RestTemplate 36 | * 37 | * @return 38 | */ 39 | @Bean 40 | public RestTemplate restTemplateByProxy() { 41 | SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); 42 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyProperties.getHost(), proxyProperties.getPort())); 43 | requestFactory.setProxy(proxy); 44 | requestFactory.setConnectTimeout(proxyProperties.getTimeout()); 45 | return new RestTemplate(requestFactory); 46 | } 47 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/constant/RedisKeyConstants.java: -------------------------------------------------------------------------------- 1 | package top.naccl.constant; 2 | 3 | /** 4 | * @Description: Redis key配置 5 | * @Author: Naccl 6 | * @Date: 2020-09-27 7 | */ 8 | public class RedisKeyConstants { 9 | /** 10 | * 首页博客简介列表 分页对象key 11 | * homeBlogInfoList : {{1,"第一页的缓存"},{2,"第二页的缓存"}} 12 | */ 13 | public static final String HOME_BLOG_INFO_LIST = "homeBlogInfoList"; 14 | /** 15 | * 分类名列表key 16 | */ 17 | public static final String CATEGORY_NAME_LIST = "categoryNameList"; 18 | /** 19 | * 标签云列表key 20 | */ 21 | public static final String TAG_CLOUD_LIST = "tagCloudList"; 22 | /** 23 | * 站点信息key 24 | */ 25 | public static final String SITE_INFO_MAP = "siteInfoMap"; 26 | /** 27 | * 最新推荐博客key 28 | */ 29 | public static final String NEW_BLOG_LIST = "newBlogList"; 30 | /** 31 | * 关于我页面key 32 | */ 33 | public static final String ABOUT_INFO_MAP = "aboutInfoMap"; 34 | /** 35 | * 友链页面信息key 36 | */ 37 | public static final String FRIEND_INFO_MAP = "friendInfoMap"; 38 | /** 39 | * 博客归档key 40 | */ 41 | public static final String ARCHIVE_BLOG_MAP = "archiveBlogMap"; 42 | /** 43 | * 博客访问量key 44 | */ 45 | public static final String BLOG_VIEWS_MAP = "blogViewsMap"; 46 | /** 47 | * 访客标识码key 48 | */ 49 | public static final String IDENTIFICATION_SET = "identificationSet"; 50 | /** 51 | * QQ号与对应头像URL key 52 | */ 53 | public static final String QQ_AVATAR_URL_MAP = "qqAvatarUrlMap"; 54 | } 55 | -------------------------------------------------------------------------------- /blog-view/src/views/tag/Tag.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 标签 {{ tagName }} 下的文章 5 | 6 | 7 | 8 | 9 | 10 | 58 | 59 | -------------------------------------------------------------------------------- /blog-cms/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/TgMessage.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * Telegram新消息 11 | * 12 | * @author: Naccl 13 | * @date: 2022-01-24 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class TgMessage { 20 | @JsonProperty("update_id") 21 | private String updateId; 22 | private Message message; 23 | 24 | @NoArgsConstructor 25 | @Getter 26 | @Setter 27 | @ToString 28 | public class Message { 29 | @JsonProperty("message_id") 30 | private String messageId; 31 | private From from; 32 | private Chat chat; 33 | private String date; 34 | private String text; 35 | 36 | @NoArgsConstructor 37 | @Getter 38 | @Setter 39 | @ToString 40 | public class From { 41 | private String id; 42 | @JsonProperty("is_bot") 43 | private Boolean isBot; 44 | @JsonProperty("first_name") 45 | private String firstName; 46 | private String username; 47 | @JsonProperty("language_code") 48 | private String languageCode; 49 | } 50 | 51 | @NoArgsConstructor 52 | @Getter 53 | @Setter 54 | @ToString 55 | public class Chat { 56 | private String id; 57 | @JsonProperty("first_name") 58 | private String firstName; 59 | private String username; 60 | private String type; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/service/RedisService.java: -------------------------------------------------------------------------------- 1 | package top.naccl.service; 2 | 3 | import top.naccl.model.vo.BlogInfo; 4 | import top.naccl.model.vo.PageResult; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface RedisService { 10 | PageResult getBlogInfoPageResultByHash(String hash, Integer pageNum); 11 | 12 | void saveKVToHash(String hash, Object key, Object value); 13 | 14 | void saveMapToHash(String hash, Map map); 15 | 16 | Map getMapByHash(String hash); 17 | 18 | Object getValueByHashKey(String hash, Object key); 19 | 20 | void incrementByHashKey(String hash, Object key, int increment); 21 | 22 | void deleteByHashKey(String hash, Object key); 23 | 24 | List getListByValue(String key); 25 | 26 | void saveListToValue(String key, List list); 27 | 28 | Map getMapByValue(String key); 29 | 30 | void saveMapToValue(String key, Map map); 31 | 32 | T getObjectByValue(String key, Class t); 33 | 34 | void incrementByKey(String key, int increment); 35 | 36 | void saveObjectToValue(String key, Object object); 37 | 38 | void saveValueToSet(String key, Object value); 39 | 40 | int countBySet(String key); 41 | 42 | void deleteValueBySet(String key, Object value); 43 | 44 | boolean hasValueInSet(String key, Object value); 45 | 46 | void deleteCacheByKey(String key); 47 | 48 | boolean hasKey(String key); 49 | 50 | void expire(String key, long time); 51 | } 52 | -------------------------------------------------------------------------------- /blog-api/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ${FILE_LOG_PATTERN} 12 | 13 | 14 | 15 | 16 | ${LOG_FILE}-%d{yyyy-MM-dd}-%i.log 17 | 365 18 | 19 | 20 | 10MB 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/model/dto/Blog.java: -------------------------------------------------------------------------------- 1 | package top.naccl.model.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import top.naccl.entity.Category; 8 | import top.naccl.entity.Tag; 9 | import top.naccl.entity.User; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | /** 16 | * @Description: 博客DTO 17 | * @Author: Naccl 18 | * @Date: 2020-08-27 19 | */ 20 | @NoArgsConstructor 21 | @Getter 22 | @Setter 23 | @ToString 24 | public class Blog { 25 | private Long id; 26 | private String title;//文章标题 27 | private String firstPicture;//文章首图,用于随机文章展示 28 | private String content;//文章正文 29 | private String description;//描述 30 | private Boolean published;//公开或私密 31 | private Boolean recommend;//推荐开关 32 | private Boolean appreciation;//赞赏开关 33 | private Boolean commentEnabled;//评论开关 34 | private Boolean top;//是否置顶 35 | private Date createTime;//创建时间 36 | private Date updateTime;//更新时间 37 | private Integer views;//浏览次数 38 | private Integer words;//文章字数 39 | private Integer readTime;//阅读时长(分钟) 40 | private String password;//密码保护 41 | 42 | private User user;//文章作者(因为是个人博客,也可以不加作者字段,暂且加上) 43 | private Category category;//文章分类 44 | private List tags = new ArrayList<>();//文章标签 45 | 46 | private Object cate;//页面展示层传输的分类对象:正常情况下为 字符串 或 分类id 47 | private List tagList;//页面展示层传输的标签对象:正常情况下为 List标签id 或 List标签名 48 | } 49 | -------------------------------------------------------------------------------- /blog-cms/src/util/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | import {Message} from 'element-ui' 5 | 6 | const request = axios.create({ 7 | baseURL: 'http://localhost:8090/admin/', 8 | timeout: 5000 9 | }) 10 | 11 | let CancelToken = axios.CancelToken 12 | 13 | // 请求拦截 14 | request.interceptors.request.use(config => { 15 | //对于访客模式,除GET请求外,都拦截并提示 16 | const userJson = window.localStorage.getItem('user') || '{}' 17 | const user = JSON.parse(userJson) 18 | if (userJson !== '{}' && user.role !== 'ROLE_admin' && config.method !== 'get') { 19 | config.cancelToken = new CancelToken(function executor(cancel) { 20 | cancel('演示模式,不允许操作') 21 | }) 22 | return config 23 | } 24 | 25 | NProgress.start() 26 | const token = window.localStorage.getItem('token') 27 | if (token) { 28 | config.headers.Authorization = token 29 | } 30 | return config 31 | }, 32 | error => { 33 | console.info(error) 34 | return Promise.reject(error) 35 | } 36 | ) 37 | 38 | // 响应拦截 39 | request.interceptors.response.use(response => { 40 | NProgress.done() 41 | const res = response.data 42 | if (res.code !== 200) { 43 | let msg = res.msg || 'Error' 44 | Message.error(msg) 45 | return Promise.reject(new Error(msg)) 46 | } 47 | return res 48 | }, 49 | error => { 50 | console.info(error) 51 | Message.error(error.message) 52 | return Promise.reject(error) 53 | } 54 | ) 55 | 56 | export default request -------------------------------------------------------------------------------- /blog-view/src/views/category/Category.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 分类 {{ categoryName }} 下的文章 5 | 6 | 7 | 8 | 9 | 10 | 58 | 59 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/article.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/upload/channel/LocalChannel.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.upload.channel; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Lazy; 5 | import org.springframework.stereotype.Component; 6 | import top.naccl.config.properties.BlogProperties; 7 | import top.naccl.config.properties.UploadProperties; 8 | import top.naccl.util.upload.UploadUtils; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.util.UUID; 13 | 14 | /** 15 | * 本地存储方式 16 | * 17 | * @author: Naccl 18 | * @date: 2022-01-23 19 | */ 20 | @Lazy 21 | @Component 22 | public class LocalChannel implements FileUploadChannel { 23 | @Autowired 24 | private BlogProperties blogProperties; 25 | @Autowired 26 | private UploadProperties uploadProperties; 27 | 28 | /** 29 | * 将图片保存到本地,并返回访问本地图片的URL 30 | * 31 | * @param image 需要保存的图片 32 | * @return 访问图片的URL 33 | * @throws Exception 34 | */ 35 | @Override 36 | public String upload(UploadUtils.ImageResource image) throws Exception { 37 | File folder = new File(uploadProperties.getPath()); 38 | if (!folder.exists()) { 39 | folder.mkdirs(); 40 | } 41 | String fileName = UUID.randomUUID() + "." + image.getType(); 42 | FileOutputStream fileOutputStream = new FileOutputStream(uploadProperties.getPath() + fileName); 43 | fileOutputStream.write(image.getData()); 44 | fileOutputStream.close(); 45 | return blogProperties.getApi() + "/image/" + fileName; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package top.naccl.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.PostMapping; 5 | import org.springframework.web.bind.annotation.RequestBody; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import top.naccl.constant.JwtConstants; 8 | import top.naccl.entity.User; 9 | import top.naccl.model.dto.LoginInfo; 10 | import top.naccl.model.vo.Result; 11 | import top.naccl.service.UserService; 12 | import top.naccl.util.JwtUtils; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * @Description: 前台登录 19 | * @Author: Naccl 20 | * @Date: 2020-09-02 21 | */ 22 | @RestController 23 | public class LoginController { 24 | @Autowired 25 | UserService userService; 26 | 27 | /** 28 | * 登录成功后,签发博主身份Token 29 | * 30 | * @param loginInfo 31 | * @return 32 | */ 33 | @PostMapping("/login") 34 | public Result login(@RequestBody LoginInfo loginInfo) { 35 | User user = userService.findUserByUsernameAndPassword(loginInfo.getUsername(), loginInfo.getPassword()); 36 | if (!"ROLE_admin".equals(user.getRole())) { 37 | return Result.create(403, "无权限"); 38 | } 39 | user.setPassword(null); 40 | String jwt = JwtUtils.generateToken(JwtConstants.ADMIN_PREFIX + user.getUsername()); 41 | Map map = new HashMap<>(4); 42 | map.put("user", user); 43 | map.put("token", jwt); 44 | return Result.ok("登录成功", map); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /blog-cms/src/icons/svg/friend.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog-view/src/views/about/About.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ about.title }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 评论已关闭 12 | 13 | 14 | 15 | 16 | 51 | 52 | 57 | -------------------------------------------------------------------------------- /blog-cms/src/api/blog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/util/request' 2 | 3 | export function getDataByQuery(queryInfo) { 4 | return axios({ 5 | url: 'blogs', 6 | method: 'GET', 7 | params: { 8 | ...queryInfo 9 | } 10 | }) 11 | } 12 | 13 | export function deleteBlogById(id) { 14 | return axios({ 15 | url: 'blog', 16 | method: 'DELETE', 17 | params: { 18 | id 19 | } 20 | }) 21 | } 22 | 23 | export function getCategoryAndTag() { 24 | return axios({ 25 | url: 'categoryAndTag', 26 | method: 'GET' 27 | }) 28 | } 29 | 30 | export function saveBlog(blog) { 31 | return axios({ 32 | url: 'blog', 33 | method: 'POST', 34 | data: { 35 | ...blog 36 | } 37 | }) 38 | } 39 | 40 | export function updateTop(id, top) { 41 | return axios({ 42 | url: 'blog/top', 43 | method: 'PUT', 44 | params: { 45 | id, 46 | top 47 | } 48 | }) 49 | } 50 | 51 | export function updateRecommend(id, recommend) { 52 | return axios({ 53 | url: 'blog/recommend', 54 | method: 'PUT', 55 | params: { 56 | id, 57 | recommend 58 | } 59 | }) 60 | } 61 | 62 | export function updateVisibility(id, form) { 63 | return axios({ 64 | url: `blog/${id}/visibility`, 65 | method: 'PUT', 66 | data: { 67 | ...form 68 | } 69 | }) 70 | } 71 | 72 | export function getBlogById(id) { 73 | return axios({ 74 | url: 'blog', 75 | method: 'GET', 76 | params: { 77 | id 78 | } 79 | }) 80 | } 81 | 82 | export function updateBlog(blog) { 83 | return axios({ 84 | url: 'blog', 85 | method: 'PUT', 86 | data: { 87 | ...blog 88 | } 89 | }) 90 | } -------------------------------------------------------------------------------- /blog-api/src/main/java/top/naccl/util/common/SpringContextUtils.java: -------------------------------------------------------------------------------- 1 | package top.naccl.util.common; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @Description: 10 | * @Author: Naccl 11 | * @Date: 2020-11-01 12 | */ 13 | @Component 14 | public class SpringContextUtils implements ApplicationContextAware { 15 | public static ApplicationContext applicationContext; 16 | 17 | @Override 18 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 19 | SpringContextUtils.applicationContext = applicationContext; 20 | } 21 | 22 | public static Object getBean(String name) { 23 | return applicationContext.getBean(name); 24 | } 25 | 26 | public static T getBean(String name, Class requiredType) { 27 | return applicationContext.getBean(name, requiredType); 28 | } 29 | 30 | public static boolean containsBean(String name) { 31 | return applicationContext.containsBean(name); 32 | } 33 | 34 | public static boolean isSingleton(String name) { 35 | return applicationContext.isSingleton(name); 36 | } 37 | 38 | public static Class extends Object> getType(String name) { 39 | return applicationContext.getType(name); 40 | } 41 | 42 | /** 43 | * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. 44 | */ 45 | public static T getBean(Class requiredType) { 46 | return applicationContext.getBean(requiredType); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /blog-cms/src/views/system/Account.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 修改当前登录账户 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 确认修改 16 | 17 | 18 | 19 | 20 | 21 | 22 | 55 | 56 | 61 | --------------------------------------------------------------------------------