From 16cc30f513bb5e63c21bc59a5866dd9b08cbf10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=B5=B7?= Date: Thu, 28 Nov 2019 19:18:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8E"Blog"=E4=BB=93=E5=BA=93=E4=B8=AD?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + .mvn/wrapper/MavenWrapperDownloader.java | 114 ++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 48337 bytes .mvn/wrapper/maven-wrapper.properties | 1 + LICENSE | 21 + README.md | 37 + blog.iml | 149 ++ blog.sql | 92 ++ mvnw | 286 ++++ mvnw.cmd | 161 ++ pom.xml | 162 ++ .../java/cn/celess/blog/BlogApplication.java | 20 + .../celess/blog/configuration/CorsConfig.java | 38 + .../blog/configuration/DruidConfig.java | 41 + .../blog/configuration/InterceptorConfig.java | 41 + .../blog/configuration/RedisConfig.java | 72 + .../blog/configuration/SwaggerConfig.java | 45 + .../filter/AuthenticationFilter.java | 83 + .../filter/MultipleSubmitFilter.java | 44 + .../configuration/filter/VisitorRecord.java | 34 + .../listener/SessionListener.java | 50 + .../blog/controller/ArticleController.java | 154 ++ .../blog/controller/CategoryController.java | 63 + .../blog/controller/CommentController.java | 101 ++ .../blog/controller/LinksController.java | 99 ++ .../java/cn/celess/blog/controller/Other.java | 172 ++ .../celess/blog/controller/TagController.java | 70 + .../blog/controller/UserController.java | 122 ++ .../blog/controller/VisitorController.java | 60 + .../controller/WebUpdateInfoController.java | 48 + .../java/cn/celess/blog/enmu/LevelEnum.java | 27 + .../cn/celess/blog/enmu/ResponseEnum.java | 81 + .../java/cn/celess/blog/entity/Article.java | 61 + .../java/cn/celess/blog/entity/Category.java | 20 + .../java/cn/celess/blog/entity/Comment.java | 40 + .../cn/celess/blog/entity/PartnerSite.java | 30 + .../java/cn/celess/blog/entity/Response.java | 34 + src/main/java/cn/celess/blog/entity/Tag.java | 16 + src/main/java/cn/celess/blog/entity/User.java | 60 + .../java/cn/celess/blog/entity/Visitor.java | 27 + .../java/cn/celess/blog/entity/WebUpdate.java | 27 + .../blog/entity/model/ArticleModel.java | 96 ++ .../blog/entity/model/CommentModel.java | 57 + .../blog/entity/model/QiniuResponse.java | 13 + .../celess/blog/entity/model/UserModel.java | 40 + .../blog/entity/model/VisitorModel.java | 24 + .../blog/entity/model/WebUpdateModel.java | 24 + .../blog/entity/request/ArticleReq.java | 19 + .../blog/entity/request/CommentReq.java | 17 + .../celess/blog/entity/request/LinkReq.java | 15 + .../celess/blog/entity/request/LoginReq.java | 19 + .../celess/blog/entity/request/UserReq.java | 26 + .../blog/exception/ExceptionHandle.java | 95 ++ .../cn/celess/blog/exception/MyException.java | 34 + .../cn/celess/blog/mapper/ArticleMapper.java | 58 + .../cn/celess/blog/mapper/CategoryMapper.java | 42 + .../cn/celess/blog/mapper/CommentMapper.java | 48 + .../cn/celess/blog/mapper/PartnerMapper.java | 40 + .../java/cn/celess/blog/mapper/TagMapper.java | 38 + .../cn/celess/blog/mapper/UserMapper.java | 58 + .../cn/celess/blog/mapper/VisitorMapper.java | 24 + .../blog/mapper/WebUpdateInfoMapper.java | 31 + .../celess/blog/service/ArticleService.java | 85 + .../celess/blog/service/CategoryService.java | 54 + .../celess/blog/service/CommentService.java | 101 ++ .../cn/celess/blog/service/CountService.java | 66 + .../cn/celess/blog/service/MailService.java | 27 + .../blog/service/PartnerSiteService.java | 56 + .../cn/celess/blog/service/QiniuService.java | 31 + .../cn/celess/blog/service/TagService.java | 82 + .../cn/celess/blog/service/UserService.java | 177 +++ .../celess/blog/service/VisitorService.java | 40 + .../blog/service/WebUpdateInfoService.java | 62 + .../serviceimpl/ArticleServiceImpl.java | 544 +++++++ .../serviceimpl/CategoryServiceImpl.java | 91 ++ .../serviceimpl/CommentServiceImpl.java | 192 +++ .../service/serviceimpl/CountServiceImpl.java | 73 + .../service/serviceimpl/MailServiceImpl.java | 41 + .../serviceimpl/PartnerSiteServiceImpl.java | 104 ++ .../service/serviceimpl/QiniuServiceImpl.java | 90 ++ .../service/serviceimpl/TagServiceImpl.java | 120 ++ .../service/serviceimpl/UserServiceImpl.java | 449 ++++++ .../serviceimpl/VisitorServiceImpl.java | 210 +++ .../serviceimpl/WebUpdateInfoServiceImpl.java | 99 ++ .../cn/celess/blog/util/DateFormatUtil.java | 42 + .../java/cn/celess/blog/util/JwtUtil.java | 93 ++ .../java/cn/celess/blog/util/MD5Util.java | 14 + .../blog/util/ProtoStuffSerializerUtil.java | 126 ++ .../cn/celess/blog/util/RedisUserUtil.java | 46 + .../java/cn/celess/blog/util/RedisUtil.java | 1411 +++++++++++++++++ .../java/cn/celess/blog/util/RegexUtil.java | 80 + .../java/cn/celess/blog/util/RequestUtil.java | 17 + .../cn/celess/blog/util/ResponseUtil.java | 59 + .../celess/blog/util/SitemapGenerateUtil.java | 109 ++ .../celess/blog/util/StringFromHtmlUtil.java | 17 + .../cn/celess/blog/util/VeriCodeUtil.java | 90 ++ .../application-openSource.properties | 75 + .../resources/application-test.properties | 80 + src/main/resources/application.properties | 5 + src/main/resources/mapper/CategoryMapper.xml | 84 + src/main/resources/mapper/CommentMapper.xml | 98 ++ .../resources/mapper/PartnerSiteMapper.xml | 76 + src/main/resources/mapper/UserMapper.xml | 127 ++ src/main/resources/mapper/VisitorMapper.xml | 33 + .../resources/mapper/WebUpdateInfoMapper.xml | 50 + src/main/resources/mapper/articleMapper.xml | 163 ++ src/main/resources/mapper/tagMapper.xml | 75 + src/test/java/cn/celess/blog/BaseTest.java | 97 ++ .../controller/ArticleControllerTest.java | 407 +++++ .../controller/CategoryControllerTest.java | 132 ++ .../controller/CommentControllerTest.java | 329 ++++ .../blog/controller/LinksControllerTest.java | 178 +++ .../blog/controller/TagControllerTest.java | 143 ++ .../blog/controller/UserControllerTest.java | 247 +++ .../controller/VisitorControllerTest.java | 82 + .../WebUpdateInfoControllerTest.java | 125 ++ .../blog/filter/AuthorizationFilter.java | 64 + .../blog/filter/MultipleSubmitFilter.java | 42 + .../java/cn/celess/blog/util/JwtUtilTest.java | 52 + 119 files changed, 11291 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 LICENSE create mode 100644 README.md create mode 100644 blog.iml create mode 100644 blog.sql create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/cn/celess/blog/BlogApplication.java create mode 100644 src/main/java/cn/celess/blog/configuration/CorsConfig.java create mode 100644 src/main/java/cn/celess/blog/configuration/DruidConfig.java create mode 100644 src/main/java/cn/celess/blog/configuration/InterceptorConfig.java create mode 100644 src/main/java/cn/celess/blog/configuration/RedisConfig.java create mode 100644 src/main/java/cn/celess/blog/configuration/SwaggerConfig.java create mode 100644 src/main/java/cn/celess/blog/configuration/filter/AuthenticationFilter.java create mode 100644 src/main/java/cn/celess/blog/configuration/filter/MultipleSubmitFilter.java create mode 100644 src/main/java/cn/celess/blog/configuration/filter/VisitorRecord.java create mode 100644 src/main/java/cn/celess/blog/configuration/listener/SessionListener.java create mode 100644 src/main/java/cn/celess/blog/controller/ArticleController.java create mode 100644 src/main/java/cn/celess/blog/controller/CategoryController.java create mode 100644 src/main/java/cn/celess/blog/controller/CommentController.java create mode 100644 src/main/java/cn/celess/blog/controller/LinksController.java create mode 100644 src/main/java/cn/celess/blog/controller/Other.java create mode 100644 src/main/java/cn/celess/blog/controller/TagController.java create mode 100644 src/main/java/cn/celess/blog/controller/UserController.java create mode 100644 src/main/java/cn/celess/blog/controller/VisitorController.java create mode 100644 src/main/java/cn/celess/blog/controller/WebUpdateInfoController.java create mode 100644 src/main/java/cn/celess/blog/enmu/LevelEnum.java create mode 100644 src/main/java/cn/celess/blog/enmu/ResponseEnum.java create mode 100644 src/main/java/cn/celess/blog/entity/Article.java create mode 100644 src/main/java/cn/celess/blog/entity/Category.java create mode 100644 src/main/java/cn/celess/blog/entity/Comment.java create mode 100644 src/main/java/cn/celess/blog/entity/PartnerSite.java create mode 100644 src/main/java/cn/celess/blog/entity/Response.java create mode 100644 src/main/java/cn/celess/blog/entity/Tag.java create mode 100644 src/main/java/cn/celess/blog/entity/User.java create mode 100644 src/main/java/cn/celess/blog/entity/Visitor.java create mode 100644 src/main/java/cn/celess/blog/entity/WebUpdate.java create mode 100644 src/main/java/cn/celess/blog/entity/model/ArticleModel.java create mode 100644 src/main/java/cn/celess/blog/entity/model/CommentModel.java create mode 100644 src/main/java/cn/celess/blog/entity/model/QiniuResponse.java create mode 100644 src/main/java/cn/celess/blog/entity/model/UserModel.java create mode 100644 src/main/java/cn/celess/blog/entity/model/VisitorModel.java create mode 100644 src/main/java/cn/celess/blog/entity/model/WebUpdateModel.java create mode 100644 src/main/java/cn/celess/blog/entity/request/ArticleReq.java create mode 100644 src/main/java/cn/celess/blog/entity/request/CommentReq.java create mode 100644 src/main/java/cn/celess/blog/entity/request/LinkReq.java create mode 100644 src/main/java/cn/celess/blog/entity/request/LoginReq.java create mode 100644 src/main/java/cn/celess/blog/entity/request/UserReq.java create mode 100644 src/main/java/cn/celess/blog/exception/ExceptionHandle.java create mode 100644 src/main/java/cn/celess/blog/exception/MyException.java create mode 100644 src/main/java/cn/celess/blog/mapper/ArticleMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/CategoryMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/CommentMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/PartnerMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/TagMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/UserMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/VisitorMapper.java create mode 100644 src/main/java/cn/celess/blog/mapper/WebUpdateInfoMapper.java create mode 100644 src/main/java/cn/celess/blog/service/ArticleService.java create mode 100644 src/main/java/cn/celess/blog/service/CategoryService.java create mode 100644 src/main/java/cn/celess/blog/service/CommentService.java create mode 100644 src/main/java/cn/celess/blog/service/CountService.java create mode 100644 src/main/java/cn/celess/blog/service/MailService.java create mode 100644 src/main/java/cn/celess/blog/service/PartnerSiteService.java create mode 100644 src/main/java/cn/celess/blog/service/QiniuService.java create mode 100644 src/main/java/cn/celess/blog/service/TagService.java create mode 100644 src/main/java/cn/celess/blog/service/UserService.java create mode 100644 src/main/java/cn/celess/blog/service/VisitorService.java create mode 100644 src/main/java/cn/celess/blog/service/WebUpdateInfoService.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/ArticleServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/CategoryServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/CommentServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/CountServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/MailServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/PartnerSiteServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/QiniuServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/TagServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/UserServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/VisitorServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/service/serviceimpl/WebUpdateInfoServiceImpl.java create mode 100644 src/main/java/cn/celess/blog/util/DateFormatUtil.java create mode 100644 src/main/java/cn/celess/blog/util/JwtUtil.java create mode 100644 src/main/java/cn/celess/blog/util/MD5Util.java create mode 100644 src/main/java/cn/celess/blog/util/ProtoStuffSerializerUtil.java create mode 100644 src/main/java/cn/celess/blog/util/RedisUserUtil.java create mode 100644 src/main/java/cn/celess/blog/util/RedisUtil.java create mode 100644 src/main/java/cn/celess/blog/util/RegexUtil.java create mode 100644 src/main/java/cn/celess/blog/util/RequestUtil.java create mode 100644 src/main/java/cn/celess/blog/util/ResponseUtil.java create mode 100644 src/main/java/cn/celess/blog/util/SitemapGenerateUtil.java create mode 100644 src/main/java/cn/celess/blog/util/StringFromHtmlUtil.java create mode 100644 src/main/java/cn/celess/blog/util/VeriCodeUtil.java create mode 100644 src/main/resources/application-openSource.properties create mode 100644 src/main/resources/application-test.properties create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/mapper/CategoryMapper.xml create mode 100644 src/main/resources/mapper/CommentMapper.xml create mode 100644 src/main/resources/mapper/PartnerSiteMapper.xml create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/mapper/VisitorMapper.xml create mode 100644 src/main/resources/mapper/WebUpdateInfoMapper.xml create mode 100644 src/main/resources/mapper/articleMapper.xml create mode 100644 src/main/resources/mapper/tagMapper.xml create mode 100644 src/test/java/cn/celess/blog/BaseTest.java create mode 100644 src/test/java/cn/celess/blog/controller/ArticleControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/CategoryControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/CommentControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/LinksControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/TagControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/UserControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/VisitorControllerTest.java create mode 100644 src/test/java/cn/celess/blog/controller/WebUpdateInfoControllerTest.java create mode 100644 src/test/java/cn/celess/blog/filter/AuthorizationFilter.java create mode 100644 src/test/java/cn/celess/blog/filter/MultipleSubmitFilter.java create mode 100644 src/test/java/cn/celess/blog/util/JwtUtilTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ca3a46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +*.imi +target/ + +# 本地项目的私有文件 +back-end/blog-dev.sql +src/main/resources/application-prod.properties +src/main/resources/application-dev.properties diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..82e87fe --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,114 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01e67997377a393fd672c7dcde9dccbedf0cb1e9 GIT binary patch literal 48337 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blog.sql b/blog.sql new file mode 100644 index 0000000..e07a364 --- /dev/null +++ b/blog.sql @@ -0,0 +1,92 @@ +CREATE DATABASE `blog`; + +USE blog; + +CREATE TABLE `article` +( + `a_id` bigint(20) primary key auto_increment, + `a_title` varchar(255) not null unique comment '文章标题', + `a_summary` varchar(255) not null comment '文章摘要', + `a_md_content` longtext not null comment '文章Markdown内容', + `a_tags_id` varchar(255) not null comment '标签id \',\'处于最尾端', + `a_category_id` bigint(20) not null comment '分类的id', + `a_url` tinytext default null comment '转载文章的原文链接', + `a_author_id` bigint(20) not null comment '作者id', + `a_is_open` boolean default true comment '文章是否可见', + `a_is_original` boolean default true comment '文章是否原创', + `next_a_id` bigint(20) default -1 comment '下篇文章id', + `pre_a_id` bigint(20) default -1 comment '前一篇文章的id', + `a_reading_number` int default 0 comment '文章阅读数', + `a_publish_date` datetime not null comment '文章发布时间', + `a_update_date` datetime default null comment '文章的更新时间' +) DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci,comment '文章表'; + +CREATE TABLE `tag` +( + `tag_id` bigint(20) primary key auto_increment, + `tag_name` varchar(255) unique not null, + `articles` tinytext default null comment 'tag对应的文章id' +) comment '标签表'; + +CREATE table `category` +( + `c_id` bigint(20) primary key auto_increment, + `c_name` varchar(255) unique not null, + `articles` varchar(255) comment '分类下的文章' +)comment '分类表'; + +CREATE TABLE `comment` +( + `co_id` bigint(20) primary key auto_increment, + `co_article_id` bigint(20) default -1 comment '文章id', + `is_comment` boolean default true comment '是否是评论', + `author_id` bigint(20) not null comment '留言者id', + `co_content` text not null comment '评论/留言内容', + `co_date` datetime not null comment '评论/留言的日期', + `co_pid` bigint not null default -1 comment '评论/留言的父id', + `co_response_id` tinytext +) DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci,comment '评论/留言表'; + +CREATE TABLE `links` +( + `site_id` bigint(20) primary key auto_increment, + `site_name` varchar(255) not null comment '友站名称', + `is_open` boolean default true comment '是否公开', + `site_url` varchar(255) not null comment '首页地址' +) comment '友站表'; + +CREATE TABLE `visitor` +( + `v_id` bigint(20) primary key auto_increment, + `v_date` datetime not null comment '访问时间', + `v_ip` varchar(255) not null comment '访客ip', + `v_user_agent` text comment '访客ua' +) comment '访客表'; + + + +CREATE TABLE IF NOT EXISTS `web_update` +( + `update_id` bigint(20) primary key auto_increment, + `update_info` varchar(255) not null comment '更新内容', + `update_time` datetime not null comment '更新时间' +) comment '更新内容表'; + +create table `user` +( + `u_id` int not null primary key auto_increment, + `u_email` varchar(50) not null, + `u_uid` varchar(40) default null comment '用户唯一标识码', + `u_pwd` varchar(40) not null comment '密码', + `email_status` boolean default false comment '邮箱验证状态', + `u_avatar` varchar(255) comment '用户头像', + `u_desc` tinytext comment '用户的描述', + `recently_landed_time` datetime comment '最近的登录时间', + `email_verify_id` varchar(40) comment '用于找回密码或验证邮箱的id', + `display_name` varchar(30) comment '展示的昵称', + `role` varchar(40) not null default 'user' comment '权限组', + unique key `uni_user_id` (`u_id`), + unique key `uni_user_uid` (`u_uid`), + unique key `uni_user_email` (`u_email`) +) comment '用户表'; + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..65f10d2 --- /dev/null +++ b/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" \ No newline at end of file diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..a5284c7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..40e8772 --- /dev/null +++ b/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.3.RELEASE + + + cn.celess + blog + 0.0.1-SNAPSHOT + blog + personal blog system project for Spring Boot + + + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.alibaba + druid + 1.1.14 + + + + + org.projectlombok + lombok + 1.18.6 + + + + + + io.springfox + springfox-swagger2 + 2.6.1 + + + io.springfox + springfox-swagger-ui + 2.6.1 + + + + + com.youbenzi + MDTool + 1.2.3 + + + net.minidev + json-smart + 2.3 + compile + + + + + net.sf.json-lib + json-lib + 2.4 + jdk15 + + + + + com.qiniu + qiniu-java-sdk + [7.2.0, 7.2.99] + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.0.1 + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.2.12 + + + + + com.dyuproject.protostuff + protostuff-core + 1.0.8 + + + com.dyuproject.protostuff + protostuff-runtime + 1.0.8 + + + + + eu.bitwalker + UserAgentUtils + 1.20 + + + + junit + junit + 4.12 + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/cn/celess/blog/BlogApplication.java b/src/main/java/cn/celess/blog/BlogApplication.java new file mode 100644 index 0000000..f65dc22 --- /dev/null +++ b/src/main/java/cn/celess/blog/BlogApplication.java @@ -0,0 +1,20 @@ +package cn.celess.blog; + +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication +@EnableAsync +@MapperScan("cn.celess.blog.mapper") +public class BlogApplication { + public static final Logger logger = LoggerFactory.getLogger(BlogApplication.class); + + public static void main(String[] args) { + SpringApplication.run(BlogApplication.class, args); + logger.info("启动完成!"); + } +} diff --git a/src/main/java/cn/celess/blog/configuration/CorsConfig.java b/src/main/java/cn/celess/blog/configuration/CorsConfig.java new file mode 100644 index 0000000..2638306 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/CorsConfig.java @@ -0,0 +1,38 @@ +package cn.celess.blog.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * @author : xiaohai + * @date : 2019/03/30 19:55 + * 跨域 + */ +@Configuration +public class CorsConfig { + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedOrigin("http://celess.cn"); + config.addAllowedOrigin("http://www.celess.cn"); + config.addAllowedOrigin("https://celess.cn"); + config.addAllowedOrigin("https://www.celess.cn"); + // 本地调试时的跨域 + config.addAllowedOrigin("http://localhost:4200"); + config.addAllowedOrigin("http://127.0.0.1:4200"); + config.addAllowedHeader("*"); + config.addAllowedMethod("OPTIONS"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("DELETE"); + config.setAllowCredentials(true); + config.setMaxAge(10800L); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } +} diff --git a/src/main/java/cn/celess/blog/configuration/DruidConfig.java b/src/main/java/cn/celess/blog/configuration/DruidConfig.java new file mode 100644 index 0000000..8f92b61 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/DruidConfig.java @@ -0,0 +1,41 @@ +package cn.celess.blog.configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author : xiaohai + * @date : 2019/03/28 14:26 + */ +@Configuration +public class DruidConfig { + @Value("${spring.datasource.url}") + private String dbUrl; + + @Value("${spring.datasource.username}") + private String username; + + @Value("${spring.datasource.password}") + private String password; + + @Value("${spring.datasource.driver-class-name}") + private String driverClassName; + + @Bean + public DruidDataSource druidDataSource() { + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + // 数据库基本信息 + dataSource.setUrl(dbUrl); + dataSource.setUsername(username); + dataSource.setPassword(password); + + // 数据库连接池配置 + dataSource.setInitialSize(10); + dataSource.setMinIdle(10); + dataSource.setMaxActive(100); + return dataSource; + } +} diff --git a/src/main/java/cn/celess/blog/configuration/InterceptorConfig.java b/src/main/java/cn/celess/blog/configuration/InterceptorConfig.java new file mode 100644 index 0000000..1e22834 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/InterceptorConfig.java @@ -0,0 +1,41 @@ +package cn.celess.blog.configuration; + +import cn.celess.blog.configuration.filter.AuthenticationFilter; +import cn.celess.blog.configuration.filter.MultipleSubmitFilter; +import cn.celess.blog.configuration.filter.VisitorRecord; +import cn.celess.blog.configuration.listener.SessionListener; +import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @Author: 小海 + * @Date: 2019/10/18 14:19 + * @Description: + */ +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new MultipleSubmitFilter()).addPathPatterns("/*"); + registry.addInterceptor(authenticationFilter()).addPathPatterns("/**"); + + // visitor 输出信息杂乱 暂时放弃使用 +// registry.addInterceptor(new VisitorRecord()).addPathPatterns("/*"); + } + + @Bean + public AuthenticationFilter authenticationFilter() { + return new AuthenticationFilter(); + } + +// // session listener register bean +// @Bean +// public ServletListenerRegistrationBean servletListenerRegistrationBean() { +// ServletListenerRegistrationBean slrBean = new ServletListenerRegistrationBean(); +// slrBean.setListener(new SessionListener()); +// return slrBean; +// } +} diff --git a/src/main/java/cn/celess/blog/configuration/RedisConfig.java b/src/main/java/cn/celess/blog/configuration/RedisConfig.java new file mode 100644 index 0000000..c36798c --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/RedisConfig.java @@ -0,0 +1,72 @@ +package cn.celess.blog.configuration; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; + +import java.lang.reflect.Method; + + +/** + * @author : xiaohai + * @date : 2019/05/22 17:35 + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + /** + * 缓存的命名前缀 + * + * @return KeyGenerator + */ + @Override + @Bean + public KeyGenerator keyGenerator() { + return new KeyGenerator() { + @Override + public Object generate(Object target, Method method, Object... params) { + StringBuilder sb = new StringBuilder(); + String name = target.getClass().getName(); + sb.append(name.substring(name.lastIndexOf(".") + 1)); + sb.append(":"); + sb.append(method.getName()); + for (Object obj : params) { + sb.append("-").append(obj.toString()); + } + return sb.toString(); + } + }; + } + + /** + * 配置redisTemplate + * + * @param redisConnectionFactory redisConnectionFactory + * @return redisTemplate + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(redisConnectionFactory); + template.setKeySerializer(jackson2JsonRedisSerializer); + template.setValueSerializer(jackson2JsonRedisSerializer); + template.setHashKeySerializer(jackson2JsonRedisSerializer); + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.afterPropertiesSet(); + return template; + } +} \ No newline at end of file diff --git a/src/main/java/cn/celess/blog/configuration/SwaggerConfig.java b/src/main/java/cn/celess/blog/configuration/SwaggerConfig.java new file mode 100644 index 0000000..01bbdc0 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/SwaggerConfig.java @@ -0,0 +1,45 @@ +package cn.celess.blog.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * @author : xiaohai + * @date : 2019/03/28 15:55 + */ +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Value("${spring.profiles.active}") + private String environment; + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .enable("dev".equals(environment)) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("cn.celess.blog")) + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("小海博客的APi") + .description("小海博客的APi") + .contact("小海") + .version("1.0") + .build(); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/celess/blog/configuration/filter/AuthenticationFilter.java b/src/main/java/cn/celess/blog/configuration/filter/AuthenticationFilter.java new file mode 100644 index 0000000..b80cf40 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/filter/AuthenticationFilter.java @@ -0,0 +1,83 @@ +package cn.celess.blog.configuration.filter; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.service.UserService; +import cn.celess.blog.util.JwtUtil; +import cn.celess.blog.util.RedisUtil; +import cn.celess.blog.util.ResponseUtil; +import net.sf.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @Author: 小海 + * @Date: 2019/11/16 11:21 + * @Description: 鉴权拦截器 + */ +public class AuthenticationFilter implements HandlerInterceptor { + @Autowired + JwtUtil jwtUtil; + @Autowired + RedisUtil redisUtil; + @Autowired + UserService userService; + + private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class); + + private static final String USER_PREFIX = "/user"; + private static final String ADMIN_PREFIX = "/admin"; + private static final String ROLE_ADMIN = "admin"; + private static final String ROLE_USER = "user"; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String path = request.getRequestURI(); + path = path.replaceAll("/+", "/"); + int indexOf = path.indexOf("/", 1); + String rootPath = indexOf == -1 ? path : path.substring(0, indexOf); + // 不需要鉴权的路径 + if (!USER_PREFIX.equals(rootPath.toLowerCase()) && !ADMIN_PREFIX.equals(rootPath.toLowerCase())) { + return true; + } + + String jwtStr = request.getHeader("Authorization"); + if (jwtStr == null || jwtStr.isEmpty()) { + return writeResponse(ResponseEnum.HAVE_NOT_LOG_IN, response, request); + } + if (jwtUtil.isTokenExpired(jwtStr)) { + return writeResponse(ResponseEnum.LOGIN_EXPIRED, response, request); + } + String email = jwtUtil.getUsernameFromToken(jwtStr); + if (!redisUtil.hasKey(email + "-login") || jwtUtil.isTokenExpired(jwtStr)) { + // 登陆过期 + return writeResponse(ResponseEnum.LOGIN_EXPIRED, response, request); + } + String role = userService.getUserRoleByEmail(email); + if (role.equals(ROLE_ADMIN)) { + // admin + return true; + } + if (role.equals(ROLE_USER) && !rootPath.equals(ADMIN_PREFIX)) { + // user not admin page + return true; + } + return writeResponse(ResponseEnum.PERMISSION_ERROR, response, request); + } + + private boolean writeResponse(ResponseEnum e, HttpServletResponse response, HttpServletRequest request) { + response.setHeader("Content-Type", "application/json;charset=UTF-8"); + try { + logger.info("鉴权失败,[code:{},msg:{},path:{}]", e.getCode(), e.getMsg(), request.getRequestURI() + "?" + request.getQueryString()); + response.getWriter().println(JSONObject.fromObject(ResponseUtil.response(e, null))); + } catch (IOException ex) { + ex.printStackTrace(); + } + return false; + } +} diff --git a/src/main/java/cn/celess/blog/configuration/filter/MultipleSubmitFilter.java b/src/main/java/cn/celess/blog/configuration/filter/MultipleSubmitFilter.java new file mode 100644 index 0000000..c0557cc --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/filter/MultipleSubmitFilter.java @@ -0,0 +1,44 @@ +package cn.celess.blog.configuration.filter; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Response; +import cn.celess.blog.util.RequestUtil; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * @Author: 小海 + * @Date: 2019/10/18 13:46 + * @Description: 多次请求拦截器 + */ +public class MultipleSubmitFilter implements HandlerInterceptor { + private static final int WAIT_TIME = 200;// 多次提交中间的间隔 + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Long lastSubmitTime = (Long) request.getSession().getAttribute("lastSubmitTime"); + String completeUrl = (String) request.getSession().getAttribute("completeUrl&method"); + if (lastSubmitTime == null || completeUrl == null) { + return true; + } + if (System.currentTimeMillis() - lastSubmitTime < WAIT_TIME && RequestUtil.getCompleteUrlAndMethod(request).equals(completeUrl)) { + // 请求参数和路径均相同 且请求时间间隔小于 WAIT_TIME + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + Response result = new Response(ResponseEnum.FAILURE.getCode(), "重复请求", null, System.currentTimeMillis()); + response.getWriter().println(result.toString()); + return false; + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + HttpSession session = request.getSession(); + session.setAttribute("lastSubmitTime", System.currentTimeMillis()); + session.setAttribute("completeUrl&method", RequestUtil.getCompleteUrlAndMethod(request)); + } +} diff --git a/src/main/java/cn/celess/blog/configuration/filter/VisitorRecord.java b/src/main/java/cn/celess/blog/configuration/filter/VisitorRecord.java new file mode 100644 index 0000000..0082cc8 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/filter/VisitorRecord.java @@ -0,0 +1,34 @@ +package cn.celess.blog.configuration.filter; + +import cn.celess.blog.util.RequestUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.HashMap; + +/** + * @Author: 小海 + * @Date: 2019/10/18 15:38 + * @Description: 记录访问情况 + */ +@Configuration +public class VisitorRecord implements HandlerInterceptor { + + @SuppressWarnings("unchecked") + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(); + HashMap visitDetail = (HashMap) session.getAttribute("visitDetail"); + // 获取访问次数 + Integer count = visitDetail.get(RequestUtil.getCompleteUrlAndMethod(request)); + // 自增 + count = count == null ? 1 : ++count; + // 更新 + visitDetail.put(RequestUtil.getCompleteUrlAndMethod(request), count); + session.setAttribute("ip",request.getRemoteAddr()); + return true; + } +} diff --git a/src/main/java/cn/celess/blog/configuration/listener/SessionListener.java b/src/main/java/cn/celess/blog/configuration/listener/SessionListener.java new file mode 100644 index 0000000..26b2bd3 --- /dev/null +++ b/src/main/java/cn/celess/blog/configuration/listener/SessionListener.java @@ -0,0 +1,50 @@ +package cn.celess.blog.configuration.listener; + +import cn.celess.blog.entity.User; +import cn.celess.blog.util.RedisUserUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.util.HashMap; + +/** + * @Author: 小海 + * @Date: 2019/10/18 15:33 + * @Description: 监听session的情况 + */ +@WebListener +public class SessionListener implements HttpSessionListener { + @Autowired + RedisUserUtil redisUserUtil; + @Autowired + HttpServletRequest request; + private static final Logger logger = LoggerFactory.getLogger(SessionListener.class); + + @Override + public void sessionCreated(HttpSessionEvent se) { + // TODO : can move 'visit' api to here + se.getSession().setAttribute("visitDetail", new HashMap()); + // se.getSession().setMaxInactiveInterval(10);// 10s for debug + logger.info("新增一个Session[{}]", se.getSession().getId()); + } + + @SuppressWarnings("unchecked") + @Override + public void sessionDestroyed(HttpSessionEvent se) { + HashMap visitDetail = (HashMap) se.getSession().getAttribute("visitDetail"); + StringBuilder sb = new StringBuilder(); + sb.append("ip => ").append(se.getSession().getAttribute("ip")); + User user = redisUserUtil.get(request); + sb.append("\t登录情况 => "); + sb.append(user == null ? "游客访问" : user.getEmail()); + visitDetail.forEach((s, integer) -> { + sb.append("\n").append("Method:[").append(s.split(":")[1]).append("]\tTimes:[").append(integer).append("]\tPath:[").append(s.split(":")[0]).append("]"); + }); + logger.info(sb.toString()); + } +} diff --git a/src/main/java/cn/celess/blog/controller/ArticleController.java b/src/main/java/cn/celess/blog/controller/ArticleController.java new file mode 100644 index 0000000..09f58ef --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/ArticleController.java @@ -0,0 +1,154 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.model.ArticleModel; +import cn.celess.blog.entity.request.ArticleReq; +import cn.celess.blog.service.ArticleService; +import cn.celess.blog.util.RedisUserUtil; +import cn.celess.blog.util.ResponseUtil; +import cn.celess.blog.util.SitemapGenerateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author : xiaohai + * @date : 2019/03/28 15:18 + */ +@RestController +public class ArticleController { + @Autowired + ArticleService articleService; + @Autowired + SitemapGenerateUtil sitemapGenerateUtil; + @Autowired + RedisUserUtil redisUserUtil; + + /** + * 新建一篇文章 + * + * @param body 请求数据 + * @return Response + */ + @PostMapping("/admin/article/create") + public Response create(@RequestBody ArticleReq body) { + ArticleModel articleModel = articleService.create(body); + sitemapGenerateUtil.createSitemap(); + return ResponseUtil.success(articleModel); + } + + /** + * 通过文章id 删除一篇文章 + * + * @param articleId 文章id + * @return Response + */ + @DeleteMapping("/admin/article/del") + public Response delete(@RequestParam("articleID") long articleId) { + boolean delete = articleService.delete(articleId); + sitemapGenerateUtil.createSitemap(); + return ResponseUtil.success(delete); + } + + /** + * 更新文章 + * + * @param body 请求数据 + * @return Response + */ + @PutMapping("/admin/article/update") + public Response update(@RequestBody ArticleReq body) { + ArticleModel update = articleService.update(body); + sitemapGenerateUtil.createSitemap(); + return ResponseUtil.success(update); + } + + /** + * 通过id查找一篇文章 + * 公开 =>返回数据 + * 不公开 + * *** =>作者 返回数据 + * *** =>其他 抛出错误 + * + * @param articleId 文章id + * @param is4update 是否是更新 + * @return Response + */ + @GetMapping("/article/articleID/{articleID}") + public Response retrieveOneById(@PathVariable("articleID") long articleId, + @RequestParam(value = "update", defaultValue = "false") boolean is4update, + HttpServletRequest request) { + ArticleModel article = articleService.retrieveOneByID(articleId, is4update); + if (article.getOpen()) { + return ResponseUtil.success(article); + } else if (article.getAuthorId().equals(redisUserUtil.get(request).getId())) { + return ResponseUtil.success(article); + } + return ResponseUtil.response(ResponseEnum.PERMISSION_ERROR, null); + } + + /** + * 分页获取所有文章状态为开放的的文章 + * + * @param page 页码 + * @param count 单页数据量 + * @return Response + */ + @GetMapping("/articles") + public Response articles(@RequestParam(name = "page", defaultValue = "1") int page, + @RequestParam(name = "count", defaultValue = "5") int count) { + return ResponseUtil.success(articleService.retrievePageForOpen(count, page)); + } + + /** + * 分页获取所有文章 + * + * @param page 页码 + * @param count 单页数据量 + * @return Response + */ + @GetMapping("/admin/articles") + public Response adminArticles(@RequestParam(name = "page", defaultValue = "1") int page, + @RequestParam(name = "count", defaultValue = "10") int count) { + return ResponseUtil.success(articleService.adminArticles(count, page)); + } + + /** + * 通过分类获取文章(文章摘要) + * + * @param name 分类名 + * @param page 页码 + * @param count 单页数据量 + * @return Response + */ + @GetMapping("/articles/category/{name}") + public Response findByCategory(@PathVariable("name") String name, + @RequestParam(name = "page", defaultValue = "1") int page, + @RequestParam(name = "count", defaultValue = "10") int count) { + return ResponseUtil.success(articleService.findByCategory(name, page, count)); + } + + /** + * 通过标签名获取文章(文章摘要) + * + * @param name 标签名 + * @param page 页码 + * @param count 单页数据量 + * @return Response + */ + @GetMapping("/articles/tag/{name}") + public Response findByTag(@PathVariable("name") String name, + @RequestParam(name = "page", defaultValue = "1") int page, + @RequestParam(name = "count", defaultValue = "10") int count) { + return ResponseUtil.success(articleService.findByTag(name, page, count)); + } + + + @GetMapping("/createSitemap") + public Response createSitemap() { + sitemapGenerateUtil.createSitemap(); + return ResponseUtil.success(null); + } +} diff --git a/src/main/java/cn/celess/blog/controller/CategoryController.java b/src/main/java/cn/celess/blog/controller/CategoryController.java new file mode 100644 index 0000000..c9a7fab --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/CategoryController.java @@ -0,0 +1,63 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Response; +import cn.celess.blog.service.CategoryService; +import cn.celess.blog.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * @author : xiaohai + * @date : 2019/03/30 20:36 + */ +@RestController +public class CategoryController { + + @Autowired + CategoryService categoryService; + + /** + * 新增一个分类 + * + * @param name 分类名 + * @return Response + */ + @PostMapping("/admin/category/create") + public Response addOne(@RequestParam("name") String name) { + return ResponseUtil.success(categoryService.create(name)); + } + + /** + * 删除一个分类 + * + * @param id 分类id + * @return Response + */ + @DeleteMapping("/admin/category/del") + public Response deleteOne(@RequestParam("id") long id) { + return ResponseUtil.success(categoryService.delete(id)); + } + + /** + * 更新一个分类 + * + * @param id 分类id + * @param name 更新后的名字 + * @return Response + */ + @PutMapping("/admin/category/update") + public Response updateOne(@RequestParam("id") Long id, + @RequestParam("name") String name) { + return ResponseUtil.success(categoryService.update(id, name)); + } + + /** + * 获取所有的分类 + * + * @return Response + */ + @GetMapping("/categories") + public Response getPage() { + return ResponseUtil.success(categoryService.retrievePage()); + } +} diff --git a/src/main/java/cn/celess/blog/controller/CommentController.java b/src/main/java/cn/celess/blog/controller/CommentController.java new file mode 100644 index 0000000..5db0bbd --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/CommentController.java @@ -0,0 +1,101 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Comment; +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.request.CommentReq; +import cn.celess.blog.service.CommentService; +import cn.celess.blog.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +/** + * @author : xiaohai + * @date : 2019/03/30 20:37 + */ +@RestController +public class CommentController { + @Autowired + CommentService commentService; + + /** + * 新增一条评论数据 + * + * @param reqBody 请求数据 + * @return Response + */ + @PostMapping("/user/comment/create") + public Response addOne(@RequestBody CommentReq reqBody) { + return ResponseUtil.success(commentService.create(reqBody)); + } + + @DeleteMapping("/user/comment/del") + public Response delete(@RequestParam("id") long id) { + return ResponseUtil.success(commentService.delete(id)); + } + + @PutMapping("/user/comment/update") + public Response update(@RequestBody CommentReq reqBody) { + return ResponseUtil.success(commentService.update(reqBody)); + } + + /** + * 获取所有的一级评论 + * + * @param articleId 文章id + * @param count 单页数据量 + * @param page 页码 + * @return Response + */ + @GetMapping("/comments") + public Response commentsOfArticle(@RequestParam("articleId") long articleId, + @RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page) { + return ResponseUtil.success(commentService.retrievePageByArticle(articleId, -1, page, count)); + } + + /** + * 通过pid获取数据 + * + * @param pid + * @param count + * @param page + * @return + */ + @GetMapping("/comment/pid/{pid}") + public Response retrievePage(@PathVariable("pid") long pid, + @RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page) { + return ResponseUtil.success(commentService.retrievePageByPid(pid, page, count)); + } + + /** + * 获取所以的一级留言 + * + * @param count + * @param page + * @return + */ + @GetMapping("/leaveMsg") + public Response retrievePageOfLeaveMsg(@RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page) { + return ResponseUtil.success(commentService.retrievePageByTypeAndPid(false, -1, page, count)); + } + + @GetMapping("/admin/comment/type/{type}") + public Response retrievePageAdmin( + @PathVariable("type") int isComment, + @RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page) { + return ResponseUtil.success(commentService.retrievePageByType(1 == isComment, page, count)); + } + + @GetMapping("/user/comment/type/{type}") + public Response retrievePageByAuthor( + @PathVariable(value = "type") int isComment, + @RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page) { + return ResponseUtil.success(commentService.retrievePageByAuthor(1 == isComment, page, count)); + } + +} diff --git a/src/main/java/cn/celess/blog/controller/LinksController.java b/src/main/java/cn/celess/blog/controller/LinksController.java new file mode 100644 index 0000000..16c751f --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/LinksController.java @@ -0,0 +1,99 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.PartnerSite; +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.request.LinkReq; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.service.MailService; +import cn.celess.blog.service.PartnerSiteService; +import cn.celess.blog.util.RedisUtil; +import cn.celess.blog.util.RegexUtil; +import cn.celess.blog.util.ResponseUtil; +import cn.celess.blog.util.DateFormatUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author : xiaohai + * @date : 2019/05/12 13:26 + */ +@RestController +public class LinksController { + @Autowired + PartnerSiteService partnerSiteService; + @Autowired + MailService mailService; + @Autowired + RedisUtil redisUtil; + @Autowired + HttpServletRequest request; + + @PostMapping("/admin/links/create") + public Response create(@RequestBody LinkReq reqBody) { + return ResponseUtil.success(partnerSiteService.create(reqBody)); + } + + @DeleteMapping("/admin/links/del/{id}") + public Response del(@PathVariable("id") long id) { + return ResponseUtil.success(partnerSiteService.del(id)); + } + + @PutMapping("/admin/links/update") + public Response update(@RequestBody LinkReq reqBody) { + return ResponseUtil.success(partnerSiteService.update(reqBody)); + } + + @GetMapping("/links") + public Response allForOpen() { + List sites = new ArrayList<>(); + for (PartnerSite p : partnerSiteService.findAll()) { + if (p.getOpen()) { + //隐藏open字段 + p.setOpen(null); + sites.add(p); + } + } + return ResponseUtil.success(sites); + } + + @GetMapping("/admin/links") + public Response all(@RequestParam("page") int page, + @RequestParam("count") int count) { + return ResponseUtil.success(partnerSiteService.PartnerSitePages(page, count)); + } + + @PostMapping("/apply") + public Response apply(@RequestParam("name") String name, + @RequestParam("url") String url) { + // TODO :: 弃用发送邮件的方式。 + if (name == null || name.replaceAll(" ", "").isEmpty()) { + return ResponseUtil.response(ResponseEnum.PARAMETERS_ERROR, null); + } + if (!RegexUtil.urlMatch(url)) { + return ResponseUtil.response(ResponseEnum.PARAMETERS_URL_ERROR, null); + } + String applyTimeStr = redisUtil.get(request.getRemoteAddr() + "-Apply"); + int applyTime = 0; + if (applyTimeStr != null) { + applyTime = Integer.parseInt(applyTimeStr); + } + if (applyTime == 10) { + throw new MyException(ResponseEnum.FAILURE.getCode(), "申请次数已达10次,请2小时后重试"); + } + SimpleMailMessage message = new SimpleMailMessage(); + message.setSubject("友链申请:" + name); + message.setTo("a@celess.cn"); + message.setText("name:" + name + "\nurl:" + url + "\n" + DateFormatUtil.getNow()); + Boolean send = mailService.send(message); + redisUtil.setEx(request.getRemoteAddr() + "-Apply", applyTime + 1 + "", 2, TimeUnit.HOURS); + return send ? ResponseUtil.success("") : ResponseUtil.failure(""); + + } +} diff --git a/src/main/java/cn/celess/blog/controller/Other.java b/src/main/java/cn/celess/blog/controller/Other.java new file mode 100644 index 0000000..2db1c32 --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/Other.java @@ -0,0 +1,172 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.model.QiniuResponse; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.service.CountService; +import cn.celess.blog.service.QiniuService; +import cn.celess.blog.util.RedisUtil; +import cn.celess.blog.util.ResponseUtil; +import cn.celess.blog.util.VeriCodeUtil; +import net.sf.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author : xiaohai + * @date : 2019/04/02 22:03 + */ +@RestController +public class Other { + public static final Logger logger = LoggerFactory.getLogger(Object.class); + + @Autowired + CountService countService; + @Autowired + QiniuService qiniuService; + @Autowired + RedisUtil redisUtil; + @Autowired + HttpServletRequest request; + + + @GetMapping("/counts") + public Response allCount() { + Map countMap = new HashMap<>(); + countMap.put("articleCount", countService.getArticleCount()); + countMap.put("commentCount", countService.getCommentCount()); + countMap.put("leaveMsgCount", countService.getLeaveMessageCount()); + countMap.put("categoryCount", countService.getCategoriesCount()); + countMap.put("tagCount", countService.getTagsCount()); + countMap.put("visitorCount", countService.getVisitorCount()); + return ResponseUtil.success(countMap); + } + + + /** + * 获取header的全部参数 + * + * @param request HttpServletRequest + * @return Response + */ + @GetMapping("/headerInfo") + public Response headerInfo(HttpServletRequest request) { + Map map = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + String str = null; + while ((str = headerNames.nextElement()) != null) { + map.put(str, request.getHeader(str)); + } + map.put("sessionID", request.getSession().getId()); + map.put("request.getRemoteAddr()", request.getRemoteAddr()); + return ResponseUtil.success(map); + } + + /** + * 返回验证码 + * + * @param response HttpServletResponse + * @throws IOException IOException + */ + @GetMapping(value = "/imgCode", produces = MediaType.IMAGE_PNG_VALUE) + public void getImg(HttpServletResponse response) throws IOException { + Object[] obj = VeriCodeUtil.createImage(); + request.getSession().setAttribute("code", obj[0]); + //将图片输出给浏览器 + BufferedImage image = (BufferedImage) obj[1]; + response.setContentType("image/png"); + OutputStream os = response.getOutputStream(); + ImageIO.write(image, "png", os); + os.close(); + } + + /** + * 验证 验证码的正确性 + * + * @param code 传进来的验证码 + * @param request HttpServletRequest + * @return Session中写入验证状态 + */ + @PostMapping("/verCode") + public Response verCode(@RequestParam("code") String code, HttpServletRequest request) { + request.getSession().setAttribute("verImgCodeStatus", false); + String codeStr = (String) request.getSession().getAttribute("code"); + if (code == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + if (codeStr == null) { + throw new MyException(ResponseEnum.IMG_CODE_TIMEOUT); + } + code = code.toLowerCase(); + codeStr = codeStr.toLowerCase(); + if (code.equals(codeStr)) { + request.getSession().removeAttribute("code"); + request.getSession().setAttribute("verImgCodeStatus", true); + return ResponseUtil.success("验证成功"); + } else { + request.getSession().removeAttribute("code"); + return ResponseUtil.failure("验证失败,请重新获取验证码"); + } + } + + /** + * FIXME :: 单张图片多次上传的问题 + * editor.md图片上传的接口 + * FUCK !!! + * + * @param file 文件 + * @return + * @throws IOException + */ + @PostMapping("/imgUpload") + public void upload(HttpServletRequest request, HttpServletResponse response, @RequestParam("editormd-image-file") MultipartFile file) throws IOException { + JSONObject jsonObject = new JSONObject(); + String uploadTimesStr = redisUtil.get(request.getRemoteAddr() + "-ImgUploadTimes"); + int uploadTimes = 0; + if (uploadTimesStr != null) { + uploadTimes = Integer.parseInt(uploadTimesStr); + } + if (uploadTimes == 10) { + throw new MyException(ResponseEnum.FAILURE.getCode(), "上传次数已达10次,请2小时后在上传"); + } + request.setCharacterEncoding("utf-8"); + response.setContentType("text/html"); + if (file.isEmpty()) { + jsonObject.put("success", 0); + jsonObject.put("message", "上传失败,请选择文件"); + response.getWriter().println(jsonObject.toString()); + return; + } + String fileName = file.getOriginalFilename(); + String mime = fileName.substring(fileName.lastIndexOf(".")); + if (".png".equals(mime.toLowerCase()) || ".jpg".equals(mime.toLowerCase()) || + ".jpeg".equals(mime.toLowerCase()) || ".bmp".equals(mime.toLowerCase())) { + QiniuResponse qiniuResponse = qiniuService.uploadFile(file.getInputStream(), "img_" + System.currentTimeMillis() + mime); + jsonObject.put("success", 0); + jsonObject.put("message", "上传成功"); + jsonObject.put("url", "http://cdn.celess.cn/" + qiniuResponse.key); + response.getWriter().println(jsonObject.toString()); + redisUtil.setEx(request.getRemoteAddr() + "-ImgUploadTimes", uploadTimes + 1 + "", 2, TimeUnit.HOURS); + return; + } + jsonObject.put("success", 0); + jsonObject.put("message", "上传失败,请上传图片文件"); + response.getWriter().println(jsonObject.toString()); + } +} diff --git a/src/main/java/cn/celess/blog/controller/TagController.java b/src/main/java/cn/celess/blog/controller/TagController.java new file mode 100644 index 0000000..c942f60 --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/TagController.java @@ -0,0 +1,70 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.Tag; +import cn.celess.blog.service.TagService; +import cn.celess.blog.util.ResponseUtil; +import net.sf.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/30 20:36 + */ +@RestController +public class TagController { + @Autowired + TagService tagService; + + + @PostMapping("/admin/tag/create") + public Response addOne(@RequestParam("name") String name) { + return ResponseUtil.success(tagService.create(name)); + } + + @DeleteMapping("/admin/tag/del") + public Response delOne(@RequestParam("id") long id) { + return ResponseUtil.success(tagService.delete(id)); + } + + + @PutMapping("/admin/tag/update") + public Response updateOne(@RequestParam("id") Long id, @RequestParam("name") String name) { + return ResponseUtil.success(tagService.update(id, name)); + } + + @GetMapping("/tag/id/{id}") + public Response retrieveOneById(@PathVariable("id") long id) { + return ResponseUtil.success(tagService.retrieveOneById(id)); + } + + @GetMapping("/tag/name/{name}") + public Response retrieveOneByName(@PathVariable("name") String name) { + return ResponseUtil.success(tagService.retrieveOneByName(name)); + } + + @GetMapping("/tags") + public Response getPage(@RequestParam(required = false, defaultValue = "10", value = "count") int count, + @RequestParam(required = false, defaultValue = "1", value = "page") int page) { + return ResponseUtil.success(tagService.retrievePage(page, count)); + } + + @GetMapping("/tags/nac") + public Response getTagNameAndCount() { + List nameAndCount = new ArrayList<>(); + List all = tagService.findAll(); + for (Tag t : all) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", t.getName()); + String articles = t.getArticles(); + jsonObject.put("size", articles == null ? 0 : articles.split(",").length); + nameAndCount.add(jsonObject); + } + return ResponseUtil.success(nameAndCount); + } + +} diff --git a/src/main/java/cn/celess/blog/controller/UserController.java b/src/main/java/cn/celess/blog/controller/UserController.java new file mode 100644 index 0000000..5e4a7ca --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/UserController.java @@ -0,0 +1,122 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.request.LoginReq; +import cn.celess.blog.entity.request.UserReq; +import cn.celess.blog.service.UserService; +import cn.celess.blog.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * @author : xiaohai + * @date : 2019/03/30 20:37 + */ +@RestController +public class UserController { + @Autowired + UserService userService; + + + @PostMapping("/login") + public Response login(@RequestBody LoginReq loginReq) { + return ResponseUtil.success(userService.login(loginReq)); + } + + @PostMapping("/registration") + public Response registration(@RequestParam("email") String email, + @RequestParam("password") String password) { + return ResponseUtil.success(userService.registration(email, password)); + } + + @GetMapping("/logout") + public Response logout() { + return ResponseUtil.success(userService.logout()); + } + + @PutMapping("/user/userInfo/update") + public Response updateInfo(String desc, String displayName) { + return ResponseUtil.success(userService.update(desc, displayName)); + } + + @GetMapping("/user/userInfo") + public Response getUserInfo() { + return ResponseUtil.success(userService.getUserInfoBySession()); + } + + /** + * 更新头像 + * + * @param file file + * @return + * @throws IOException + */ + @PostMapping("/user/imgUpload") + @ResponseBody + public Response upload(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + return ResponseUtil.failure("上传失败,请选择文件"); + } + String fileName = file.getOriginalFilename(); + String mime = fileName.substring(fileName.lastIndexOf(".")); + if (".png".equals(mime.toLowerCase()) || ".jpg".equals(mime.toLowerCase()) || + ".jpeg".equals(mime.toLowerCase()) || ".bmp".equals(mime.toLowerCase())) { + return (Response) userService.updateUserAavatarImg(file.getInputStream(), mime); + } + return ResponseUtil.failure("请上传图片文件"); + } + + @PostMapping("/sendResetPwdEmail") + public Response sendResetPwdEmail(@RequestParam("email") String email) { + return ResponseUtil.success(userService.sendResetPwdEmail(email)); + } + + @PostMapping("/sendVerifyEmail") + public Response sendVerifyEmail(@RequestParam("email") String email) { + return ResponseUtil.success(userService.sendVerifyEmail(email)); + } + + + @PostMapping("/emailVerify") + public Response emailVerify(@RequestParam("verifyId") String verifyId, + @RequestParam("email") String mail) { + return ResponseUtil.success(userService.verifyEmail(verifyId, mail)); + } + + @PostMapping("/resetPwd") + public Response resetPwd(@RequestParam("verifyId") String verifyId, + @RequestParam("email") String email, + @RequestParam("pwd") String pwd) { + return ResponseUtil.success(userService.reSetPwd(verifyId, email, pwd)); + } + + @DeleteMapping("/admin/user/delete") + public Response multipleDelete(@RequestBody Integer[] ids) { + return ResponseUtil.success(userService.deleteUser(ids)); + } + + @DeleteMapping("/admin/user/delete/{id}") + public Response delete(@PathVariable("id") Integer id) { + return ResponseUtil.success(userService.deleteUser(new Integer[]{id})); + } + + @PutMapping("/admin/user") + public Response updateInfoByAdmin(@RequestBody UserReq user) { + return ResponseUtil.success(userService.adminUpdate(user)); + } + + @GetMapping("/admin/users") + public Response getAllUser(@RequestParam("page") int pageNum, @RequestParam("count") int count) { + return ResponseUtil.success(userService.getUserList(pageNum, count)); + } + + @GetMapping("/emailStatus/{email}") + public Response getEmailStatus(@PathVariable("email") String email) { + return ResponseUtil.success(userService.getStatusOfEmail(email)); + } + + +} diff --git a/src/main/java/cn/celess/blog/controller/VisitorController.java b/src/main/java/cn/celess/blog/controller/VisitorController.java new file mode 100644 index 0000000..741e8ef --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/VisitorController.java @@ -0,0 +1,60 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Response; +import cn.celess.blog.service.CountService; +import cn.celess.blog.service.VisitorService; +import cn.celess.blog.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author : xiaohai + * @date : 2019/04/02 23:09 + */ +@RestController +public class VisitorController { + @Autowired + VisitorService visitorService; + @Autowired + CountService countService; + + @GetMapping("/visitor/count") + public Response getVisitorCount() { + return ResponseUtil.success(countService.getVisitorCount()); + } + + @GetMapping("/admin/visitor/page") + public Response page(@RequestParam(value = "count", required = false, defaultValue = "10") int count, + @RequestParam(value = "page", required = false, defaultValue = "1") int page, + @RequestParam(value = "showLocation", required = false, defaultValue = "false") boolean showLocation) { + return ResponseUtil.success(visitorService.visitorPage(page, count, showLocation)); + } + + @PostMapping("/visit") + public Response add(HttpServletRequest request) { + return ResponseUtil.success(visitorService.addVisitor(request)); + } + + @GetMapping("/dayVisitCount") + public Response dayVisitCount() { + return ResponseUtil.success(countService.getDayVisitCount()); + } + + @GetMapping("/ip/{ip}") + public Response ipLocation(@PathVariable("ip") String ip) { + return ResponseUtil.success(visitorService.location(ip)); + } + + /** + * 获取本地访问者的ip + * + * @param request + * @return + */ + @GetMapping("/ip") + public Response getIp(HttpServletRequest request) { + return ResponseUtil.success(request.getRemoteAddr()); + } +} diff --git a/src/main/java/cn/celess/blog/controller/WebUpdateInfoController.java b/src/main/java/cn/celess/blog/controller/WebUpdateInfoController.java new file mode 100644 index 0000000..a69f4b0 --- /dev/null +++ b/src/main/java/cn/celess/blog/controller/WebUpdateInfoController.java @@ -0,0 +1,48 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.entity.Response; +import cn.celess.blog.service.WebUpdateInfoService; +import cn.celess.blog.util.ResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * @author : xiaohai + * @date : 2019/05/12 13:09 + */ +@RestController +public class WebUpdateInfoController { + @Autowired + WebUpdateInfoService webUpdateInfoService; + + @PostMapping("/admin/webUpdate/create") + public Response create(@RequestParam("info") String info) { + return ResponseUtil.success(webUpdateInfoService.create(info)); + } + + @DeleteMapping("/admin/webUpdate/del/{id}") + public Response del(@PathVariable("id") long id) { + return ResponseUtil.success(webUpdateInfoService.del(id)); + } + + @PutMapping("/admin/webUpdate/update") + public Response update(@RequestParam("id") long id, @RequestParam("info") String info) { + return ResponseUtil.success(webUpdateInfoService.update(id, info)); + } + + @GetMapping("/webUpdate") + public Response findAll() { + return ResponseUtil.success(webUpdateInfoService.findAll()); + } + + @GetMapping("/webUpdate/pages") + public Response page(@RequestParam("page") int page, @RequestParam("count") int count) { + return ResponseUtil.success(webUpdateInfoService.pages(count, page)); + } + @GetMapping("/lastestUpdateTime") + public Response lastestUpdateTime() { + return ResponseUtil.success(webUpdateInfoService.getLastestUpdateTime()); + } + + +} diff --git a/src/main/java/cn/celess/blog/enmu/LevelEnum.java b/src/main/java/cn/celess/blog/enmu/LevelEnum.java new file mode 100644 index 0000000..feb9d2c --- /dev/null +++ b/src/main/java/cn/celess/blog/enmu/LevelEnum.java @@ -0,0 +1,27 @@ +package cn.celess.blog.enmu; + +import lombok.Getter; + +/** + * @Author: 小海 + * @Date: 2019/06/29 00:00 + * @Description: 文章数据模型转换的级别(响应参数的选择) + */ +@Getter +public enum LevelEnum { + //低级 + LOW(0), + //中级 + MIDDLE(1), + //另一个级别的转化 + BETWEEN_M_AND_H(2), + //高级 + HEIGHT(3); + + private int levelCode; + + LevelEnum(int levelCode) { + this.levelCode = levelCode; + } + +} diff --git a/src/main/java/cn/celess/blog/enmu/ResponseEnum.java b/src/main/java/cn/celess/blog/enmu/ResponseEnum.java new file mode 100644 index 0000000..0e16b0f --- /dev/null +++ b/src/main/java/cn/celess/blog/enmu/ResponseEnum.java @@ -0,0 +1,81 @@ +package cn.celess.blog.enmu; + +/** + * @author : xiaohai + * @date : 2019/03/28 15:37 + */ +public enum ResponseEnum { + // Response enum + + SUCCESS(0, "成功"), + FAILURE(-1, "失败"), + ERROR(-2, "错误"), + + //文章类 + ARTICLE_NOT_EXIST(201, "文章不存在"), + ARTICLE_HAS_EXIST(202, "文章已存在"), + ARTICLE_NOT_PUBLIC(203, "文章暂未公开"), + ARTICLE_NOT_BELONG_YOU(204, "无权限操作别人的文章"), + + //用户类 + HAVE_NOT_LOG_IN(301, "还未登录"), + PERMISSION_ERROR(302, "没有此权限"), + USER_NOT_EXIST(303, "用户不存在"), + USERNAME_HAS_EXIST(304, "用户名已存在"), + USERNAME_TOO_SHORT(305, "用户名太短"), + PASSWORD_TOO_SHORT_OR_LONG(306, "密码长度过长或者过短"), + LOGIN_FAILURE(310, "登录失败,用户名/密码不正确"), + USEREMAIL_NULL(331, "未设置邮箱"), + USEREMAIL_NOT_VERIFY(332, "邮箱未验证"), + LOGIN_LATER(350, "错误次数已达5次,请稍后再试"), + PWD_SAME(360, "新密码与原密码相同"), + LOGIN_EXPIRED(370, "登陆过期"), + + //标签 + TAG_NOT_EXIST(401, "标签不存在"), + TAG_HAS_EXIST(402, "标签已存在"), + + //分类 + CATEGORY_NOT_EXIST(501, "分类不存在"), + CATEGORY_HAS_EXIST(502, "分类已存在"), + + //评论/留言 + COMMENT_NOT_EXIST(601, "评论/留言不存在"), + COMMENT_HAS_EXIST(602, "评论/留言已存在,请不要重复提交"), + + //webUdpateInfo amd PartnerSite + DATA_NOT_EXIST(701, "数据不存在"), + DATA_HAS_EXIST(702, "数据已存在"), + + //其他 + + //提交更新之前,没有获取数据/, + DID_NOT_GET_THE_DATA(802, "非法访问"), + IMG_CODE_TIMEOUT(810, "验证码已失效"), + IMG_CODE_DIDNOTVERIFY(820, "请先验证验证码"), + VERIFY_ERROR(830, "验证失败"), + PARAMETERS_ERROR(850, "参数错误"), + PARAMETERS_URL_ERROR(851, "链接格式错误"), + PARAMETERS_EMAIL_ERROR(852, "邮箱格式错误"), + PARAMETERS_PHONE_ERROR(853, "手机格式错误"), + PARAMETERS_QQ_ERROR(854, "QQ格式错误"), + PARAMETERS_PWD_ERROR(855, "密码格式错误"), + VERIFY_OUT(840, "已经验证过了"); + + private int code; + private String msg; + + + ResponseEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/src/main/java/cn/celess/blog/entity/Article.java b/src/main/java/cn/celess/blog/entity/Article.java new file mode 100644 index 0000000..29b865c --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Article.java @@ -0,0 +1,61 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/03/28 14:51 + */ +@Data +public class Article { + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 摘要 + */ + private String summary; + + /** + * Markdown正文 + */ + private String mdContent; + + /** + * 文章类型 true(1)为原创 false(0)为转载 + */ + private Boolean type; + + /** + * 若为转载 则为转载文章的url + */ + private String url = null; + + private Date publishDate; + + private Date updateDate = null; + + private Long categoryId; + + private String tagsId; + + private Long authorId; + + private Long preArticleId; + + private Long nextArticleId; + + private Long readingNumber; + + /** + * 文章的状态 true:公开 false:不公开 + */ + private Boolean open; + +} diff --git a/src/main/java/cn/celess/blog/entity/Category.java b/src/main/java/cn/celess/blog/entity/Category.java new file mode 100644 index 0000000..b72c9a0 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Category.java @@ -0,0 +1,20 @@ +package cn.celess.blog.entity; + + +import lombok.Data; + + +/** + * @author : xiaohai + * @date : 2019/03/28 22:18 + */ +@Data +public class Category { + + private Long id; + + private String name; + + private String articles; + +} diff --git a/src/main/java/cn/celess/blog/entity/Comment.java b/src/main/java/cn/celess/blog/entity/Comment.java new file mode 100644 index 0000000..90b480f --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Comment.java @@ -0,0 +1,40 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/03/29 16:47 + */ + +@Data +public class Comment { + + private Long id; + + /** + * 是评论还是留言 0:评论 其他(1):留言 + */ + private Boolean type; + + private Long authorID; + + private String content; + + private Long articleID; + + private Date date; + + /** + * 回应着ID 默认为顶级回复 + */ + private String responseId = ""; + + /** + * 评论的父ID + */ + private Long pid; + +} diff --git a/src/main/java/cn/celess/blog/entity/PartnerSite.java b/src/main/java/cn/celess/blog/entity/PartnerSite.java new file mode 100644 index 0000000..c1a1493 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/PartnerSite.java @@ -0,0 +1,30 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +/** + * 友链 + * + * @author : xiaohai + * @date : 2019/05/12 11:33 + */ +@Data +public class PartnerSite { + + private Long id; + + private String name; + + private String url; + + private Boolean open; + + public PartnerSite() { + } + + public PartnerSite(String name, String url, Boolean open) { + this.name = name; + this.url = url; + this.open = open; + } +} diff --git a/src/main/java/cn/celess/blog/entity/Response.java b/src/main/java/cn/celess/blog/entity/Response.java new file mode 100644 index 0000000..f2fa23f --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Response.java @@ -0,0 +1,34 @@ +package cn.celess.blog.entity; + +import lombok.Data; +import net.sf.json.JSONObject; + +import java.io.Serializable; + +/** + * @author : xiaohai + * @date : 2019/03/28 15:24 + */ +@Data +public class Response implements Serializable { + private int code; + private String msg; + private Object result; + private long date; + + public Response() { + } + + public Response(int code, String msg, Object result, long date) { + this.code = code; + this.msg = msg; + this.result = result; + this.date = date; + } + + @Override + public String toString() { + JSONObject jsonObject = JSONObject.fromObject(this); + return jsonObject.toString(); + } +} diff --git a/src/main/java/cn/celess/blog/entity/Tag.java b/src/main/java/cn/celess/blog/entity/Tag.java new file mode 100644 index 0000000..58526a4 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Tag.java @@ -0,0 +1,16 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/03/28 22:19 + */ +@Data +public class Tag { + private Long id; + + private String name; + + private String articles; +} diff --git a/src/main/java/cn/celess/blog/entity/User.java b/src/main/java/cn/celess/blog/entity/User.java new file mode 100644 index 0000000..7a3d9dd --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/User.java @@ -0,0 +1,60 @@ +package cn.celess.blog.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/03/28 14:52 + */ +@Data +public class User { + private Long id; + + /** + * 邮箱 + */ + private String email; + + /** + * 用户唯一标识码 + */ + @JsonIgnore + private String uid; + + /** + * 密码 + */ + @JsonIgnore + private String pwd; + + /** + * 昵称 + */ + private String displayName; + + private Boolean emailStatus = false; + + /** + * 头像地址 + */ + private String avatarImgUrl; + + private String desc; + + private Date recentlyLandedDate; + + /** + * 随机码 用户验证邮箱/找回密码 + * 暂时废弃这一字段 + */ + private String emailVerifyId; + + private String role = "user"; + + public User() { + } + +} diff --git a/src/main/java/cn/celess/blog/entity/Visitor.java b/src/main/java/cn/celess/blog/entity/Visitor.java new file mode 100644 index 0000000..2092745 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/Visitor.java @@ -0,0 +1,27 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/04/02 22:14 + */ +@Data +public class Visitor { + + private long id; + private String ip; + private Date date; + private String ua; + + public Visitor(String ip, Date date, String ua) { + this.ip = ip; + this.date = date; + this.ua = ua; + } + + public Visitor() { + } +} diff --git a/src/main/java/cn/celess/blog/entity/WebUpdate.java b/src/main/java/cn/celess/blog/entity/WebUpdate.java new file mode 100644 index 0000000..8cb3ee6 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/WebUpdate.java @@ -0,0 +1,27 @@ +package cn.celess.blog.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:29 + */ +@Data +public class WebUpdate { + + private long id; + + private String updateInfo; + + private Date updateTime; + + public WebUpdate() { + } + + public WebUpdate(String updateInfo, Date updateTime) { + this.updateInfo = updateInfo; + this.updateTime = updateTime; + } +} diff --git a/src/main/java/cn/celess/blog/entity/model/ArticleModel.java b/src/main/java/cn/celess/blog/entity/model/ArticleModel.java new file mode 100644 index 0000000..22cd274 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/ArticleModel.java @@ -0,0 +1,96 @@ +package cn.celess.blog.entity.model; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * @author : xiaohai + * @date : 2019/04/23 12:02 + */ +@Getter +@Setter +public class ArticleModel { + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 摘要 + */ + private String summary; + + /** + * Markdown正文 + */ + private String mdContent; + + /** + * 文章类型 true(1)为原创 false(0)为转载 + */ + private Boolean original; + + /** + * 若为转载 则为转载文章的url + */ + private String url; + + /** + * 发布时间 + */ + private String publishDateFormat; + + /** + * 更新时间 + */ + private String updateDateFormat; + + /** + * 分类 + */ + private String category; + + /** + * 标签 + */ + private String[] tags; + + /** + * 作者 + */ + private Long authorId; + + /** + * 作者名字 + */ + private String authorName; + + /** + * 上一篇文章 + */ + private Long preArticleId; + + /** + * 下一篇文章 + */ + private Long nextArticleId; + + private String preArticleTitle; + + private String nextArticleTitle; + + /** + * 阅读数 + */ + private Long readingNumber; + + /** + * 文章的状态 true:公开 false:不公开 + */ + private Boolean open; + +} diff --git a/src/main/java/cn/celess/blog/entity/model/CommentModel.java b/src/main/java/cn/celess/blog/entity/model/CommentModel.java new file mode 100644 index 0000000..93201db --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/CommentModel.java @@ -0,0 +1,57 @@ +package cn.celess.blog.entity.model; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + * @author : xiaohai + * @date : 2019/04/22 21:50 + */ +@Setter +@Getter +public class CommentModel { + private long id; + + /** + * 是评论还是留言 0:评论 其他(1):留言 + */ + private boolean isComment; + + private String authorName; + + private String authorAvatarImgUrl; + + /** + * 内容 + */ + private String content; + + /** + * 文章ID + */ + private long articleID; + + /** + * 文章标题 + */ + private String articleTitle; + + /** + * 发布日期 + */ + private String date; + + /** + * 回应着ID 默认为顶级回复 + */ + private String responseId = ""; + + /** + * 评论的父ID + */ + private long pid = -1; + + +} diff --git a/src/main/java/cn/celess/blog/entity/model/QiniuResponse.java b/src/main/java/cn/celess/blog/entity/model/QiniuResponse.java new file mode 100644 index 0000000..2048f14 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/QiniuResponse.java @@ -0,0 +1,13 @@ +package cn.celess.blog.entity.model; + + +/** + * @author : xiaohai + * @date : 2019/04/21 22:43 + */ +public class QiniuResponse { + public String key; + public String hash; + public String bucket; + public long fsize; +} diff --git a/src/main/java/cn/celess/blog/entity/model/UserModel.java b/src/main/java/cn/celess/blog/entity/model/UserModel.java new file mode 100644 index 0000000..4e240e6 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/UserModel.java @@ -0,0 +1,40 @@ +package cn.celess.blog.entity.model; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author : xiaohai + * @date : 2019/04/22 23:13 + */ +@Getter +@Setter +public class UserModel { + + private Long id; + + /** + * 邮箱 + */ + private String email; + + /** + * 昵称 + */ + private String displayName; + + private Boolean emailStatus = false; + + /** + * 头像地址 + */ + private String avatarImgUrl; + + private String desc; + + private String recentlyLandedDate; + + private String role = "user"; + + private String token; +} diff --git a/src/main/java/cn/celess/blog/entity/model/VisitorModel.java b/src/main/java/cn/celess/blog/entity/model/VisitorModel.java new file mode 100644 index 0000000..6afd55c --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/VisitorModel.java @@ -0,0 +1,24 @@ +package cn.celess.blog.entity.model; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/05/05 16:05 + */ +@Data +public class VisitorModel { + private long id; + + private String ip; + + private String date; + + private String browserName; + + private String browserVersion; + + private String OSName; + + private String location; +} diff --git a/src/main/java/cn/celess/blog/entity/model/WebUpdateModel.java b/src/main/java/cn/celess/blog/entity/model/WebUpdateModel.java new file mode 100644 index 0000000..0aecf09 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/model/WebUpdateModel.java @@ -0,0 +1,24 @@ +package cn.celess.blog.entity.model; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:32 + */ +@Data +@NoArgsConstructor +public class WebUpdateModel { + private long id; + + private String info; + + private String time; + + public WebUpdateModel(long id, String info, String time) { + this.id = id; + this.info = info; + this.time = time; + } +} diff --git a/src/main/java/cn/celess/blog/entity/request/ArticleReq.java b/src/main/java/cn/celess/blog/entity/request/ArticleReq.java new file mode 100644 index 0000000..ae30c2c --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/request/ArticleReq.java @@ -0,0 +1,19 @@ +package cn.celess.blog.entity.request; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/06/01 22:46 + */ +@Data +public class ArticleReq { + private Long id; + private String title; + private String mdContent; + private String tags; + private Boolean type; + private String url; + private String category; + private Boolean open = true; +} diff --git a/src/main/java/cn/celess/blog/entity/request/CommentReq.java b/src/main/java/cn/celess/blog/entity/request/CommentReq.java new file mode 100644 index 0000000..a310c30 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/request/CommentReq.java @@ -0,0 +1,17 @@ +package cn.celess.blog.entity.request; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/06/02 10:35 + */ +@Data +public class CommentReq { + private Long id; + private Boolean comment; + private String content; + private Long pid; + private Long articleID; + private String responseId; +} diff --git a/src/main/java/cn/celess/blog/entity/request/LinkReq.java b/src/main/java/cn/celess/blog/entity/request/LinkReq.java new file mode 100644 index 0000000..137673e --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/request/LinkReq.java @@ -0,0 +1,15 @@ +package cn.celess.blog.entity.request; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/06/02 11:40 + */ +@Data +public class LinkReq { + private long id; + private String name; + private String url; + private boolean open; +} diff --git a/src/main/java/cn/celess/blog/entity/request/LoginReq.java b/src/main/java/cn/celess/blog/entity/request/LoginReq.java new file mode 100644 index 0000000..d0f8813 --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/request/LoginReq.java @@ -0,0 +1,19 @@ +package cn.celess.blog.entity.request; + +import lombok.Data; + +/** + * @author : xiaohai + * @date : 2019/06/01 22:47 + */ +@Data +public class LoginReq { + private String email; + private String password; + /** + * isRememberMe默认为false + */ + private Boolean isRememberMe = false; + +} + diff --git a/src/main/java/cn/celess/blog/entity/request/UserReq.java b/src/main/java/cn/celess/blog/entity/request/UserReq.java new file mode 100644 index 0000000..5bbad0b --- /dev/null +++ b/src/main/java/cn/celess/blog/entity/request/UserReq.java @@ -0,0 +1,26 @@ +package cn.celess.blog.entity.request; + +import lombok.Data; + +/** + * @Author: 小海 + * @Date: 2019/09/06 13:33 + * @Description: + */ +@Data +public class UserReq { + private Long id; + + private String email; + + private String pwd; + + private String displayName; + + private Boolean emailStatus; + + private String desc; + + private String role; + +} diff --git a/src/main/java/cn/celess/blog/exception/ExceptionHandle.java b/src/main/java/cn/celess/blog/exception/ExceptionHandle.java new file mode 100644 index 0000000..480a686 --- /dev/null +++ b/src/main/java/cn/celess/blog/exception/ExceptionHandle.java @@ -0,0 +1,95 @@ +package cn.celess.blog.exception; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Response; +import cn.celess.blog.service.MailService; +import cn.celess.blog.util.DateFormatUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author : xiaohai + * @date : 2019/03/28 17:02 + */ + +@ControllerAdvice +public class ExceptionHandle { + @Autowired + MailService mailService; + @Autowired + HttpServletRequest request; + public static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); + + @Value("${spring.profiles.active}") + private String activeModel; + + @ExceptionHandler(value = Exception.class) + @ResponseBody + public Response handle(Exception e) { + //自定义错误 + if (e instanceof MyException) { + logger.debug("返回了自定义的exception,[code={},msg={}]", ((MyException) e).getCode(), e.getMessage()); + return new Response(((MyException) e).getCode(), e.getMessage(), null, System.currentTimeMillis()); + } + //请求路径不支持该方法 + if (e instanceof HttpRequestMethodNotSupportedException) { + logger.debug("遇到请求路径与请求方法不匹配的请求,[msg={},path:{},method:{}]", e.getMessage(),request.getRequestURL(),request.getMethod()); + return new Response(ResponseEnum.ERROR.getCode(), e.getMessage(), null, System.currentTimeMillis()); + } + //数据输入类型不匹配 + if (e instanceof MethodArgumentTypeMismatchException) { + logger.debug("输入类型不匹配,[msg={}]", e.getMessage()); + return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改后再访问", null, System.currentTimeMillis()); + } + //数据验证失败 + if (e instanceof BindException) { + logger.debug("数据验证失败,[msg={}]", e.getMessage()); + return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改", null, System.currentTimeMillis()); + } + //数据输入不完整 + if (e instanceof MissingServletRequestParameterException) { + logger.debug("数据输入不完整,[msg={}]", e.getMessage()); + return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入不完整,请检查", null, System.currentTimeMillis()); + } + + // 发送错误信息到邮箱 + if ("prod".equals(activeModel)) { + logger.debug("有一个未捕获的bug,已发送到邮箱"); + sendMessage(e); + } + e.printStackTrace(); + return new Response(ResponseEnum.ERROR.getCode(), "服务器出现错误,已记录", null, System.currentTimeMillis()); + } + + /** + * 发送错误信息 + * + * @param e 错误 + */ + private void sendMessage(Exception e) { + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setTo("a@celess.cn"); + simpleMailMessage.setSubject("服务器出现了错误"); + StringBuilder msg = new StringBuilder(); + msg.append("requirePath:\n").append(request.getRequestURL().toString()).append("?").append(request.getQueryString()).append("\n\n\n"); + msg.append("msg:\n").append(e.getMessage()).append("\n\n\n"); + msg.append("date:\n").append(DateFormatUtil.getNow()).append("\n\n\n"); + msg.append("from:\n").append(request.getHeader("User-Agent")).append("\n\n\n"); + msg.append("ip:\n").append(request.getRemoteAddr()).append("\n\n\n"); + simpleMailMessage.setText(msg.toString()); + mailService.AsyncSend(simpleMailMessage); + } + +} diff --git a/src/main/java/cn/celess/blog/exception/MyException.java b/src/main/java/cn/celess/blog/exception/MyException.java new file mode 100644 index 0000000..7c4d412 --- /dev/null +++ b/src/main/java/cn/celess/blog/exception/MyException.java @@ -0,0 +1,34 @@ +package cn.celess.blog.exception; + +import cn.celess.blog.enmu.ResponseEnum; + +/** + * @author : xiaohai + * @date : 2019/03/28 16:56 + */ +public class MyException extends RuntimeException { + private int code; + + public MyException(int code, String msg) { + super(msg); + this.code = code; + } + + public MyException(ResponseEnum e) { + super(e.getMsg()); + this.code = e.getCode(); + } + + public MyException(ResponseEnum e, String msg) { + super(msg + e.getMsg()); + this.code = e.getCode(); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } +} diff --git a/src/main/java/cn/celess/blog/mapper/ArticleMapper.java b/src/main/java/cn/celess/blog/mapper/ArticleMapper.java new file mode 100644 index 0000000..aef3de4 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/ArticleMapper.java @@ -0,0 +1,58 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.Article; +import org.apache.ibatis.annotations.*; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/06/27 20:43 + * @Description: + */ +@Mapper +@Repository +public interface ArticleMapper { + + int insert(Article a); + + int delete(long id); + + int update(Article a); + + int updateNextArticleId(long targetArticleID, long nextArticleID); + + int updatePreArticleId(long targetArticleID, long preArticleID); + + long getLastestArticleId(); + + Article getLastestArticle(); + + Article findArticleById(long id); + + boolean existsByTitle(String title); + + boolean existsById(long id); + + List
findAllByAuthorId(long authorID); + + List
findAllByOpen(boolean isOpen); + + String getTitleById(long id); + + List
findAllByCategoryId(long id); + + List
findAll(); + + Article getSimpleInfo(long id); + + List
getSimpleInfoByCategory(long categoryId); + + List
getSimpleInfoByTag(List idList); + + int setReadingNumber(long number, long id); + + long count(); + +} diff --git a/src/main/java/cn/celess/blog/mapper/CategoryMapper.java b/src/main/java/cn/celess/blog/mapper/CategoryMapper.java new file mode 100644 index 0000000..2c3f941 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/CategoryMapper.java @@ -0,0 +1,42 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.Category; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/06/30 12:56 + * @Description: + */ +@Mapper +@Repository +public interface CategoryMapper { + int insert(Category c); + + int delete(long id); + + int update(Category c); + + boolean existsByName(String name); + + boolean existsById(long id); + + Category findCategoryByName(String name); + + Category findCategoryById(long id); + + List findAll(); + + List getAllName(); + + String getNameById(long id); + + Long getIDByName(String name); + + Category getLastestCategory(); + + long count(); +} diff --git a/src/main/java/cn/celess/blog/mapper/CommentMapper.java b/src/main/java/cn/celess/blog/mapper/CommentMapper.java new file mode 100644 index 0000000..4b0bd7f --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/CommentMapper.java @@ -0,0 +1,48 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.Comment; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/06/30 16:19 + * @Description: + */ +@Mapper +@Repository +public interface CommentMapper { + int insert(Comment c); + + int updateContent(String content, long id); + + int updateResponder(String responder, long id); + + int delete(long id); + + int deleteByArticleId(long articleId); + + boolean existsById(long id); + + Comment findCommentById(long id); + + Comment getLastestComment(); + + List findAllByAuthorIDAndType(long id, boolean isComment); + + List findAllByPId(long pid); + + List findAllByArticleID(long articleId); + + List findAllByArticleIDAndPId(long articleID, long pid); + + List findCommentsByTypeAndPId(boolean isComment, long pid); + + List findAllByPId(int pid); + + List findAllByType(boolean isComment); + + long countByType(boolean isComment); +} diff --git a/src/main/java/cn/celess/blog/mapper/PartnerMapper.java b/src/main/java/cn/celess/blog/mapper/PartnerMapper.java new file mode 100644 index 0000000..4785ee5 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/PartnerMapper.java @@ -0,0 +1,40 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.PartnerSite; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/07/03 00:22 + * @Description: + */ +@Mapper +@Repository +public interface PartnerMapper { + int insert(PartnerSite site); + + int delete(long id); + + int update(PartnerSite site); + + boolean existsById(long id); + + boolean existsByName(String name); + + boolean existsByUrl(String url); + + PartnerSite findById(long id); + + PartnerSite findByName(String name); + + PartnerSite findByUrl(String url); + + PartnerSite getLastest(); + + List findAll(); + + +} diff --git a/src/main/java/cn/celess/blog/mapper/TagMapper.java b/src/main/java/cn/celess/blog/mapper/TagMapper.java new file mode 100644 index 0000000..4877747 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/TagMapper.java @@ -0,0 +1,38 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.Tag; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/06/29 22:00 + * @Description: + */ +@Mapper +@Repository +public interface TagMapper { + int insert(Tag tag); + + int update(Tag tag); + + int delete(long id); + + Tag findTagById(long id); + + Tag findTagByName(String name); + + Boolean existsByName(String name); + + Long getIDByName(String name); + + String getNameById(long id); + + Tag getLastestTag(); + + List findAll(); + + long count(); +} diff --git a/src/main/java/cn/celess/blog/mapper/UserMapper.java b/src/main/java/cn/celess/blog/mapper/UserMapper.java new file mode 100644 index 0000000..6419da4 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/UserMapper.java @@ -0,0 +1,58 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.User; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.Date; +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/07/03 00:23 + * @Description: + */ +@Mapper +@Repository +public interface UserMapper { + + int addUser(String email, String pwd); + + int updateInfo(String desc, String displayName, long id); + + int updateAvatarImgUrl(String avatarImgUrl, long id); + + int updateLoginTime(String email, Date date); + + int updateEmailStatus(String email, boolean status); + + int updatePwd(String email, String pwd); + + String getPwd(String email); + + boolean existsByEmail(String email); + + User findByEmail(String email); + + User findById(long id); + + String getAvatarImgUrlById(long id); + + String getEmail(long id); + + String getDisPlayName(long id); + + String getRoleByEmail(String email); + + String getRoleById(long id); + + long count(); + + int delete(long id); + + int setUserRole(Long uid, String role); + + List findAll(); + + int update(User user); +} diff --git a/src/main/java/cn/celess/blog/mapper/VisitorMapper.java b/src/main/java/cn/celess/blog/mapper/VisitorMapper.java new file mode 100644 index 0000000..ed84320 --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/VisitorMapper.java @@ -0,0 +1,24 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.Visitor; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/07/03 00:23 + * @Description: + */ +@Repository +@Mapper +public interface VisitorMapper { + int insert(Visitor visitor); + + int delete(long id); + + List findAll(); + + long count(); +} diff --git a/src/main/java/cn/celess/blog/mapper/WebUpdateInfoMapper.java b/src/main/java/cn/celess/blog/mapper/WebUpdateInfoMapper.java new file mode 100644 index 0000000..612fc9d --- /dev/null +++ b/src/main/java/cn/celess/blog/mapper/WebUpdateInfoMapper.java @@ -0,0 +1,31 @@ +package cn.celess.blog.mapper; + +import cn.celess.blog.entity.WebUpdate; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.Date; +import java.util.List; + +/** + * @Author: 小海 + * @Date: 2019/07/03 00:24 + * @Description: + */ +@Mapper +@Repository +public interface WebUpdateInfoMapper { + int insert(WebUpdate webUpdate); + + int delete(long id); + + int update(long id, String info); + + boolean existsById(long id); + + WebUpdate findById(long id); + + List findAll(); + + Date getLastestOne(); +} diff --git a/src/main/java/cn/celess/blog/service/ArticleService.java b/src/main/java/cn/celess/blog/service/ArticleService.java new file mode 100644 index 0000000..36f706e --- /dev/null +++ b/src/main/java/cn/celess/blog/service/ArticleService.java @@ -0,0 +1,85 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.model.ArticleModel; +import cn.celess.blog.entity.request.ArticleReq; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + + +/** + * @author : xiaohai + * @date : 2019/03/28 15:20 + */ +@Service +public interface ArticleService { + /** + * 新增一篇文章 + * + * @param reqBody 请求文章的数据 + * @return 文章数据 + */ + ArticleModel create(ArticleReq reqBody); + + /** + * 删除一篇文章 + * + * @param articleID 文章id + * @return 删除状态 true:删除成功 false:失败 + */ + boolean delete(long articleID); + + /** + * 更新一篇文章 + * + * @param reqBody 请求数据 + * @return 文章数据 + */ + ArticleModel update(ArticleReq reqBody); + + /** + * 获取一篇文章的数据 + * + * @param articleID 文章id + * @param is4update 是否是因文章更新而请求数据 + * @return 文章数据 + */ + ArticleModel retrieveOneByID(long articleID, boolean is4update); + + /** + * 管理员 获取分页数据 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo adminArticles(int count, int page); + + /** + * 获取文章状态为开放的文章 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageForOpen(int count, int page); + + /** + * 根据分类名获取文章数据 + * + * @param name 分类名 + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo findByCategory(String name, int page, int count); + + /** + * 根据标签名获取文章数据 + * + * @param name 标签名 + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo findByTag(String name, int page, int count); +} diff --git a/src/main/java/cn/celess/blog/service/CategoryService.java b/src/main/java/cn/celess/blog/service/CategoryService.java new file mode 100644 index 0000000..561b99f --- /dev/null +++ b/src/main/java/cn/celess/blog/service/CategoryService.java @@ -0,0 +1,54 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.Category; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/28 22:42 + */ +@Service +public interface CategoryService { + /** + * 增加一个分类 + * + * @param name 分类名 + * @return 所增加的分类数据 + */ + Category create(String name); + + /** + * 增加一个分类 + * + * @param category 分类对象 + * @return 所增加的分类数据 + */ + Category create(Category category); + + /** + * 通过id删除分类 + * + * @param id 分类id + * @return 删除状态 + */ + boolean delete(long id); + + /** + * 编辑分类的名字 + * + * @param id 分类id + * @param name 分类名字 + * @return 更新后的分类的数据 + */ + Category update(Long id, String name); + + /** + * 获取全部的分类数据 + * + * @return 全部的分类数据 + */ + List retrievePage(); + +} diff --git a/src/main/java/cn/celess/blog/service/CommentService.java b/src/main/java/cn/celess/blog/service/CommentService.java new file mode 100644 index 0000000..b52a514 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/CommentService.java @@ -0,0 +1,101 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.model.CommentModel; +import cn.celess.blog.entity.request.CommentReq; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + +/** + * @author : xiaohai + * @date : 2019/03/29 16:58 + */ +@Service +public interface CommentService { + /** + * 新增数据 + * + * @param reqBody 请求数据体 + * @return 增加的comment数据 + */ + CommentModel create(CommentReq reqBody); + + /** + * 删除数据 + * + * @param id comment的id + * @return 删除状态 + */ + boolean delete(long id); + + /** + * 更新数据 + * + * @param reqBody comment请求体 + * @return 更新后的数据 + */ + CommentModel update(CommentReq reqBody); + + /** + * 分页获取数据 + * + * @param isComment true:评论 false:留言 + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePage(Boolean isComment, int page, int count); + + /** + * 通过pid获取数据 + * + * @param pid 父id + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageByPid(long pid, int page, int count); + + + /** + * 根据评论者获取数据 + * + * @param isComment true:评论 false:留言 + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageByAuthor(Boolean isComment, int page, int count); + + /** + * 根据文章获取数据 + * + * @param articleID 文章id + * @param pid 父id + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageByArticle(long articleID, long pid, int page, int count); + + /** + * 根据数据的type和pid获取数据 + * + * @param isComment true:评论 false:留言 + * @param pid 父id + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageByTypeAndPid(Boolean isComment, int pid, int page, int count); + + /** + * 根据type获取数据 + * + * @param isComment true:评论 false:留言 + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePageByType(Boolean isComment, int page, int count); + +} diff --git a/src/main/java/cn/celess/blog/service/CountService.java b/src/main/java/cn/celess/blog/service/CountService.java new file mode 100644 index 0000000..b553031 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/CountService.java @@ -0,0 +1,66 @@ +package cn.celess.blog.service; + +import org.springframework.stereotype.Service; + +/** + * @author : xiaohai + * @date : 2019/04/02 22:04 + */ +@Service +public interface CountService { + /** + * 获取评论的数据量 + * + * @return 评论的数据量 + */ + long getCommentCount(); + + /** + * 获取文章的篇数 + * + * @return 文章的篇数 + */ + long getArticleCount(); + + /** + * 获取分类数量 + * + * @return 分类数量 + */ + long getCategoriesCount(); + + /** + * 获取标签数量 + * + * @return 标签数量 + */ + long getTagsCount(); + + /** + * 获取留言数量 + * + * @return 留言数量 + */ + long getLeaveMessageCount(); + + /** + * 获取用户量 + * + * @return 用户量 + */ + long getUserCount(); + + /** + * 获取总访问量 + * + * @return 总访问量 + */ + long getVisitorCount(); + + /** + * 获取日访问量 + * + * @return 日访问量 + */ + long getDayVisitCount(); +} diff --git a/src/main/java/cn/celess/blog/service/MailService.java b/src/main/java/cn/celess/blog/service/MailService.java new file mode 100644 index 0000000..61d8e35 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/MailService.java @@ -0,0 +1,27 @@ +package cn.celess.blog.service; + +import org.springframework.mail.SimpleMailMessage; +import org.springframework.stereotype.Service; + +/** + * @author : xiaohai + * @date : 2019/04/22 14:25 + */ +@Service +public interface MailService { + /** + * 异步发生邮件 + * + * @param message SimpleMailMessage对象 + * @return // + */ + Boolean AsyncSend(SimpleMailMessage message); + + /** + * 同步发送邮件 + * + * @param message SimpleMailMessage对象 + * @return 发送状态 + */ + Boolean send(SimpleMailMessage message); +} diff --git a/src/main/java/cn/celess/blog/service/PartnerSiteService.java b/src/main/java/cn/celess/blog/service/PartnerSiteService.java new file mode 100644 index 0000000..6874604 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/PartnerSiteService.java @@ -0,0 +1,56 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.PartnerSite; +import cn.celess.blog.entity.request.LinkReq; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:42 + */ +@Service +public interface PartnerSiteService { + /** + * 新增数据 + * + * @param reqBody 数据请求体 + * @return 新增数据 + */ + PartnerSite create(LinkReq reqBody); + + /** + * 删除数据 + * + * @param id 数据id + * @return 删除状态 + */ + Boolean del(long id); + + /** + * 更新数据 + * + * @param reqBody 数据请求体 + * @return 更新后的数据 + */ + PartnerSite update(LinkReq reqBody); + + /** + * 分页获取数据 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo PartnerSitePages(int page, int count); + + /** + * 获取全部数据 + * + * @return 全部友链数据 + */ + List findAll(); + +} diff --git a/src/main/java/cn/celess/blog/service/QiniuService.java b/src/main/java/cn/celess/blog/service/QiniuService.java new file mode 100644 index 0000000..32bdc2a --- /dev/null +++ b/src/main/java/cn/celess/blog/service/QiniuService.java @@ -0,0 +1,31 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.model.QiniuResponse; +import com.qiniu.storage.model.FileInfo; +import org.springframework.stereotype.Service; + +import java.io.InputStream; + +/** + * @author : xiaohai + * @date : 2019/04/25 18:15 + */ +@Service +public interface QiniuService { + /** + * 上传文件 + * + * @param is InputStream流 + * @param fileName 文件名 + * @return 响应数据 + */ + QiniuResponse uploadFile(InputStream is, String fileName); + + /** + * 获取文件列表 + * + * @return 文件列表 + */ + FileInfo[] getFileList(); + +} diff --git a/src/main/java/cn/celess/blog/service/TagService.java b/src/main/java/cn/celess/blog/service/TagService.java new file mode 100644 index 0000000..e9b3301 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/TagService.java @@ -0,0 +1,82 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.Tag; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/28 22:23 + */ +@Service +public interface TagService { + /** + * 新增数据 + * + * @param name 标签名 + * @return 新增后的数据 + */ + Tag create(String name); + + /** + * 新增数据 + * + * @param tag tag对象 + * @return 新增后的数据 + */ + + Tag create(Tag tag); + + /** + * 删除数据 + * + * @param tagId 标签id + * @return 删除状态 + */ + boolean delete(long tagId); + + /** + * 更新数据 + * + * @param id 标签id + * @param name 改名的name值 + * @return 更新后的数据 + */ + Tag update(Long id, String name); + + /** + * 查询单个标签信息 + * + * @param tagId id + * @return 标签的数据 + */ + Tag retrieveOneById(long tagId); + + /** + * 通过name查询标签的信息 + * + * @param name tag的名称 + * @return 标签数据 + */ + Tag retrieveOneByName(String name); + + + /** + * 分页获取标签数据 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo retrievePage(int page, int count); + + /** + * 获取全部标签数据 + * + * @return 标签数据列表 + */ + List findAll(); + +} diff --git a/src/main/java/cn/celess/blog/service/UserService.java b/src/main/java/cn/celess/blog/service/UserService.java new file mode 100644 index 0000000..c584b20 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/UserService.java @@ -0,0 +1,177 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.User; +import cn.celess.blog.entity.model.UserModel; +import cn.celess.blog.entity.request.LoginReq; +import cn.celess.blog.entity.request.UserReq; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONObject; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.io.InputStream; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/30 18:40 + */ +@Service +public interface UserService { + /** + * 注册 + * + * @param email 邮箱 + * @param password 密码 + * @return 注册状态 + */ + Boolean registration(String email, String password); + + /** + * 登录 + * + * @param loginReq 请求数据 + * @return 用户数据 + */ + UserModel login(LoginReq loginReq); + + /** + * 注销登录 + * + * @return ** + */ + Object logout(); + + /** + * 获取用户头像的链接 + * + * @param id 用户id + * @return 头像链接 + */ + String getAvatarImg(long id); + + /** + * 更新用户数据 + * + * @param desc 用户描述 + * @param displayName 显示昵称 + * @return 用户数据 + */ + UserModel update(String desc, String displayName); + + /** + * 更新头像 + * + * @param is 头像文件的输入流 + * @param mime 文件的mime + * @return 响应数据 + */ + Object updateUserAavatarImg(InputStream is, String mime); + + /** + * 获取session中存储的用户资料 + * + * @return 用户资料 + */ + UserModel getUserInfoBySession(); + + /** + * 获取用户的角色 + * + * @param email 用户的邮箱 + * @return role + */ + String getUserRoleByEmail(String email); + + /** + * 通过邮箱获取用户的信息 + * + * @param email 用户邮箱 + * @return 用户信息 + */ + User getUserInfoByEmail(String email); + + /** + * 获取邮箱是否注册过 + * + * @param email 用户邮箱 + * @return 注册状态 + */ + boolean isExistOfEmail(String email); + + /** + * 获取用户的name 优先返回displayName 否则返回email + * + * @param id 用户id + * @return name + */ + String getNameById(long id); + + /** + * 发送重置密码邮件 + * + * @param email 用户邮箱 + * @return 发送状态 + */ + Object sendResetPwdEmail(String email); + + /** + * 发送验证邮箱邮件 + * + * @param email 用户邮箱 + * @return 发送状态 + */ + Object sendVerifyEmail(String email); + + /** + * 验证邮箱 + * + * @param verifyId 验证码 + * @param email 邮箱 + * @return 验证状态 + */ + Object verifyEmail(String verifyId, String email); + + /** + * 重置密码 + * + * @param verifyId 验证码 + * @param email 邮箱 + * @param pwd 新密码 + * @return 修改状态 + */ + Object reSetPwd(String verifyId, String email, String pwd); + + /** + * 删除用户 + * + * @param id 用户id的数组 + * @return 对应id 的删除状态 + */ + Object deleteUser(Integer[] id); + + /** + * 获取用户列表 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo getUserList(Integer page, Integer count); + + /** + * 更改用户信息 + * + * @param user 用户数据 + * @return 用户信息 + */ + UserModel adminUpdate(UserReq user); + + /** + * 获取电子邮件的存在状态 + * + * @param email email + * @return true:存在 false:不存在 + */ + boolean getStatusOfEmail(String email); +} diff --git a/src/main/java/cn/celess/blog/service/VisitorService.java b/src/main/java/cn/celess/blog/service/VisitorService.java new file mode 100644 index 0000000..8f6f595 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/VisitorService.java @@ -0,0 +1,40 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.model.VisitorModel; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author : xiaohai + * @date : 2019/04/02 23:03 + */ +@Service +public interface VisitorService { + /** + * 分页获取访客数据 + * + * @param count 单页数据量 + * @param page 数据页 + * @param showLocation 是否显示位置信息 开启改选项数据响应超慢 + * @return 分页数据 + */ + PageInfo visitorPage(int page, int count, boolean showLocation); + + /** + * 新增访客 + * + * @param request HttpServletRequest + * @return 返回状态 null: 访客信息已记录、爬虫 + */ + VisitorModel addVisitor(HttpServletRequest request); + + /** + * 获取位置信息 + * + * @param ip ip地址 + * @return 位置信息 + */ + String location(String ip); +} diff --git a/src/main/java/cn/celess/blog/service/WebUpdateInfoService.java b/src/main/java/cn/celess/blog/service/WebUpdateInfoService.java new file mode 100644 index 0000000..cb6f3aa --- /dev/null +++ b/src/main/java/cn/celess/blog/service/WebUpdateInfoService.java @@ -0,0 +1,62 @@ +package cn.celess.blog.service; + +import cn.celess.blog.entity.model.WebUpdateModel; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:42 + */ +@Service +public interface WebUpdateInfoService { + /** + * 新增记录 + * + * @param info 更新内容 + * @return 创建状态 + */ + WebUpdateModel create(String info); + + /** + * 删除数据 + * + * @param id 数据id + * @return 删除状态 + */ + Boolean del(long id); + + /** + * 更新数据 + * + * @param id 数据id + * @param info 新内容 + * @return 数据 + */ + WebUpdateModel update(long id, String info); + + /** + * 分页获取更新记录 + * + * @param count 单页数据量 + * @param page 数据页 + * @return 分页数据 + */ + PageInfo pages(int count, int page); + + /** + * 获取全部的更新记录 + * + * @return 更新记录 + */ + List findAll(); + + /** + * 获取最后更新时间 + * + * @return + */ + String getLastestUpdateTime(); +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/ArticleServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/ArticleServiceImpl.java new file mode 100644 index 0000000..5c81ee0 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/ArticleServiceImpl.java @@ -0,0 +1,544 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.LevelEnum; +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.*; +import cn.celess.blog.entity.model.ArticleModel; +import cn.celess.blog.entity.request.ArticleReq; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.ArticleMapper; +import cn.celess.blog.mapper.CategoryMapper; +import cn.celess.blog.mapper.CommentMapper; +import cn.celess.blog.mapper.TagMapper; +import cn.celess.blog.service.ArticleService; +import cn.celess.blog.service.UserService; +import cn.celess.blog.util.DateFormatUtil; +import cn.celess.blog.util.RedisUserUtil; +import cn.celess.blog.util.RegexUtil; +import cn.celess.blog.util.StringFromHtmlUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.youbenzi.mdtool.tool.MDTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + + +/** + * @author : xiaohai + * @date : 2019/03/28 15:21 + */ +@Service +public class ArticleServiceImpl implements ArticleService { + public static final Logger logger = LoggerFactory.getLogger(ArticleServiceImpl.class); + + @Autowired + ArticleMapper articleMapper; + + @Autowired + TagMapper tagMapper; + @Autowired + CategoryMapper categoryMapper; + @Autowired + CommentMapper commentMapper; + @Autowired + UserService userService; + @Autowired + HttpServletRequest request; + @Autowired + RedisUserUtil redisUserUtil; + + @Override + @Transactional(rollbackFor = Exception.class) + public ArticleModel create(ArticleReq reqBody) { + if (reqBody == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //数据判断 + if (reqBody.getTitle() == null || reqBody.getTitle().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } else if (reqBody.getMdContent() == null || reqBody.getMdContent().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //转载 判断链接 + if (!reqBody.getType()) { + if (reqBody.getUrl() == null || reqBody.getUrl().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } else if (!RegexUtil.urlMatch(reqBody.getUrl())) { + throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR); + } + } + if (reqBody.getCategory() == null || reqBody.getCategory().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + if (reqBody.getTags() == null || reqBody.getTags().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + + + //写入数据库的数据 + Article article = new Article(); + article.setTitle(reqBody.getTitle()); + article.setOpen(reqBody.getOpen()); + article.setMdContent(reqBody.getMdContent()); + article.setUrl(reqBody.getUrl()); + article.setType(reqBody.getType()); + + article.setAuthorId(redisUserUtil.get(request).getId()); + article.setPublishDate(new Date()); + + //防止出现 “null,xxx”这种情况 + article.setTagsId(""); + + + //是否需要更新上一篇文章 + boolean isUpdatePreArticle = true; + + Article preArticle = null; + + + if (articleMapper.count() == 0) { + isUpdatePreArticle = false; + + + } else { + //获取最新的一条数据 + preArticle = articleMapper.getLastestArticle(); + } + + if (isUpdatePreArticle) { + logger.info("上一篇文章的id为:" + preArticle.getId()); + //设置上一篇文章的id + article.setPreArticleId(preArticle.getId()); + } + + //markdown->html->summary + String str = StringFromHtmlUtil.getString(MDTool.markdown2Html(article.getMdContent())); + //获取摘要 摘要长度为255个字符 + String summary = str.length() > 240 ? str.substring(0, 240) + "......" : str; + + //去除转换后存在的空格 + String tagStr = reqBody.getTags().replaceAll(" ", ""); + article.setSummary(summary); + + if (articleMapper.existsByTitle(article.getTitle())) { + throw new MyException(ResponseEnum.ARTICLE_HAS_EXIST); + } + + + //将分类写入数据库 + Category category1 = categoryMapper.findCategoryByName(reqBody.getCategory()); + if (category1 == null) { + category1 = new Category(); + category1.setArticles(""); + category1.setName(reqBody.getCategory()); + categoryMapper.insert(category1); + } + + article.setCategoryId(category1.getId()); + + //文章存数据库 + articleMapper.insert(article); + //获取新增的文章 + + if (isUpdatePreArticle) { + //更新上一篇文章的“下一篇文章ID” + articleMapper.updateNextArticleId(preArticle.getId(), article.getId()); + } + + //无效 + // articleMapper.updatePreArticleId(article.getId(), preArticle == null ? -1 : preArticle.getId()); + article.setPreArticleId(preArticle == null ? -1 : preArticle.getId()); + + category1.setArticles(category1.getArticles() + article.getId() + ","); + categoryMapper.update(category1); + + + //将标签写入数据库 + for (String t : tagStr.split(",")) { + if (t.replaceAll(" ", "").length() == 0) { + //单个标签只含空格 + continue; + } + Tag tag = tagMapper.findTagByName(t); + if (tag == null) { + tag = new Tag(); + tag.setName(t); + tag.setArticles(""); + tagMapper.insert(tag); + } + tag.setArticles(tag.getArticles() + article.getId() + ","); + article.setTagsId(article.getTagsId() + tag.getId() + ","); + tagMapper.update(tag); + } + articleMapper.update(article); + return fullTransform(articleMapper.getLastestArticle()); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean delete(long articleID) { + + Article articleForDel = articleMapper.findArticleById(articleID); + + if (articleForDel == null) { + throw new MyException(ResponseEnum.ARTICLE_NOT_EXIST);//文章不存在 + } + + Article preArticle = articleMapper.findArticleById(articleForDel.getPreArticleId()); + Article nextArticle = articleMapper.findArticleById(articleForDel.getNextArticleId()); + + //对访问情况进行判断 非博主/非自己文章 拒绝访问 + User user = redisUserUtil.get(request); + if (!user.getRole().contains("admin") && !articleForDel.getAuthorId().equals(user.getId())) { + throw new MyException(ResponseEnum.PERMISSION_ERROR); + } + + //删除的文章处于中间位置 + if (nextArticle != null && preArticle != null) { + + //修改上一篇文章的“下一篇文章”y + articleMapper.updateNextArticleId(articleForDel.getPreArticleId(), articleForDel.getNextArticleId()); + + //修改下一篇文章的 “上一篇文章” + articleMapper.updatePreArticleId(articleForDel.getNextArticleId(), articleForDel.getPreArticleId()); + } + if (preArticle == null && nextArticle != null) { + //删除的是第一篇文章 + articleMapper.updatePreArticleId(nextArticle.getId(), -1); + } + if (nextArticle == null && preArticle != null) { + //删除的是最后一篇文章 + articleMapper.updateNextArticleId(preArticle.getId(), -1); + } + // delete count 为删除的数据数量 + int deleteCount = commentMapper.deleteByArticleId(articleID); + + //删除标签中的文章id + String tag = articleForDel.getTagsId(); + if (tag.length() > 0) { + String[] tags = tag.split(","); + for (String t : tags) { + if (t != null) { + //查询标签 + Tag tag1 = tagMapper.findTagById(Long.parseLong(t)); + //去除标签中的articleId中的待删除的文章id + String s = tag1.getArticles().replaceAll(articleForDel.getId() + ",", ""); + tag1.setArticles(s); + tagMapper.update(tag1); + } + } + } + + + //删除分类中的文章id + //获取文章的分类 + long categoryId = articleForDel.getCategoryId(); + Category category = categoryMapper.findCategoryById(categoryId); + //删除文章id + category.setArticles(category.getArticles().replaceAll(articleForDel.getId() + ",", "")); + //更新 + categoryMapper.update(category); + + //删除指定文章 + articleMapper.delete(articleID); + return true; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public ArticleModel update(ArticleReq reqBody) { + if (reqBody == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //数据判断 + if (reqBody.getTitle() == null || reqBody.getTitle().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } else if (reqBody.getMdContent() == null || reqBody.getMdContent().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //转载 判断链接 + if (!reqBody.getType()) { + if (reqBody.getUrl() == null || reqBody.getUrl().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } else if (!RegexUtil.urlMatch(reqBody.getUrl())) { + throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR); + } + } + if (reqBody.getCategory() == null || reqBody.getCategory().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } +// 暂时不更新tags +// if (reqBody.getTags() == null || reqBody.getTags().replaceAll(" ", "").isEmpty()) { +// throw new MyException(ResponseEnum.PARAMETERS_ERROR); +// } + + //写入数据库的数据 + Article article = new Article(); + if (reqBody.getId() == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR.getCode(), "id不能为空"); + } + article.setId(reqBody.getId()); + article.setTitle(reqBody.getTitle()); + article.setOpen(reqBody.getOpen()); + article.setMdContent(reqBody.getMdContent()); + article.setUrl(reqBody.getUrl()); + article.setType(reqBody.getType()); + + + Article oldArticle = articleMapper.findArticleById(reqBody.getId()); + + Category category = categoryMapper.findCategoryById(oldArticle.getCategoryId()); + if (!(category.getName()).equals(reqBody.getCategory())) { + //修改更新之前数据 的分类 + category.setArticles(category.getArticles().replace(reqBody.getId() + ",", "")); + //更新 + categoryMapper.update(category); + + //更新 更新之后的分类 + Category category1 = categoryMapper.findCategoryByName(reqBody.getCategory()); + if (category1 == null) { + category1 = new Category(); + category1.setName(reqBody.getCategory()); + category1.setArticles(reqBody.getId() + ","); + categoryMapper.insert(category1); + } + article.setCategoryId(category1.getId()); + } else { + article.setCategoryId(oldArticle.getCategoryId()); + } + + +// String[] newTags = reqBody.getTags().replaceAll(" ", "").split(","); + +// //防止出现 ‘null2’这种情况 +// article.setTagsId(""); +// for (String t : newTags) { +// Tag tag = tagMapper.findTagByName(t); +// if (tag == null) { +// tag = new Tag(); +// tag.setName(t); +// tag.setArticles(oldArticle.getId() + ","); +// tag = tagMapper.save(tag); +// article.setTagsId(article.getTagsId() + tag.getId() + ","); +// continue; +// } +// article.setTagsId(article.getTagsId() + tag.getId() + ","); +// } + + // TODO:::: tag的更新 + article.setTagsId(oldArticle.getTagsId()); + + + article.setUpdateDate(new Date()); + // TODO::::换用beansUtil + // 设置不定参数 + article.setReadingNumber(oldArticle.getReadingNumber()); + article.setPublishDate(oldArticle.getPublishDate()); + article.setAuthorId(redisUserUtil.get(request).getId()); + article.setPreArticleId(oldArticle.getPreArticleId()); + article.setNextArticleId(oldArticle.getNextArticleId()); + String str = StringFromHtmlUtil.getString(MDTool.markdown2Html(article.getMdContent())); + article.setSummary(str.length() > 240 ? str.substring(0, 240) + "......" : str); + articleMapper.update(article); + //更新完成移除 + request.getSession().removeAttribute("article4update"); + return fullTransform(article); + } + + @Override + public ArticleModel retrieveOneByID(long articleID, boolean is4update) { + Article article = articleMapper.findArticleById(articleID); + if (article == null) { + throw new MyException(ResponseEnum.ARTICLE_NOT_EXIST); + } + if (!article.getOpen()) { + User user = redisUserUtil.getWithOutExc(request); + if (user == null || "user".equals(user.getRole())) { + throw new MyException(ResponseEnum.ARTICLE_NOT_PUBLIC); + } + } + article.setReadingNumber(article.getReadingNumber() + 1); + if (is4update) { + //因更新而获取文章 不需要增加阅读量 + request.getSession().setAttribute("article4update", article); + return fullTransform(article); + } + articleMapper.setReadingNumber(article.getReadingNumber() + 1, articleID); + return fullTransform(article); + } + + /** + * @param count 数目 + * @param page 页面 默认减1 + * @return + */ + @Override + public PageInfo adminArticles(int count, int page) { + PageHelper.startPage(page, count); + List
articleList = articleMapper.findAll(); + PageInfo pageInfo = new PageInfo(articleList); + pageInfo.setList(list2list(articleList, LevelEnum.BETWEEN_M_AND_H)); + return pageInfo; + } + + @Override + public PageInfo retrievePageForOpen(int count, int page) { + PageHelper.startPage(page, count); + List
articleList = articleMapper.findAllByOpen(true); + PageInfo pageInfo = new PageInfo(articleList); + pageInfo.setList(list2list(articleList, LevelEnum.MIDDLE)); + return pageInfo; + } + + @Override + public PageInfo findByCategory(String name, int page, int count) { + Long idByName = categoryMapper.getIDByName(name); + if (idByName == null) { + throw new MyException(ResponseEnum.CATEGORY_NOT_EXIST); + } + PageHelper.startPage(page, count); + PageInfo pageInfo = new PageInfo(articleMapper.getSimpleInfoByCategory(idByName)); + return pageInfo; + } + + @Override + public PageInfo findByTag(String name, int page, int count) { + Tag tag = tagMapper.findTagByName(name); + if (tag == null) { + throw new MyException(ResponseEnum.TAG_NOT_EXIST); + } + PageHelper.startPage(page, count); + String[] split = tag.getArticles().split(","); + List list = Arrays.asList(split); + List
articleList = articleMapper.getSimpleInfoByTag(list); + PageInfo pageInfo = new PageInfo(articleList); + return pageInfo; + } + + /** + * page转换 + * + * @param articleList 数据源 + * @param level 转换级别 + * @return list + */ + private List list2list(List
articleList, LevelEnum level) { + List content = new ArrayList<>(); + for (Article a : articleList) { + ArticleModel model; + switch (level.getLevelCode()) { + case 0: + model = simpleTransform(a); + break; + case 1: + model = suitableTransform(a); + break; + case 2: + model = suitableTransformForAdmin(a); + break; + case 3: + default: + model = fullTransform(a); + } + content.add(model); + } + return content; + } + + /** + * 简单的模型转换 + * [id,title,summary] + * + * @param a 源数据 + * @return 模型 + */ + private ArticleModel simpleTransform(Article a) { + ArticleModel model = new ArticleModel(); + model.setId(a.getId()); + model.setTitle(a.getTitle()); + model.setSummary(a.getSummary()); + + return model; + } + + /** + * 中等转换 + * [id,title,summary] + * + + * [original,tags,category] + * + * @param a + * @return + */ + private ArticleModel suitableTransform(Article a) { + ArticleModel model = simpleTransform(a); + model.setAuthorName(userService.getNameById(a.getAuthorId())); + model.setPublishDateFormat(DateFormatUtil.get(a.getPublishDate())); + model.setOriginal(a.getType()); + model.setCategory(categoryMapper.getNameById(a.getCategoryId())); + String[] split = a.getTagsId().split(","); + String[] tags = new String[split.length]; + for (int i = 0; i < split.length; i++) { + if (split[i] == null || "".equals(split[i])) { + continue; + } + tags[i] = tagMapper.getNameById(Long.parseLong(split[i])); + } + model.setTags(tags); + return model; + } + + /** + * 中等转换 for admin页面 + * [id,title] + * + + * [original,UpdateDate,open,readingNumber] + * + * @param a + * @return + */ + private ArticleModel suitableTransformForAdmin(Article a) { + ArticleModel model = simpleTransform(a); + model.setPublishDateFormat(DateFormatUtil.get(a.getPublishDate())); + model.setUpdateDateFormat(DateFormatUtil.get(a.getUpdateDate())); + model.setReadingNumber(a.getReadingNumber()); + model.setOpen(a.getOpen()); + model.setOriginal(a.getType()); + model.setSummary(null); + return model; + } + + /** + * 全转换 + * [id,title,summary,original,tags,category] + * + + * [UpdateDate,MdContent,NextArticleId,NextArticleTitle,preArticleId,preArticleTitle,open,url,readingNumber] + * + * @param a + * @return + */ + private ArticleModel fullTransform(Article a) { + ArticleModel model = suitableTransform(a); + model.setUpdateDateFormat(DateFormatUtil.get(a.getUpdateDate())); + model.setMdContent(a.getMdContent()); + model.setNextArticleId(a.getNextArticleId()); + model.setNextArticleTitle(a.getNextArticleId() == -1 ? "无" : articleMapper.getTitleById(a.getNextArticleId())); + model.setPreArticleId(a.getPreArticleId()); + model.setPreArticleTitle(a.getPreArticleId() == -1 ? "无" : articleMapper.getTitleById(a.getPreArticleId())); + model.setOpen(a.getOpen()); + model.setUrl(a.getUrl()); + model.setReadingNumber(a.getReadingNumber()); + return model; + } + +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/CategoryServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/CategoryServiceImpl.java new file mode 100644 index 0000000..3c319b5 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/CategoryServiceImpl.java @@ -0,0 +1,91 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Article; +import cn.celess.blog.entity.Category; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.ArticleMapper; +import cn.celess.blog.mapper.CategoryMapper; +import cn.celess.blog.service.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/28 22:43 + */ +@Service +public class CategoryServiceImpl implements CategoryService { + @Autowired + CategoryMapper categoryMapper; + @Autowired + HttpServletRequest request; + @Autowired + ArticleMapper articleMapper; + + @Override + public Category create(String name) { + if (categoryMapper.existsByName(name)) { + throw new MyException(ResponseEnum.CATEGORY_HAS_EXIST); + } + Category category = new Category(); + category.setName(name); + category.setArticles(""); + categoryMapper.insert(category); + return category; + } + + @Override + public Category create(Category category) { + if (category == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + categoryMapper.insert(category); + return category; + } + + @Override + public boolean delete(long id) { + Category category = categoryMapper.findCategoryById(id); + + if (category == null) { + throw new MyException(ResponseEnum.CATEGORY_NOT_EXIST); + } + String[] articleArray = category.getArticles().split(","); + for (int i = 0; i < articleArray.length; i++) { + if (articleArray[i] == null || "".equals(articleArray[i])) { + continue; + } + long articleId = Long.parseLong(articleArray[i]); + Article article = articleMapper.findArticleById(articleId); + if (article == null) { + continue; + } + article.setCategoryId(-1L); + //一个 文章只对应一个分类,分类不存在则文章默认不可见 + article.setOpen(false); + articleMapper.update(article); + } + return categoryMapper.delete(id) == 1; + } + + + @Override + public Category update(Long id, String name) { + if (id == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR.getCode(), "id不可为空"); + } + Category category = categoryMapper.findCategoryById(id); + category.setName(name); + categoryMapper.update(category); + return category; + } + + @Override + public List retrievePage() { + return categoryMapper.findAll(); + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/CommentServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/CommentServiceImpl.java new file mode 100644 index 0000000..0c54937 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/CommentServiceImpl.java @@ -0,0 +1,192 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Comment; +import cn.celess.blog.entity.model.CommentModel; +import cn.celess.blog.entity.request.CommentReq; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.ArticleMapper; +import cn.celess.blog.mapper.CommentMapper; +import cn.celess.blog.service.CommentService; +import cn.celess.blog.service.UserService; +import cn.celess.blog.util.DateFormatUtil; +import cn.celess.blog.util.RedisUserUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/29 17:05 + */ +@Service +public class CommentServiceImpl implements CommentService { + @Autowired + CommentMapper commentMapper; + @Autowired + UserService userService; + @Autowired + ArticleMapper articleMapper; + @Autowired + HttpServletRequest request; + @Autowired + RedisUserUtil redisUserUtil; + + @Override + public CommentModel create(CommentReq reqBody) { + + if (reqBody == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + long authorID = redisUserUtil.get(request).getId(); + Comment pComment = null; + if (reqBody.getPid() != null && reqBody.getPid() != -1) { + pComment = commentMapper.findCommentById(reqBody.getPid()); + } + if (reqBody.getPid() == null) { + reqBody.setPid(-1L); + } + //不是一级评论 + if (reqBody.getPid() != -1 && pComment == null) { + //父评论不存在 + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + Comment comment = new Comment(); + comment.setAuthorID(authorID); + comment.setType(reqBody.getComment()); + if (reqBody.getComment()) { + //若为评论 + if (reqBody.getArticleID() <= 0) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + comment.setArticleID(reqBody.getArticleID()); + } else { + comment.setArticleID(-1L); + } + comment.setContent(reqBody.getContent()); + comment.setPid(reqBody.getPid()); + comment.setDate(new Date()); + comment.setResponseId(""); + commentMapper.insert(comment); + if (reqBody.getPid() != -1) { + commentMapper.updateResponder(pComment.getResponseId() + comment.getId() + ",", reqBody.getPid()); + } + return trans(comment); + } + + @Override + public boolean delete(long id) { + boolean b = commentMapper.existsById(id); + if (!b) { + throw new MyException(ResponseEnum.COMMENT_NOT_EXIST); + } + commentMapper.delete(id); + return true; + } + + @Override + public CommentModel update(CommentReq reqBody) { + if (reqBody.getId() == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR.getCode(), "id不可为空"); + } + Comment comment = commentMapper.findCommentById(reqBody.getId()); + if (!comment.getContent().equals(reqBody.getContent())) { + commentMapper.updateContent(reqBody.getContent(), reqBody.getId()); + comment.setContent(reqBody.getContent()); + } + if (!comment.getResponseId().equals(reqBody.getResponseId())) { + commentMapper.updateResponder(reqBody.getResponseId(), reqBody.getId()); + comment.setResponseId(reqBody.getResponseId()); + } + return trans(comment); + } + + + @Override + public PageInfo retrievePage(Boolean isComment, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findAllByType(isComment); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + @Override + public PageInfo retrievePageByPid(long pid, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findAllByPId(pid); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + @Override + public PageInfo retrievePageByArticle(long articleID, long pid, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findAllByArticleIDAndPId(articleID, pid); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + @Override + public PageInfo retrievePageByTypeAndPid(Boolean isComment, int pid, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findCommentsByTypeAndPId(isComment, pid); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + @Override + public PageInfo retrievePageByAuthor(Boolean isComment, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findAllByAuthorIDAndType(redisUserUtil.get(request).getId(), isComment); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + + @Override + public PageInfo retrievePageByType(Boolean isComment, int page, int count) { + PageHelper.startPage(page, count); + List commentList = commentMapper.findAllByType(isComment); + PageInfo pageInfo = new PageInfo(commentList); + pageInfo.setList(list2List(commentList)); + return pageInfo; + } + + private List list2List(List commentList) { + List content = new ArrayList<>(); + for (Comment c : commentList) { + content.add(trans(c)); + } + return content; + } + + private CommentModel trans(Comment comment) { + CommentModel commentModel = new CommentModel(); + commentModel.setId(comment.getId()); + commentModel.setComment(comment.getType()); + commentModel.setContent(comment.getContent()); + commentModel.setArticleID(comment.getArticleID()); + commentModel.setDate(DateFormatUtil.get(comment.getDate())); + commentModel.setResponseId(comment.getResponseId()); + commentModel.setPid(comment.getPid()); + commentModel.setAuthorName(userService.getNameById(comment.getAuthorID())); + commentModel.setAuthorAvatarImgUrl("http://cdn.celess.cn/" + userService.getAvatarImg(comment.getAuthorID())); + + if (comment.getType() && commentModel.getArticleID() > 0) { + commentModel.setArticleTitle(articleMapper.getTitleById(comment.getArticleID())); + } + return commentModel; + } + +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/CountServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/CountServiceImpl.java new file mode 100644 index 0000000..3165768 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/CountServiceImpl.java @@ -0,0 +1,73 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.mapper.*; +import cn.celess.blog.service.CountService; +import cn.celess.blog.util.RedisUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author : xiaohai + * @date : 2019/04/02 22:06 + */ +@Service +public class CountServiceImpl implements CountService { + @Autowired + ArticleMapper articleMapper; + @Autowired + TagMapper tagMapper; + @Autowired + CommentMapper commentMapper; + @Autowired + CategoryMapper categoryMapper; + @Autowired + UserMapper userMapper; + @Autowired + VisitorMapper visitorMapper; + @Autowired + RedisUtil redisUtil; + + @Override + public long getCommentCount() { + return commentMapper.countByType(true); + } + + @Override + public long getArticleCount() { + return articleMapper.count(); + } + + @Override + public long getCategoriesCount() { + return categoryMapper.count(); + } + + @Override + public long getTagsCount() { + return tagMapper.count(); + } + + @Override + public long getLeaveMessageCount() { + return commentMapper.countByType(false); + } + + @Override + public long getUserCount() { + return userMapper.count(); + } + + @Override + public long getVisitorCount() { + return visitorMapper.count(); + } + + @Override + public long getDayVisitCount() { + String dayVisitCount = redisUtil.get("dayVisitCount"); + if (dayVisitCount == null) { + return 1; + } + return Integer.parseInt(dayVisitCount); + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/MailServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/MailServiceImpl.java new file mode 100644 index 0000000..eeab067 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/MailServiceImpl.java @@ -0,0 +1,41 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.service.MailService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; + +/** + * @author : xiaohai + * @date : 2019/04/22 14:26 + */ +@Service +public class MailServiceImpl implements MailService { + public static final String FROM = ""; + @Autowired + JavaMailSender javaMailSender; + + @Override + @Async + public Boolean AsyncSend(SimpleMailMessage message) { + this.send(message); + return true; + } + + @Override + public Boolean send(SimpleMailMessage message) { + String nick = null; + try { + nick = javax.mail.internet.MimeUtility.encodeText("小海博客"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + message.setFrom(nick + FROM); + javaMailSender.send(message); + return true; + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/PartnerSiteServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/PartnerSiteServiceImpl.java new file mode 100644 index 0000000..af47434 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/PartnerSiteServiceImpl.java @@ -0,0 +1,104 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.PartnerSite; +import cn.celess.blog.entity.request.LinkReq; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.PartnerMapper; +import cn.celess.blog.service.PartnerSiteService; +import cn.celess.blog.util.RegexUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:43 + */ +@Service +public class PartnerSiteServiceImpl implements PartnerSiteService { + @Autowired + PartnerMapper partnerMapper; + + @Override + public PartnerSite create(LinkReq reqBody) { + if (reqBody == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //判空 + if (reqBody.getName() == null || reqBody.getUrl() == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //判空 + if (reqBody.getName().replaceAll(" ", "").isEmpty() || reqBody.getUrl().replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + //是否存在 同名 + if (partnerMapper.existsByName(reqBody.getName())) { + throw new MyException(ResponseEnum.DATA_HAS_EXIST); + } + //url是否合法 + if (!RegexUtil.urlMatch(reqBody.getUrl())) { + throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR); + } + PartnerSite partnerSite = new PartnerSite(); + reqBody.setId(0); + if (!reqBody.getUrl().contains("http://") && !reqBody.getUrl().contains("https://")) { + reqBody.setUrl("http://" + reqBody.getUrl()); + } + BeanUtils.copyProperties(reqBody, partnerSite); + partnerMapper.insert(partnerSite); + return partnerSite; + } + + @Override + public Boolean del(long id) { + //判断数据是否存在 + if (!partnerMapper.existsById(id)) { + throw new MyException(ResponseEnum.DATA_NOT_EXIST); + } + return partnerMapper.delete(id) == 1; + } + + @Override + public PartnerSite update(LinkReq reqBody) { + PartnerSite partnerSite = partnerMapper.findById(reqBody.getId()); + if (partnerSite == null) { + throw new MyException(ResponseEnum.DATA_NOT_EXIST); + } + if (partnerMapper.existsByName(reqBody.getName()) && !reqBody.getName().equals(partnerSite.getName())) { + throw new MyException(ResponseEnum.DATA_HAS_EXIST); + } + if (!RegexUtil.urlMatch(reqBody.getUrl())) { + throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR); + } + if (!reqBody.getUrl().contains("http://") && !reqBody.getUrl().contains("https://")) { + reqBody.setUrl("http://" + reqBody.getUrl()); + } + partnerSite.setName(reqBody.getName()); + partnerSite.setUrl(reqBody.getUrl()); + partnerSite.setOpen(reqBody.isOpen()); + partnerMapper.update(partnerSite); + return partnerSite; + } + + @Override + public PageInfo PartnerSitePages(int page, int count) { + PageHelper.startPage(page, count); + List sitePage = partnerMapper.findAll(); + PageInfo pageInfo = new PageInfo(sitePage); + return pageInfo; + } + + @Override + public List findAll() { + List all = partnerMapper.findAll(); + return all; + } + + +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/QiniuServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/QiniuServiceImpl.java new file mode 100644 index 0000000..503aa86 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/QiniuServiceImpl.java @@ -0,0 +1,90 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.entity.model.QiniuResponse; +import cn.celess.blog.service.QiniuService; +import com.qiniu.common.QiniuException; +import com.qiniu.common.Zone; +import com.qiniu.http.Response; +import com.qiniu.storage.BucketManager; +import com.qiniu.storage.Configuration; +import com.qiniu.storage.UploadManager; +import com.qiniu.storage.model.FileInfo; +import com.qiniu.util.Auth; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import java.io.InputStream; + +/** + * @author : xiaohai + * @date : 2019/04/25 18:15 + */ +@Service +public class QiniuServiceImpl implements QiniuService { + private Configuration cfg = new Configuration(Zone.zone2()); + private UploadManager uploadManager; + private BucketManager bucketManager; + private Auth auth; + + private static String bucket; + + + { + /* ***** 必填 ****** + * 七牛的配置 * + * ***** 必填 ****** + */ + // accessKeyString,secretKeyString,bucketString:请替换为自己的值 + String accessKey = "accessKeyString"; + String secretKey = "secretKeyString"; + bucket = "bucketString"; + + auth = Auth.create(accessKey, secretKey); + uploadManager = new UploadManager(cfg); + bucketManager = new BucketManager(auth, cfg); + } + + @Override + public QiniuResponse uploadFile(InputStream is, String fileName) { + //文件存在则删除文件 + if (continueFile(fileName)) { + try { + System.out.println(bucketManager.delete(bucket, fileName).toString()); + } catch (QiniuException e) { + e.printStackTrace(); + } + } + //上传 + try { + Response response = uploadManager.put(is, fileName, auth.uploadToken(bucket), null, null); + return response.jsonToObject(QiniuResponse.class); + } catch (QiniuException e) { + Response r = e.response; + System.err.println(r.toString()); + return null; + } + } + + @Override + public FileInfo[] getFileList() { + BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(bucket, "", 1000, ""); + FileInfo[] items = null; + while (fileListIterator.hasNext()) { + //处理获取的file list结果 + items = fileListIterator.next(); + } + return items; + } + + private boolean continueFile(String key) { + FileInfo[] allFile = getFileList(); + for (FileInfo fileInfo : allFile) { + if (key.equals(fileInfo.key)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/TagServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/TagServiceImpl.java new file mode 100644 index 0000000..098c7ca --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/TagServiceImpl.java @@ -0,0 +1,120 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Article; +import cn.celess.blog.entity.Tag; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.ArticleMapper; +import cn.celess.blog.mapper.TagMapper; +import cn.celess.blog.service.TagService; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/03/28 22:29 + */ +@Service +public class TagServiceImpl implements TagService { + @Autowired + TagMapper tagMapper; + @Autowired + HttpServletRequest request; + @Autowired + ArticleMapper articleMapper; + + @Override + public Tag create(String name) { + boolean b = tagMapper.existsByName(name); + if (b) { + throw new MyException(ResponseEnum.TAG_HAS_EXIST); + } + Tag tag = new Tag(); + tag.setName(name); + tagMapper.insert(tag); + return tag; + } + + @Override + public Tag create(Tag tag) { + if (tag == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + tagMapper.insert(tag); + return tag; + } + + @Override + public boolean delete(long tagId) { + Tag tag = tagMapper.findTagById(tagId); + if (tag == null) { + throw new MyException(ResponseEnum.TAG_NOT_EXIST); + } + if (tag.getArticles()==null){ + return tagMapper.delete(tagId) == 1; + } + String[] articleArray = tag.getArticles().split(","); + for (int i = 0; i < articleArray.length; i++) { + if (articleArray[i] == null || "".equals(articleArray)) { + continue; + } + long articleID = Long.parseLong(articleArray[i]); + Article article = articleMapper.findArticleById(articleID); + if (article == null) { + continue; + } + article.setTagsId(article.getTagsId().replace(tagId + ",", "")); + articleMapper.update(article); + } + return tagMapper.delete(tagId) == 1; + } + + + @Override + public Tag update(Long id,String name) { + if (id == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR.getCode(), "缺少ID"); + } + Tag tagFromDB = tagMapper.findTagById(id); + tagFromDB.setName(name); + + tagMapper.update(tagFromDB); + return tagFromDB; + + } + + @Override + public Tag retrieveOneById(long tagId) { + Tag tag = tagMapper.findTagById(tagId); + if (tag == null) { + throw new MyException(ResponseEnum.TAG_NOT_EXIST); + } + return tag; + } + + @Override + public Tag retrieveOneByName(String name) { + Tag tag = tagMapper.findTagByName(name); + if (tag == null) { + throw new MyException(ResponseEnum.TAG_NOT_EXIST); + } + return tag; + } + + @Override + public PageInfo retrievePage(int page, int count) { + PageHelper.startPage(page, count); + PageInfo pageInfo = new PageInfo(tagMapper.findAll()); + return pageInfo; + } + + @Override + public List findAll() { + return tagMapper.findAll(); + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/UserServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/UserServiceImpl.java new file mode 100644 index 0000000..07eb7b6 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/UserServiceImpl.java @@ -0,0 +1,449 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.User; +import cn.celess.blog.entity.model.QiniuResponse; +import cn.celess.blog.entity.model.UserModel; +import cn.celess.blog.entity.request.LoginReq; +import cn.celess.blog.entity.request.UserReq; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.UserMapper; +import cn.celess.blog.service.MailService; +import cn.celess.blog.service.QiniuService; +import cn.celess.blog.service.UserService; +import cn.celess.blog.util.*; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.beans.Transient; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @author : xiaohai + * @date : 2019/03/30 18:41 + */ +@Service +public class UserServiceImpl implements UserService { + private final static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); + + @Autowired + UserMapper userMapper; + @Autowired + HttpServletRequest request; + @Autowired + MailService mailService; + @Autowired + QiniuService qiniuService; + @Autowired + RedisUtil redisUtil; + @Autowired + JwtUtil jwtUtil; + @Autowired + RedisUserUtil redisUserUtil; + + @Override + @Transient + public Boolean registration(String email, String password) { + if (password.length() < 6 || password.length() > 16) { + throw new MyException(ResponseEnum.PASSWORD_TOO_SHORT_OR_LONG); + } + if (!RegexUtil.emailMatch(email)) { + throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR); + } + if (!RegexUtil.pwdMatch(password)) { + throw new MyException(ResponseEnum.PARAMETERS_PWD_ERROR); + } + //验证码验证状态 + Boolean verifyStatus = (Boolean) request.getSession().getAttribute("verImgCodeStatus"); + if (verifyStatus == null || !verifyStatus) { + throw new MyException(ResponseEnum.IMG_CODE_DIDNOTVERIFY); + } + if (userMapper.existsByEmail(email)) { + throw new MyException(ResponseEnum.USERNAME_HAS_EXIST); + } + boolean b = userMapper.addUser(email, MD5Util.getMD5(password)) == 1; + if (b) { + String verifyId = UUID.randomUUID().toString().replaceAll("-", ""); + redisUtil.setEx(email + "-verify", verifyId, 2, TimeUnit.DAYS); + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email); + mailMessage.setSubject("邮箱验证"); + mailMessage.setText("欢迎注册小海博客,点击下面链接进行邮箱验证:\n https://www.celess.cn/emailVerify?email=" + email + "&verifyId=" + + verifyId + "\n该链接两日内有效,若失效了,请登录后台进行重新激活。"); + mailService.send(mailMessage); + } + return b; + } + + @Override + public UserModel login(LoginReq loginReq) { + if (loginReq == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + if (!RegexUtil.emailMatch(loginReq.getEmail())) { + throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR); + } + if (!RegexUtil.pwdMatch(loginReq.getPassword())) { + throw new MyException(ResponseEnum.PARAMETERS_PWD_ERROR); + } + //获取redis缓存中登录失败次数 + String s = redisUtil.get(loginReq.getEmail() + "-passwordWrongTime"); + if (s != null) { + if (Integer.parseInt(s) == 5) { + throw new MyException(ResponseEnum.LOGIN_LATER, loginReq.getEmail()); + } + } + User user = null; + user = userMapper.findByEmail(loginReq.getEmail()); + String token = null; + // 密码比对 + if (user == null) { + // 用户不存在 + throw new MyException(ResponseEnum.USER_NOT_EXIST); + } + if (user.getPwd().equals(MD5Util.getMD5(loginReq.getPassword()))) { + logger.info("====> {} 进行权限认证 状态:登录成功 <====", loginReq.getEmail()); + userMapper.updateLoginTime(loginReq.getEmail(), new Date()); + redisUtil.delete(loginReq.getEmail() + "-passwordWrongTime"); + // redis 标记 + redisUtil.setEx(loginReq.getEmail() + "-login", JSONObject.fromObject(user).toString(), + (loginReq.getIsRememberMe() ? JwtUtil.EXPIRATION_LONG_TIME : JwtUtil.EXPIRATION_SHORT_TIME), TimeUnit.MILLISECONDS); + token = jwtUtil.generateToken(user, loginReq.getIsRememberMe()); + } else { + logger.info("====> {} 进行权限认证 状态:登录失败 <====", loginReq.getEmail()); + request.getSession().removeAttribute("code"); + //登录失败 + //设置登录失败的缓存 + if (s == null) { + redisUtil.setEx(loginReq.getEmail() + "-passwordWrongTime", "1", 2, TimeUnit.HOURS); + s = "0"; + } + int count = Integer.parseInt(s); + //登录次数++ + count++; + //更新登录失败的次数 + redisUtil.setEx(loginReq.getEmail() + "-passwordWrongTime", count + "", 2, TimeUnit.HOURS); + throw new MyException(ResponseEnum.LOGIN_FAILURE); + } + UserModel trans = trans(user); + trans.setToken(token); + return trans; + + } + + @Override + public Object logout() { + String token = request.getHeader("Authorization"); + if (token == null || token.isEmpty()) { + return "注销登录成功"; + } + String email = jwtUtil.getUsernameFromToken(token); + if (redisUtil.hasKey(email + "-login")) { + redisUtil.delete(email + "-login"); + } + return "注销登录成功"; + } + + @Override + public UserModel update(String desc, String displayName) { + User user = redisUserUtil.get(request); + user.setDesc(desc); + user.setDisplayName(displayName); + + userMapper.updateInfo(desc, displayName, user.getId()); + redisUserUtil.set(user); + return trans(user); + } + + @Override + public String getUserRoleByEmail(String email) { + String role = userMapper.getRoleByEmail(email); + if (role == null) { + throw new MyException(ResponseEnum.USER_NOT_EXIST); + } + return role; + } + + @Override + public User getUserInfoByEmail(String email) { + User user = userMapper.findByEmail(email); + if (user == null) { + throw new MyException(ResponseEnum.USER_NOT_EXIST); + } + return user; + } + + @Override + public String getAvatarImg(long id) { + return userMapper.getAvatarImgUrlById(id); + } + + @Override + public Object updateUserAavatarImg(InputStream is, String mime) { + User user = redisUserUtil.get(request); + QiniuResponse upload = qiniuService.uploadFile(is, user.getEmail() + "_" + user.getId() + mime.toLowerCase()); + user.setAvatarImgUrl(upload.key); + userMapper.updateAvatarImgUrl(upload.key, user.getId()); + redisUserUtil.set(user); + return ResponseUtil.success(user.getAvatarImgUrl()); + } + + @Override + public UserModel getUserInfoBySession() { + User user = redisUserUtil.get(request); + return trans(user); + } + + @Override + public boolean isExistOfEmail(String email) { + return userMapper.existsByEmail(email); + } + + @Override + public String getNameById(long id) { + String name = userMapper.getDisPlayName(id); + if (name == null) { + name = userMapper.getEmail(id); + if (name == null) { + throw new MyException(ResponseEnum.USER_NOT_EXIST); + } + } + return name; + } + + /** + * 找回密码 + */ + @Override + public Object sendResetPwdEmail(String email) { + if (!RegexUtil.emailMatch(email)) { + throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR); + } + + User user = userMapper.findByEmail(email); + if (user == null) { + return "发送成功!"; + } + + if (!user.getEmailStatus()) { + throw new MyException(ResponseEnum.USEREMAIL_NOT_VERIFY); + } + + String verifyId = UUID.randomUUID().toString().replaceAll("-", ""); + + redisUtil.setEx(user.getEmail() + "-resetPwd", verifyId, 2, TimeUnit.DAYS); + + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email); + mailMessage.setSubject("密码重置"); + mailMessage.setText("点击下面链接进行重置密码:\n https://www.celess.cn/resetPwd?email=" + email + "&verifyId=" + verifyId); + + mailService.send(mailMessage); + return "发送成功!"; + } + + //TODO + @Override + public Object sendVerifyEmail(String email) { + if (!RegexUtil.emailMatch(email)) { + throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR); + } + + User user = userMapper.findByEmail(email); + if (user == null) { + return "发送成功!"; + } + + if (user.getEmailStatus()) { + return "已经验证过了!"; + } + + String verifyId = UUID.randomUUID().toString().replaceAll("-", ""); + + redisUtil.setEx(user.getEmail() + "-verify", verifyId, 2, TimeUnit.DAYS); + + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email); + mailMessage.setSubject("邮箱验证"); + mailMessage.setText("点击下面链接进行邮箱验证:\n https://www.celess.cn/emailVerify?email=" + email + "&verifyId=" + verifyId); + mailService.send(mailMessage); + return "发送成功!"; + } + + @Override + public Object verifyEmail(String verifyId, String email) { + User user = userMapper.findByEmail(email); + if (user == null) { + throw new MyException(ResponseEnum.FAILURE); + } + if (user.getEmailStatus()) { + throw new MyException(ResponseEnum.FAILURE.getCode(), "邮箱已验证过了"); + } + String verifyIdFromCache = redisUtil.get(user.getEmail() + "-verify"); + if (verifyIdFromCache == null) { + throw new MyException(ResponseEnum.FAILURE.getCode(), "验证链接无效"); + } + if (verifyIdFromCache.equals(verifyId)) { + userMapper.updateEmailStatus(email, true); + redisUtil.delete(user.getEmail() + "-verify"); + user.setEmailStatus(true); + redisUserUtil.set(user); + return "验证成功"; + } else { + throw new MyException(ResponseEnum.FAILURE); + } + } + + @Override + public Object reSetPwd(String verifyId, String email, String pwd) { + User user = userMapper.findByEmail(email); + if (user == null) { + throw new MyException(ResponseEnum.USER_NOT_EXIST); + } + if (!RegexUtil.pwdMatch(pwd)) { + throw new MyException(ResponseEnum.PARAMETERS_PWD_ERROR); + } + if (!user.getEmailStatus()) { + throw new MyException(ResponseEnum.USEREMAIL_NOT_VERIFY); + } + String resetPwdIdFromCache = redisUtil.get(user.getEmail() + "-resetPwd"); + if (resetPwdIdFromCache == null) { + throw new MyException(ResponseEnum.FAILURE.getCode(), "请先获取重置密码的邮件"); + } + if (resetPwdIdFromCache.equals(verifyId)) { + if (MD5Util.getMD5(pwd).equals(user.getPwd())) { + throw new MyException(ResponseEnum.PWD_SAME); + } + userMapper.updatePwd(email, MD5Util.getMD5(pwd)); + redisUtil.delete(user.getEmail() + "-resetPwd"); + return "验证成功"; + } else { + throw new MyException(ResponseEnum.FAILURE.getCode(), "标识码不一致"); + } + } + + @Override + public Object deleteUser(Integer[] id) { + JSONArray status = new JSONArray(); + if (id == null || id.length == 0) { + return null; + } + for (Integer integer : id) { + String role = userMapper.getRoleById(integer); + int deleteResult = 0; + JSONObject deleteStatus = new JSONObject(); + deleteStatus.put("id", integer); + // 管理员账户不可删 + if ("admin".equals(role)) { + deleteStatus.put("msg", "用户为管理员,不可删除"); + deleteStatus.put("status", false); + status.add(deleteStatus); + logger.info("删除用户id为{}的用户,删除状态{}, 原因:用户为管理员,不可删除", integer, false); + continue; + } + // 非管理员账户 + deleteResult = userMapper.delete(integer); + deleteStatus.put("status", deleteResult == 1); + logger.info("删除用户id为{}的用户,删除状态{}", integer, deleteResult == 1); + if (deleteResult == 0) { + deleteStatus.put("msg", "用户不存在"); + } + status.add(deleteStatus); + } + return status; + } + + @Override + public PageInfo getUserList(Integer page, Integer count) { + PageHelper.startPage(page, count); + List all = userMapper.findAll(); + PageInfo pageInfo = PageInfo.of(all); + List modelList = new ArrayList<>(); + all.forEach(user -> modelList.add(trans(user))); + pageInfo.setList(modelList); + return pageInfo; + } + + @Override + public UserModel adminUpdate(UserReq userReq) { + if (userReq == null || userReq.getId() == null) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + User user = userMapper.findById(userReq.getId()); + // 设置数据 + if (userReq.getDesc() != null) { + user.setDesc(userReq.getDesc()); + } + if (userReq.getDisplayName() != null) { + user.setDisplayName(userReq.getDisplayName()); + } + if (userReq.getEmailStatus() != null) { + user.setEmailStatus(userReq.getEmailStatus()); + } + if (userReq.getPwd() != null) { + if (userReq.getPwd().length() < 6 || userReq.getPwd().length() > 16) { + throw new MyException(ResponseEnum.PASSWORD_TOO_SHORT_OR_LONG); + } + if (!RegexUtil.pwdMatch(userReq.getPwd())) { + throw new MyException(ResponseEnum.PARAMETERS_PWD_ERROR); + } + user.setPwd(MD5Util.getMD5(userReq.getPwd())); + } + if (userReq.getRole() != null) { + // TODO:用enum存放角色分类 + if ("user".equals(userReq.getRole()) || "admin".equals(userReq.getRole())) { + user.setRole(userReq.getRole()); + } else { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + } + if (userReq.getEmail() != null) { + if (!RegexUtil.emailMatch(userReq.getEmail())) { + throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR); + } + // TODO :: 邮件提醒 + user.setEmail(userReq.getEmail()); + } + // 数据写入 + int updateResult = userMapper.update(user); + if (updateResult == 0) { + throw new MyException(ResponseEnum.FAILURE); + } + if (redisUserUtil.get(request).getId().equals(userReq.getId())) { + redisUserUtil.set(user); + } + logger.info("修改了用户 [id={}] 的用户的资料", userReq.getId()); + return trans(user); + } + + @Override + public boolean getStatusOfEmail(String email) { + return userMapper.existsByEmail(email); + } + + private UserModel trans(User u) { + UserModel user = new UserModel(); + user.setId(u.getId()); + user.setAvatarImgUrl(u.getAvatarImgUrl() == null ? null : "http://cdn.celess.cn/" + u.getAvatarImgUrl()); + user.setEmail(u.getEmail()); + user.setDesc(u.getDesc()); + user.setDisplayName(u.getDisplayName() == null ? u.getEmail() : u.getDisplayName()); + user.setEmailStatus(u.getEmailStatus()); + user.setRecentlyLandedDate(DateFormatUtil.get(u.getRecentlyLandedDate())); + user.setRole(u.getRole()); + return user; + } +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/VisitorServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/VisitorServiceImpl.java new file mode 100644 index 0000000..d1d7fc3 --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/VisitorServiceImpl.java @@ -0,0 +1,210 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Visitor; +import cn.celess.blog.entity.model.VisitorModel; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.VisitorMapper; +import cn.celess.blog.service.VisitorService; +import cn.celess.blog.util.DateFormatUtil; +import cn.celess.blog.util.RedisUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import eu.bitwalker.useragentutils.Browser; +import eu.bitwalker.useragentutils.OperatingSystem; +import eu.bitwalker.useragentutils.UserAgent; +import eu.bitwalker.useragentutils.Version; +import org.apache.commons.lang.time.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.json.JsonParserFactory; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author : xiaohai + * @date : 2019/04/02 23:04 + */ +@Service +public class VisitorServiceImpl implements VisitorService { + @Autowired + VisitorMapper visitorMapper; + @Autowired + RedisUtil redisUtil; + + @Override + public String location(String ip) { + return getLocation(ip); + } + + @Override + public PageInfo visitorPage(int page, int count, boolean showLocation) { + PageHelper.startPage(page, count); + List visitorList = visitorMapper.findAll(); + PageInfo pageInfo = new PageInfo(visitorList); + pageInfo.setList(list2List(visitorList, showLocation)); + return pageInfo; + } + + @Override + public VisitorModel addVisitor(HttpServletRequest request) { + //新session + if (!request.getSession().isNew()) { + return null; + } + if (isSpiderBot(request.getHeader("User-Agent"))) { + return null; + } + Visitor visitor = new Visitor(); + visitor.setIp(request.getRemoteAddr()); + visitor.setDate(new Date()); + visitor.setUa(request.getHeader("User-Agent")); + //记录当日的访问 + String dayVisitCount = redisUtil.get("dayVisitCount"); + long secondsLeftToday = 86400 - DateUtils.getFragmentInSeconds(Calendar.getInstance(), Calendar.DATE); + Date date = new Date(Calendar.YEAR); + if (dayVisitCount == null) { + redisUtil.setEx("dayVisitCount", "1", secondsLeftToday, TimeUnit.SECONDS); + } else { + int count = Integer.parseInt(dayVisitCount) + 1; + redisUtil.setEx("dayVisitCount", count + "", secondsLeftToday, TimeUnit.SECONDS); + } + if (visitorMapper.insert(visitor) == 0) { + throw new MyException(ResponseEnum.FAILURE); + } + return trans(visitor); + } + + + /** + * 数据修改 + * + * @return + */ + private List list2List(List visitorList, boolean showLocation) { + List visitorModelList = new ArrayList<>(); + for (Visitor v : visitorList) { + VisitorModel trans = trans(v); + if (showLocation) { + trans.setLocation(getLocation(v.getIp())); + } + visitorModelList.add(trans); + } + return visitorModelList; + } + + /*** + * 转化为model + * + * @param v + * @return + */ + private VisitorModel trans(Visitor v) { + UserAgent userAgent = UserAgent.parseUserAgentString(v.getUa()); + VisitorModel visitor = new VisitorModel(); + visitor.setId(v.getId()); + visitor.setDate(DateFormatUtil.get(v.getDate())); + visitor.setIp(v.getIp()); + Browser browser = userAgent.getBrowser(); + visitor.setBrowserName(browser == null ? "" : browser.getName()); + OperatingSystem operatingSystem = userAgent.getOperatingSystem(); + visitor.setOSName(operatingSystem == null ? "" : operatingSystem.getName()); + Version browserVersion = userAgent.getBrowserVersion(); + visitor.setBrowserVersion(browserVersion == null ? "" : browserVersion.getVersion()); + return visitor; + } + + /** + * 根据ua判断是不是爬虫 + * + * @param ua ua + * @return true:爬虫 false :不是爬虫 + */ + private boolean isSpiderBot(String ua) { + if (ua == null) { + return false; + } + //服务器端的缓存抓取 + if (ua.contains("https://github.com/prerender/prerender")) { + return true; + } + //搜索引擎得爬虫ua一般有链接 + if (ua.contains("http://")) { + return true; + } + //防止没有匹配到http + return ua.toLowerCase().contains("spider"); + } + + /** + * 获取ip的地址 + * + * @param ip + * @return + */ + private String getLocation(String ip) { + StringBuilder result = new StringBuilder(); + URL url; + HttpURLConnection conn = null; + InputStream inputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + try { + url = new URL("http://ip.taobao.com/service/getIpInfo.php?ip=" + ip); + conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(3000); + conn.setDoInput(true); + conn.setRequestMethod("GET"); + inputStream = conn.getInputStream(); + inputStreamReader = new InputStreamReader(inputStream); + bufferedReader = new BufferedReader(inputStreamReader); + String tmp; + while ((tmp = bufferedReader.readLine()) != null) { + result.append(tmp); + } + } catch (Exception e) { + // ignore + } finally { + try { + if (conn != null) { + conn.disconnect(); + } + if (inputStream != null) { + inputStream.close(); + } + if (inputStreamReader != null) { + inputStreamReader.close(); + } + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (Exception e) { + // ignore + } + } + StringBuilder sb = new StringBuilder(); + if ("".equals(result.toString())) { + throw new MyException(ResponseEnum.FAILURE); + } + Map stringObjectMap = JsonParserFactory.getJsonParser().parseMap(result.toString()); + if ((Integer) stringObjectMap.get("code") == 0) { + LinkedHashMap data = (LinkedHashMap) stringObjectMap.get("data"); + sb.append(data.get("country")) + .append("-") + .append(data.get("region")) + .append("-") + .append(data.get("city")); + } + return sb.toString(); + + } + + +} diff --git a/src/main/java/cn/celess/blog/service/serviceimpl/WebUpdateInfoServiceImpl.java b/src/main/java/cn/celess/blog/service/serviceimpl/WebUpdateInfoServiceImpl.java new file mode 100644 index 0000000..1a70f8a --- /dev/null +++ b/src/main/java/cn/celess/blog/service/serviceimpl/WebUpdateInfoServiceImpl.java @@ -0,0 +1,99 @@ +package cn.celess.blog.service.serviceimpl; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.WebUpdate; +import cn.celess.blog.entity.model.WebUpdateModel; +import cn.celess.blog.exception.MyException; +import cn.celess.blog.mapper.WebUpdateInfoMapper; +import cn.celess.blog.service.WebUpdateInfoService; +import cn.celess.blog.util.DateFormatUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:43 + */ +@Service +public class WebUpdateInfoServiceImpl implements WebUpdateInfoService { + @Autowired + WebUpdateInfoMapper webUpdateInfoMapper; + + + @Override + public WebUpdateModel create(String info) { + if (info == null || info.replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + WebUpdate webUpdate = new WebUpdate(info, new Date()); + if (webUpdateInfoMapper.insert(webUpdate) == 0) { + throw new MyException(ResponseEnum.FAILURE); + } + return trans(webUpdate); + } + + @Override + public Boolean del(long id) { + if (!webUpdateInfoMapper.existsById(id)) { + throw new MyException(ResponseEnum.DATA_NOT_EXIST); + } + return webUpdateInfoMapper.delete(id) == 1; + } + + @Override + public WebUpdateModel update(long id, String info) { + WebUpdate webUpdate = webUpdateInfoMapper.findById(id); + if (webUpdate == null) { + throw new MyException(ResponseEnum.DATA_NOT_EXIST); + } + if (info == null || info.replaceAll(" ", "").isEmpty()) { + throw new MyException(ResponseEnum.PARAMETERS_ERROR); + } + webUpdate.setUpdateInfo(info); + webUpdateInfoMapper.update(id, info); + return trans(webUpdate); + } + + @Override + public PageInfo pages(int count, int page) { + PageHelper.startPage(page, count); + List updateList = webUpdateInfoMapper.findAll(); + PageInfo pageInfo = new PageInfo(updateList); + pageInfo.setList(list2List(updateList)); + return pageInfo; + } + + @Override + public List findAll() { + List all = webUpdateInfoMapper.findAll(); + List webUpdateModels = new ArrayList<>(); + for (WebUpdate w : all) { + webUpdateModels.add(trans(w)); + } + return webUpdateModels; + } + + @Override + public String getLastestUpdateTime() { + return DateFormatUtil.get(webUpdateInfoMapper.getLastestOne()); + } + + private List list2List(List webUpdates) { + List webUpdateModels = new ArrayList<>(); + for (WebUpdate w : webUpdates) { + webUpdateModels.add(trans(w)); + } + return webUpdateModels; + } + + private WebUpdateModel trans(WebUpdate webUpdate) { + return new WebUpdateModel(webUpdate.getId(), webUpdate.getUpdateInfo(), DateFormatUtil.get(webUpdate.getUpdateTime())); + } + +} diff --git a/src/main/java/cn/celess/blog/util/DateFormatUtil.java b/src/main/java/cn/celess/blog/util/DateFormatUtil.java new file mode 100644 index 0000000..9ca8279 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/DateFormatUtil.java @@ -0,0 +1,42 @@ +package cn.celess.blog.util; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * @author : xiaohai + * @date : 2019/03/28 17:22 + */ +public class DateFormatUtil { + public static String get(Date date) { + if (date == null) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.format(date); + } + + public static String getForXmlDate(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ"); + GregorianCalendar gc = new GregorianCalendar(); + String dateString = sdf.format(date); + try { + gc.setTime(sdf.parse(dateString)); + XMLGregorianCalendar date2 = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc); + return date2.toString(); + } catch (DatatypeConfigurationException | ParseException e) { + e.printStackTrace(); + return null; + } + } + + + public static String getNow() { + return get(new Date()); + } +} diff --git a/src/main/java/cn/celess/blog/util/JwtUtil.java b/src/main/java/cn/celess/blog/util/JwtUtil.java new file mode 100644 index 0000000..eb4af71 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/JwtUtil.java @@ -0,0 +1,93 @@ +package cn.celess.blog.util; + +import cn.celess.blog.entity.User; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: 小海 + * @Date: 2019/11/16 11:26 + * @Description: JWT工具类 + */ +@Component +public class JwtUtil { + private static final String CLAIM_KEY_USERNAME = "sub"; + + /** + * 5天(毫秒) + */ + public static final long EXPIRATION_LONG_TIME = 432000000; + + /** + * 两小时(毫秒) + */ + public static final long EXPIRATION_SHORT_TIME = 7200000; + /** + * JWT 秘钥需自行设置不可泄露 + */ + private static final String SECRET = "xxx"; + + + public String generateToken(User user, boolean isRemember) { + Map claims = new HashMap<>(16); + claims.put(CLAIM_KEY_USERNAME, user.getEmail()); + + return Jwts.builder() + .setClaims(claims) + .setExpiration(new Date(Instant.now().toEpochMilli() + (isRemember ? EXPIRATION_LONG_TIME : EXPIRATION_SHORT_TIME))) + .signWith(SignatureAlgorithm.HS512, SECRET) + .compact(); + } + + public Boolean validateToken(String token, User user) { + String username = getUsernameFromToken(token); + + return (username.equals(user.getEmail()) && !isTokenExpired(token)); + } + + /** + * 获取token是否过期 + */ + public Boolean isTokenExpired(String token) { + try { + Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } catch (ExpiredJwtException e) { + return true; + } + } + + /** + * 根据token获取username + */ + public String getUsernameFromToken(String token) { + return getClaimsFromToken(token).getSubject(); + } + + /** + * 获取token的过期时间 + */ + public Date getExpirationDateFromToken(String token) { + return getClaimsFromToken(token).getExpiration(); + } + + /** + * 解析JWT + */ + private Claims getClaimsFromToken(String token) { + Claims claims = Jwts.parser() + .setSigningKey(SECRET) + .parseClaimsJws(token) + .getBody(); + return claims; + } + +} diff --git a/src/main/java/cn/celess/blog/util/MD5Util.java b/src/main/java/cn/celess/blog/util/MD5Util.java new file mode 100644 index 0000000..2d7f82a --- /dev/null +++ b/src/main/java/cn/celess/blog/util/MD5Util.java @@ -0,0 +1,14 @@ +package cn.celess.blog.util; + +import org.springframework.util.DigestUtils; + +/** + * @author : xiaohai + * @date : 2019/03/30 18:56 + */ +public class MD5Util { + public static String getMD5(String str) { + String md5 = DigestUtils.md5DigestAsHex(str.getBytes()); + return md5; + } +} diff --git a/src/main/java/cn/celess/blog/util/ProtoStuffSerializerUtil.java b/src/main/java/cn/celess/blog/util/ProtoStuffSerializerUtil.java new file mode 100644 index 0000000..ec3d805 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/ProtoStuffSerializerUtil.java @@ -0,0 +1,126 @@ +package cn.celess.blog.util; + + +import com.dyuproject.protostuff.LinkedBuffer; +import com.dyuproject.protostuff.ProtostuffIOUtil; +import com.dyuproject.protostuff.Schema; +import com.dyuproject.protostuff.runtime.RuntimeSchema; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * ProtoStuffSerializerUtil + * + * @author Sirius + * @date 2019-1-8 + */ +public class ProtoStuffSerializerUtil { + /** + * 序列化对象 + * + * @param obj + * @return + */ + public static byte[] serialize(T obj) { + if (obj == null) { + throw new RuntimeException("序列化对象(" + obj + ")!"); + } + @SuppressWarnings("unchecked") + Schema schema = (Schema) RuntimeSchema.getSchema(obj.getClass()); + LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024); + byte[] protostuff = null; + try { + protostuff = ProtostuffIOUtil.toByteArray(obj, schema, buffer); + } catch (Exception e) { + throw new RuntimeException("序列化(" + obj.getClass() + ")对象(" + obj + ")发生异常!", e); + } finally { + buffer.clear(); + } + return protostuff; + } + + /** + * 反序列化对象 + * + * @param paramArrayOfByte + * @param targetClass + * @return + */ + public static T deserialize(byte[] paramArrayOfByte, Class targetClass) { + if (paramArrayOfByte == null || paramArrayOfByte.length == 0) { + throw new RuntimeException("反序列化对象发生异常,byte序列为空!"); + } + T instance = null; + try { + instance = targetClass.newInstance(); + } catch (InstantiationException e1) { + throw new RuntimeException("反序列化过程中依据类型创建对象失败!", e1); + } catch (IllegalAccessException e2) { + throw new RuntimeException("反序列化过程中依据类型创建对象失败!", e2); + } + Schema schema = RuntimeSchema.getSchema(targetClass); + ProtostuffIOUtil.mergeFrom(paramArrayOfByte, instance, schema); + return instance; + } + + /** + * 序列化列表 + * + * @param objList + * @return + */ + public static byte[] serializeList(List objList) { + if (objList == null || objList.isEmpty()) { + throw new RuntimeException("序列化对象列表(" + objList + ")参数异常!"); + } + @SuppressWarnings("unchecked") + Schema schema = (Schema) RuntimeSchema.getSchema(objList.get(0).getClass()); + LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024); + byte[] protostuff = null; + ByteArrayOutputStream bos = null; + try { + bos = new ByteArrayOutputStream(); + ProtostuffIOUtil.writeListTo(bos, objList, schema, buffer); + protostuff = bos.toByteArray(); + } catch (Exception e) { + throw new RuntimeException("序列化对象列表(" + objList + ")发生异常!", e); + } finally { + buffer.clear(); + try { + if (bos != null) { + bos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + return protostuff; + } + + /** + * 反序列化列表 + * + * @param paramArrayOfByte + * @param targetClass + * @return + */ + public static List deserializeList(byte[] paramArrayOfByte, Class targetClass) { + if (paramArrayOfByte == null || paramArrayOfByte.length == 0) { + throw new RuntimeException("反序列化对象发生异常,byte序列为空!"); + } + + Schema schema = RuntimeSchema.getSchema(targetClass); + List result = null; + try { + result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(paramArrayOfByte), schema); + } catch (IOException e) { + throw new RuntimeException("反序列化对象列表发生异常!", e); + } + return result; + } + +} diff --git a/src/main/java/cn/celess/blog/util/RedisUserUtil.java b/src/main/java/cn/celess/blog/util/RedisUserUtil.java new file mode 100644 index 0000000..935c725 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/RedisUserUtil.java @@ -0,0 +1,46 @@ +package cn.celess.blog.util; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.User; +import cn.celess.blog.exception.MyException; +import net.sf.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.concurrent.TimeUnit; + +/** + * @author : xiaohai + * @date : 2019/03/08 15:06 + */ +@Component +public class RedisUserUtil { + @Autowired + RedisUtil redisUtil; + @Autowired + JwtUtil jwtUtil; + + public User get(HttpServletRequest request) { + User user = getWithOutExc(request); + if (user == null) { + throw new MyException(ResponseEnum.HAVE_NOT_LOG_IN); + } + return user; + } + + public User getWithOutExc(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + if (token == null || token.isEmpty()) { + return null; + } + String email = jwtUtil.getUsernameFromToken(token); + return (User) JSONObject.toBean(JSONObject.fromObject(redisUtil.get(email + "-login")), User.class); + } + + public User set(User user) { + redisUtil.setEx(user.getEmail() + "-login", JSONObject.fromObject(user).toString(), + redisUtil.getExpire(user.getEmail() + "-login"), TimeUnit.MILLISECONDS); + return user; + } +} diff --git a/src/main/java/cn/celess/blog/util/RedisUtil.java b/src/main/java/cn/celess/blog/util/RedisUtil.java new file mode 100644 index 0000000..7bf60a3 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/RedisUtil.java @@ -0,0 +1,1411 @@ +package cn.celess.blog.util; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +/** + * Redis工具类 + * + * @author WangFan + * @date 2018-02-24 下午03:09:50 + * @version 1.1 (GitHub文档: https://github.com/whvcse/RedisUtil ) + */ + +@Component +public class RedisUtil { + @Autowired + private StringRedisTemplate redisTemplate; + + public void setRedisTemplate(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public StringRedisTemplate getRedisTemplate() { + return this.redisTemplate; + } + + /** -------------------key相关操作--------------------- */ + + /** + * 删除key + * + * @param key + */ + public void delete(String key) { + redisTemplate.delete(key); + } + + /** + * 批量删除key + * + * @param keys + */ + public void delete(Collection keys) { + redisTemplate.delete(keys); + } + + /** + * 序列化key + * + * @param key + * @return + */ + public byte[] dump(String key) { + return redisTemplate.dump(key); + } + + /** + * 是否存在key + * + * @param key + * @return + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 设置过期时间 + * + * @param key + * @param timeout + * @param unit + * @return + */ + public Boolean expire(String key, long timeout, TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 设置过期时间 + * + * @param key + * @param date + * @return + */ + public Boolean expireAt(String key, Date date) { + return redisTemplate.expireAt(key, date); + } + + /** + * 查找匹配的key + * + * @param pattern + * @return + */ + public Set keys(String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 将当前数据库的 key 移动到给定的数据库 db 当中 + * + * @param key + * @param dbIndex + * @return + */ + public Boolean move(String key, int dbIndex) { + return redisTemplate.move(key, dbIndex); + } + + /** + * 移除 key 的过期时间,key 将持久保持 + * + * @param key + * @return + */ + public Boolean persist(String key) { + return redisTemplate.persist(key); + } + + /** + * 返回 key 的剩余的过期时间 + * + * @param key + * @param unit + * @return + */ + public Long getExpire(String key, TimeUnit unit) { + return redisTemplate.getExpire(key, unit); + } + + /** + * 返回 key 的剩余的过期时间 + * + * @param key + * @return + */ + public Long getExpire(String key) { + return redisTemplate.getExpire(key); + } + + /** + * 从当前数据库中随机返回一个 key + * + * @return + */ + public String randomKey() { + return redisTemplate.randomKey(); + } + + /** + * 修改 key 的名称 + * + * @param oldKey + * @param newKey + */ + public void rename(String oldKey, String newKey) { + redisTemplate.rename(oldKey, newKey); + } + + /** + * 仅当 newkey 不存在时,将 oldKey 改名为 newkey + * + * @param oldKey + * @param newKey + * @return + */ + public Boolean renameIfAbsent(String oldKey, String newKey) { + return redisTemplate.renameIfAbsent(oldKey, newKey); + } + + /** + * 返回 key 所储存的值的类型 + * + * @param key + * @return + */ + public DataType type(String key) { + return redisTemplate.type(key); + } + + /** -------------------string相关操作--------------------- */ + + /** + * 设置指定 key 的值 + * @param key + * @param value + */ + public void set(String key, String value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 获取指定 key 的值 + * @param key + * @return + */ + public String get(String key) { + return redisTemplate.opsForValue().get(key); + } + + /** + * 返回 key 中字符串值的子字符 + * @param key + * @param start + * @param end + * @return + */ + public String getRange(String key, long start, long end) { + return redisTemplate.opsForValue().get(key, start, end); + } + + /** + * 将给定 key 的值设为 value ,并返回 key 的旧值(old value) + * + * @param key + * @param value + * @return + */ + public String getAndSet(String key, String value) { + return redisTemplate.opsForValue().getAndSet(key, value); + } + + /** + * 对 key 所储存的字符串值,获取指定偏移量上的位(bit) + * + * @param key + * @param offset + * @return + */ + public Boolean getBit(String key, long offset) { + return redisTemplate.opsForValue().getBit(key, offset); + } + + /** + * 批量获取 + * + * @param keys + * @return + */ + public List multiGet(Collection keys) { + return redisTemplate.opsForValue().multiGet(keys); + } + + /** + * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value + * + * @param key + * @param postion + * 位置 + * @param value + * 值,true为1, false为0 + * @return + */ + public boolean setBit(String key, long offset, boolean value) { + return redisTemplate.opsForValue().setBit(key, offset, value); + } + + /** + * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout + * + * @param key + * @param value + * @param timeout + * 过期时间 + * @param unit + * 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES + * 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS + */ + public void setEx(String key, String value, long timeout, TimeUnit unit) { + redisTemplate.opsForValue().set(key, value, timeout, unit); + } + + /** + * 只有在 key 不存在时设置 key 的值 + * + * @param key + * @param value + * @return 之前已经存在返回false,不存在返回true + */ + public boolean setIfAbsent(String key, String value) { + return redisTemplate.opsForValue().setIfAbsent(key, value); + } + + /** + * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 + * + * @param key + * @param value + * @param offset + * 从指定位置开始覆写 + */ + public void setRange(String key, String value, long offset) { + redisTemplate.opsForValue().set(key, value, offset); + } + + /** + * 获取字符串的长度 + * + * @param key + * @return + */ + public Long size(String key) { + return redisTemplate.opsForValue().size(key); + } + + /** + * 批量添加 + * + * @param maps + */ + public void multiSet(Map maps) { + redisTemplate.opsForValue().multiSet(maps); + } + + /** + * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 + * + * @param maps + * @return 之前已经存在返回false,不存在返回true + */ + public boolean multiSetIfAbsent(Map maps) { + return redisTemplate.opsForValue().multiSetIfAbsent(maps); + } + + /** + * 增加(自增长), 负数则为自减 + * + * @param key + * @param value + * @return + */ + public Long incrBy(String key, long increment) { + return redisTemplate.opsForValue().increment(key, increment); + } + + /** + * + * @param key + * @param value + * @return + */ + public Double incrByFloat(String key, double increment) { + return redisTemplate.opsForValue().increment(key, increment); + } + + /** + * 追加到末尾 + * + * @param key + * @param value + * @return + */ + public Integer append(String key, String value) { + return redisTemplate.opsForValue().append(key, value); + } + + /** -------------------hash相关操作------------------------- */ + + /** + * 获取存储在哈希表中指定字段的值 + * + * @param key + * @param field + * @return + */ + public Object hGet(String key, String field) { + return redisTemplate.opsForHash().get(key, field); + } + + /** + * 获取所有给定字段的值 + * + * @param key + * @return + */ + public Map hGetAll(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 获取所有给定字段的值 + * + * @param key + * @param fields + * @return + */ + public List hMultiGet(String key, Collection fields) { + return redisTemplate.opsForHash().multiGet(key, fields); + } + + public void hPut(String key, String hashKey, String value) { + redisTemplate.opsForHash().put(key, hashKey, value); + } + + public void hPutAll(String key, Map maps) { + redisTemplate.opsForHash().putAll(key, maps); + } + + /** + * 仅当hashKey不存在时才设置 + * + * @param key + * @param hashKey + * @param value + * @return + */ + public Boolean hPutIfAbsent(String key, String hashKey, String value) { + return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); + } + + /** + * 删除一个或多个哈希表字段 + * + * @param key + * @param fields + * @return + */ + public Long hDelete(String key, Object... fields) { + return redisTemplate.opsForHash().delete(key, fields); + } + + /** + * 查看哈希表 key 中,指定的字段是否存在 + * + * @param key + * @param field + * @return + */ + public boolean hExists(String key, String field) { + return redisTemplate.opsForHash().hasKey(key, field); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + * + * @param key + * @param field + * @param increment + * @return + */ + public Long hIncrBy(String key, Object field, long increment) { + return redisTemplate.opsForHash().increment(key, field, increment); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + * + * @param key + * @param field + * @param delta + * @return + */ + public Double hIncrByFloat(String key, Object field, double delta) { + return redisTemplate.opsForHash().increment(key, field, delta); + } + + /** + * 获取所有哈希表中的字段 + * + * @param key + * @return + */ + public Set hKeys(String key) { + return redisTemplate.opsForHash().keys(key); + } + + /** + * 获取哈希表中字段的数量 + * + * @param key + * @return + */ + public Long hSize(String key) { + return redisTemplate.opsForHash().size(key); + } + + /** + * 获取哈希表中所有值 + * + * @param key + * @return + */ + public List hValues(String key) { + return redisTemplate.opsForHash().values(key); + } + + /** + * 迭代哈希表中的键值对 + * + * @param key + * @param options + * @return + */ + public Cursor> hScan(String key, ScanOptions options) { + return redisTemplate.opsForHash().scan(key, options); + } + + /** ------------------------list相关操作---------------------------- */ + + /** + * 通过索引获取列表中的元素 + * + * @param key + * @param index + * @return + */ + public String lIndex(String key, long index) { + return redisTemplate.opsForList().index(key, index); + } + + /** + * 获取列表指定范围内的元素 + * + * @param key + * @param start + * 开始位置, 0是开始位置 + * @param end + * 结束位置, -1返回所有 + * @return + */ + public List lRange(String key, long start, long end) { + return redisTemplate.opsForList().range(key, start, end); + } + + /** + * 存储在list头部 + * + * @param key + * @param value + * @return + */ + public Long lLeftPush(String key, String value) { + return redisTemplate.opsForList().leftPush(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lLeftPushAll(String key, String... value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lLeftPushAll(String key, Collection value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * 当list存在的时候才加入 + * + * @param key + * @param value + * @return + */ + public Long lLeftPushIfPresent(String key, String value) { + return redisTemplate.opsForList().leftPushIfPresent(key, value); + } + + /** + * 如果pivot存在,再pivot前面添加 + * + * @param key + * @param pivot + * @param value + * @return + */ + public Long lLeftPush(String key, String pivot, String value) { + return redisTemplate.opsForList().leftPush(key, pivot, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPush(String key, String value) { + return redisTemplate.opsForList().rightPush(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPushAll(String key, String... value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPushAll(String key, Collection value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * 为已存在的列表添加值 + * + * @param key + * @param value + * @return + */ + public Long lRightPushIfPresent(String key, String value) { + return redisTemplate.opsForList().rightPushIfPresent(key, value); + } + + /** + * 在pivot元素的右边添加值 + * + * @param key + * @param pivot + * @param value + * @return + */ + public Long lRightPush(String key, String pivot, String value) { + return redisTemplate.opsForList().rightPush(key, pivot, value); + } + + /** + * 通过索引设置列表元素的值 + * + * @param key + * @param index + * 位置 + * @param value + */ + public void lSet(String key, long index, String value) { + redisTemplate.opsForList().set(key, index, value); + } + + /** + * 移出并获取列表的第一个元素 + * + * @param key + * @return 删除的元素 + */ + public String lLeftPop(String key) { + return redisTemplate.opsForList().leftPop(key); + } + + /** + * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout + * 等待时间 + * @param unit + * 时间单位 + * @return + */ + public String lBLeftPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().leftPop(key, timeout, unit); + } + + /** + * 移除并获取列表最后一个元素 + * + * @param key + * @return 删除的元素 + */ + public String lRightPop(String key) { + return redisTemplate.opsForList().rightPop(key); + } + + /** + * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout + * 等待时间 + * @param unit + * 时间单位 + * @return + */ + public String lBRightPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPop(key, timeout, unit); + } + + /** + * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 + * + * @param sourceKey + * @param destinationKey + * @return + */ + public String lRightPopAndLeftPush(String sourceKey, String destinationKey) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, + destinationKey); + } + + /** + * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param sourceKey + * @param destinationKey + * @param timeout + * @param unit + * @return + */ + public String lBRightPopAndLeftPush(String sourceKey, String destinationKey, + long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, + destinationKey, timeout, unit); + } + + /** + * 删除集合中值等于value得元素 + * + * @param key + * @param index + * index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; + * index<0, 从尾部开始删除第一个值等于value的元素; + * @param value + * @return + */ + public Long lRemove(String key, long index, String value) { + return redisTemplate.opsForList().remove(key, index, value); + } + + /** + * 裁剪list + * + * @param key + * @param start + * @param end + */ + public void lTrim(String key, long start, long end) { + redisTemplate.opsForList().trim(key, start, end); + } + + /** + * 获取列表长度 + * + * @param key + * @return + */ + public Long lLen(String key) { + return redisTemplate.opsForList().size(key); + } + + /** --------------------set相关操作-------------------------- */ + + /** + * set添加元素 + * + * @param key + * @param values + * @return + */ + public Long sAdd(String key, String... values) { + return redisTemplate.opsForSet().add(key, values); + } + + /** + * set移除元素 + * + * @param key + * @param values + * @return + */ + public Long sRemove(String key, Object... values) { + return redisTemplate.opsForSet().remove(key, values); + } + + /** + * 移除并返回集合的一个随机元素 + * + * @param key + * @return + */ + public String sPop(String key) { + return redisTemplate.opsForSet().pop(key); + } + + /** + * 将元素value从一个集合移到另一个集合 + * + * @param key + * @param value + * @param destKey + * @return + */ + public Boolean sMove(String key, String value, String destKey) { + return redisTemplate.opsForSet().move(key, value, destKey); + } + + /** + * 获取集合的大小 + * + * @param key + * @return + */ + public Long sSize(String key) { + return redisTemplate.opsForSet().size(key); + } + + /** + * 判断集合是否包含value + * + * @param key + * @param value + * @return + */ + public Boolean sIsMember(String key, Object value) { + return redisTemplate.opsForSet().isMember(key, value); + } + + /** + * 获取两个集合的交集 + * + * @param key + * @param otherKey + * @return + */ + public Set sIntersect(String key, String otherKey) { + return redisTemplate.opsForSet().intersect(key, otherKey); + } + + /** + * 获取key集合与多个集合的交集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sIntersect(String key, Collection otherKeys) { + return redisTemplate.opsForSet().intersect(key, otherKeys); + } + + /** + * key集合与otherKey集合的交集存储到destKey集合中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sIntersectAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKey, + destKey); + } + + /** + * key集合与多个集合的交集存储到destKey集合中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sIntersectAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, + destKey); + } + + /** + * 获取两个集合的并集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sUnion(String key, String otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * 获取key集合与多个集合的并集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sUnion(String key, Collection otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * key集合与otherKey集合的并集存储到destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey); + } + + /** + * key集合与多个集合的并集存储到destKey中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sUnionAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey); + } + + /** + * 获取两个集合的差集 + * + * @param key + * @param otherKey + * @return + */ + public Set sDifference(String key, String otherKey) { + return redisTemplate.opsForSet().difference(key, otherKey); + } + + /** + * 获取key集合与多个集合的差集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sDifference(String key, Collection otherKeys) { + return redisTemplate.opsForSet().difference(key, otherKeys); + } + + /** + * key集合与otherKey集合的差集存储到destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sDifference(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKey, + destKey); + } + + /** + * key集合与多个集合的差集存储到destKey中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sDifference(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, + destKey); + } + + /** + * 获取集合所有元素 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Set setMembers(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 随机获取集合中的一个元素 + * + * @param key + * @return + */ + public String sRandomMember(String key) { + return redisTemplate.opsForSet().randomMember(key); + } + + /** + * 随机获取集合中count个元素 + * + * @param key + * @param count + * @return + */ + public List sRandomMembers(String key, long count) { + return redisTemplate.opsForSet().randomMembers(key, count); + } + + /** + * 随机获取集合中count个元素并且去除重复的 + * + * @param key + * @param count + * @return + */ + public Set sDistinctRandomMembers(String key, long count) { + return redisTemplate.opsForSet().distinctRandomMembers(key, count); + } + + /** + * + * @param key + * @param options + * @return + */ + public Cursor sScan(String key, ScanOptions options) { + return redisTemplate.opsForSet().scan(key, options); + } + + /**------------------zSet相关操作--------------------------------*/ + + /** + * 添加元素,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @param score + * @return + */ + public Boolean zAdd(String key, String value, double score) { + return redisTemplate.opsForZSet().add(key, value, score); + } + + /** + * + * @param key + * @param values + * @return + */ + public Long zAdd(String key, Set> values) { + return redisTemplate.opsForZSet().add(key, values); + } + + /** + * + * @param key + * @param values + * @return + */ + public Long zRemove(String key, Object... values) { + return redisTemplate.opsForZSet().remove(key, values); + } + + /** + * 增加元素的score值,并返回增加后的值 + * + * @param key + * @param value + * @param delta + * @return + */ + public Double zIncrementScore(String key, String value, double delta) { + return redisTemplate.opsForZSet().incrementScore(key, value, delta); + } + + /** + * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @return 0表示第一位 + */ + public Long zRank(String key, Object value) { + return redisTemplate.opsForZSet().rank(key, value); + } + + /** + * 返回元素在集合的排名,按元素的score值由大到小排列 + * + * @param key + * @param value + * @return + */ + public Long zReverseRank(String key, Object value) { + return redisTemplate.opsForZSet().reverseRank(key, value); + } + + /** + * 获取集合的元素, 从小到大排序 + * + * @param key + * @param start + * 开始位置 + * @param end + * 结束位置, -1查询所有 + * @return + */ + public Set zRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + + /** + * 获取集合元素, 并且把score值也获取 + * + * @param key + * @param start + * @param end + * @return + */ + public Set> zRangeWithScores(String key, long start, + long end) { + return redisTemplate.opsForZSet().rangeWithScores(key, start, end); + } + + /** + * 根据Score值查询集合元素 + * + * @param key + * @param min + * 最小值 + * @param max + * 最大值 + * @return + */ + public Set zRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从小到大排序 + * + * @param key + * @param min + * 最小值 + * @param max + * 最大值 + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max); + } + + /** + * + * @param key + * @param min + * @param max + * @param start + * @param end + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max, long start, long end) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, + start, end); + } + + /** + * 获取集合的元素, 从大到小排序 + * + * @param key + * @param start + * @param end + * @return + */ + public Set zReverseRange(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRange(key, start, end); + } + + /** + * 获取集合的元素, 从大到小排序, 并返回score值 + * + * @param key + * @param start + * @param end + * @return + */ + public Set> zReverseRangeWithScores(String key, + long start, long end) { + return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, + end); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + * + * @param key + * @param min + * @param max + * @return + */ + public Set zReverseRangeByScore(String key, double min, + double max) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + * + * @param key + * @param min + * @param max + * @return + */ + public Set> zReverseRangeByScoreWithScores( + String key, double min, double max) { + return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, + min, max); + } + + /** + * + * @param key + * @param min + * @param max + * @param start + * @param end + * @return + */ + public Set zReverseRangeByScore(String key, double min, + double max, long start, long end) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, + start, end); + } + + /** + * 根据score值获取集合元素数量 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zCount(String key, double min, double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zZCard(String key) { + return redisTemplate.opsForZSet().zCard(key); + } + + /** + * 获取集合中value元素的score值 + * + * @param key + * @param value + * @return + */ + public Double zScore(String key, Object value) { + return redisTemplate.opsForZSet().score(key, value); + } + + /** + * 移除指定索引位置的成员 + * + * @param key + * @param start + * @param end + * @return + */ + public Long zRemoveRange(String key, long start, long end) { + return redisTemplate.opsForZSet().removeRange(key, start, end); + } + + /** + * 根据指定的score值的范围来移除成员 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zRemoveRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().removeRangeByScore(key, min, max); + } + + /** + * 获取key和otherKey的并集并存储在destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey); + } + + /** + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet() + .unionAndStore(key, otherKeys, destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, String otherKey, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, + destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, + destKey); + } + + /** + * + * @param key + * @param options + * @return + */ + public Cursor> zScan(String key, ScanOptions options) { + return redisTemplate.opsForZSet().scan(key, options); + } + + /** + * 获取Redis List 序列化 + * @param key + * @param targetClass + * @param + * @return + */ + public List getListCache(final String key, Class targetClass) { + byte[] result = redisTemplate.execute(new RedisCallback() { + @Override + public byte[] doInRedis(RedisConnection connection) throws DataAccessException { + return connection.get(key.getBytes()); + } + }); + if (result == null) { + return null; + } + return ProtoStuffSerializerUtil.deserializeList(result, targetClass); + } + + /*** + * 将List 放进缓存里面 + * @param key + * @param objList + * @param expireTime + * @param + * @return + */ + public boolean putListCacheWithExpireTime(String key, List objList, final long expireTime) { + final byte[] bkey = key.getBytes(); + final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList); + boolean result = redisTemplate.execute(new RedisCallback() { + @Override + public Boolean doInRedis(RedisConnection connection) throws DataAccessException { + connection.setEx(bkey, expireTime, bvalue); + return true; + } + }); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/cn/celess/blog/util/RegexUtil.java b/src/main/java/cn/celess/blog/util/RegexUtil.java new file mode 100644 index 0000000..6926229 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/RegexUtil.java @@ -0,0 +1,80 @@ +package cn.celess.blog.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author : xiaohai + * @date : 2019/05/12 11:04 + */ +public class RegexUtil { + /** + * 网址匹配 + * + * @param url + * @return + */ + public static boolean urlMatch(String url) { + if (url == null || url.replaceAll(" ", "").isEmpty()) { + return false; + } + //正则 (http(s)://www.celess/xxxx,www.celess.cn/xxx) + String pattern = "^(http://|https://|)([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$"; + return match(url, pattern); + } + + /** + * 邮箱验证 + * + * @param email + * @return + */ + public static boolean emailMatch(String email) { + if (email == null || email.replaceAll(" ", "").isEmpty()) { + return false; + } + //正则 + String pattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; + return match(email, pattern); + } + + /** + * 手机号匹配 + * + * @param phone + * @return + */ + public static boolean phoneMatch(String phone) { + if (phone == null || phone.replaceAll(" ", "").isEmpty()) { + return false; + } + //正则 + String pattern = "^([1][3,4,5,6,7,8,9])\\d{9}$"; + return match(phone, pattern); + } + + /** + * 密码正则 + * 最短6位,最长16位 {6,16} + * 可以包含小写大母 [a-z] 和大写字母 [A-Z] + * 可以包含数字 [0-9] + * 可以包含下划线 [ _ ] 和减号 [ - ] + * + * @param pwd + * @return + */ + public static boolean pwdMatch(String pwd) { + if (pwd == null || pwd.replaceAll(" ", "").isEmpty()) { + return false; + } + //正则 + String pattern = "^[\\w_-]{6,16}$"; + return match(pwd, pattern); + } + + private static boolean match(String str, String pattern) { + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(str); + return m.matches(); + } +} diff --git a/src/main/java/cn/celess/blog/util/RequestUtil.java b/src/main/java/cn/celess/blog/util/RequestUtil.java new file mode 100644 index 0000000..1a5336e --- /dev/null +++ b/src/main/java/cn/celess/blog/util/RequestUtil.java @@ -0,0 +1,17 @@ +package cn.celess.blog.util; + +import javax.servlet.http.HttpServletRequest; + +/** + * @Author: 小海 + * @Date: 2019/10/18 15:44 + * @Description: + */ +public class RequestUtil { + public static String getCompleteUrlAndMethod(HttpServletRequest request) { + // like this : /articles?page=1&count=5:GET + return request.getRequestURI() + + (request.getQueryString() == null ? "" : "?" + request.getQueryString()) + + ":" + request.getMethod(); + } +} diff --git a/src/main/java/cn/celess/blog/util/ResponseUtil.java b/src/main/java/cn/celess/blog/util/ResponseUtil.java new file mode 100644 index 0000000..0cf49e5 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/ResponseUtil.java @@ -0,0 +1,59 @@ +package cn.celess.blog.util; + +import cn.celess.blog.enmu.ResponseEnum; +import cn.celess.blog.entity.Response; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author : xiaohai + * @date : 2019/03/28 15:32 + */ +@ResponseBody +public class ResponseUtil { + + /** + * 成功相应 + * + * @param result 结果 + * @return + */ + public static Response success(Object result) { + Response response = new Response(); + response.setCode(ResponseEnum.SUCCESS.getCode()); + response.setMsg(ResponseEnum.SUCCESS.getMsg()); + response.setDate(System.currentTimeMillis()); + response.setResult(result); + return response; + } + + /** + * 失败的响应 + * + * @param result 结果 + * @return + */ + public static Response failure(String result) { + Response response = new Response(); + response.setCode(ResponseEnum.FAILURE.getCode()); + response.setMsg(ResponseEnum.FAILURE.getMsg()); + response.setDate(System.currentTimeMillis()); + response.setResult(result); + return response; + } + + /** + * 其他的响应 + * + * @param r 枚举常量 + * @param result 结果 + * @return + */ + public static Response response(ResponseEnum r, String result) { + Response response = new Response(); + response.setCode(r.getCode()); + response.setMsg(r.getMsg()); + response.setDate(System.currentTimeMillis()); + response.setResult(result); + return response; + } +} diff --git a/src/main/java/cn/celess/blog/util/SitemapGenerateUtil.java b/src/main/java/cn/celess/blog/util/SitemapGenerateUtil.java new file mode 100644 index 0000000..ebcc737 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/SitemapGenerateUtil.java @@ -0,0 +1,109 @@ +package cn.celess.blog.util; + +import cn.celess.blog.entity.Article; +import cn.celess.blog.mapper.ArticleMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Author: 小海 + * @Date: 2019/07/30 17:29 + * @Description: + */ +@Component +public class SitemapGenerateUtil { + + @Value("${sitemap.path}") + private String path; + private Map urlList; + + @Autowired + ArticleMapper articleMapper; + + @Async + public void createSitemap() { + initList(); + File file = new File(path); + try { + if (file.exists()) { + file.delete(); + } else { + file.createNewFile(); + } + } catch (IOException e) { + e.printStackTrace(); + } + DocumentBuilder db = getDocumentBuilder(); + Document document = db.newDocument(); + document.setXmlVersion("1.0"); + document.setXmlStandalone(true); + Element urlset = document.createElement("urlset"); + urlset.setAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"); + // 创建url 结点 + urlList.forEach((s, s2) -> { + Element url = document.createElement("url"); + Element loc = document.createElement("loc"); + Element lastmod = document.createElement("lastmod"); + loc.setTextContent(s); + lastmod.setTextContent(s2); + url.appendChild(loc); + url.appendChild(lastmod); + urlset.appendChild(url); + }); + document.appendChild(urlset); + try { + TransformerFactory tff = TransformerFactory.newInstance(); + Transformer tf = tff.newTransformer(); + tf.setOutputProperty(OutputKeys.INDENT, "yes"); + tf.transform(new DOMSource(document), new StreamResult(file)); + } catch (TransformerException e) { + e.printStackTrace(); + } + } + + private void initList() { + urlList = new HashMap<>(); + urlList.put("https://www.celess.cn", DateFormatUtil.getForXmlDate(new Date())); + urlList.put("https://www.celess.cn/links", DateFormatUtil.getForXmlDate(new Date())); + urlList.put("https://www.celess.cn/leaveMsg", DateFormatUtil.getForXmlDate(new Date())); + List
articles = articleMapper.findAll(); + articles.forEach(article -> { + urlList.put("https://www.celess.cn/article/" + article.getId(), DateFormatUtil.getForXmlDate( + article.getUpdateDate() == null ? article.getPublishDate() : article.getUpdateDate())); + }); + } + + + private static DocumentBuilder getDocumentBuilder() { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = null; + try { + db = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } + return db; + } + +} + diff --git a/src/main/java/cn/celess/blog/util/StringFromHtmlUtil.java b/src/main/java/cn/celess/blog/util/StringFromHtmlUtil.java new file mode 100644 index 0000000..afd1502 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/StringFromHtmlUtil.java @@ -0,0 +1,17 @@ +package cn.celess.blog.util; + +/** + * @author : xiaohai + * @date : 2019/03/28 17:21 + */ +public class StringFromHtmlUtil { + public static String getString(String html) { + //从html中提取纯文本 + //剔出的标签 + String txtcontent = html.replaceAll("]+>", ""); + //去除字符串中的空格,回车,换行符,制表符 + txtcontent = txtcontent.replaceAll("\\s*|\t|\r|\n", ""); + return txtcontent; + } +} + diff --git a/src/main/java/cn/celess/blog/util/VeriCodeUtil.java b/src/main/java/cn/celess/blog/util/VeriCodeUtil.java new file mode 100644 index 0000000..e3cb153 --- /dev/null +++ b/src/main/java/cn/celess/blog/util/VeriCodeUtil.java @@ -0,0 +1,90 @@ +package cn.celess.blog.util; + + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Random; + +/** + * @author : xiaohai + * @date : 2019/04/11 15:42 + */ + +public class VeriCodeUtil { + // 验证码字符集 + private static final char[] chars = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + // 字符数量 + private static final int SIZE = 4; + // 干扰线数量 + private static final int LINES = 5; + // 宽度 + private static final int WIDTH = 80; + // 高度 + private static final int HEIGHT = 40; + // 字体大小 + private static final int FONT_SIZE = 30; + + /** + * 生成随机验证码及图片 + * Object[0]:验证码字符串; + * Object[1]:验证码图片。 + */ + public static Object[] createImage() { + StringBuffer sb = new StringBuffer(); + // 1.创建空白图片 + BufferedImage image = new BufferedImage( + WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + // 2.获取图片画笔 + Graphics graphic = image.getGraphics(); + // 3.设置画笔颜色 + graphic.setColor(Color.LIGHT_GRAY); + // 4.绘制矩形背景 + graphic.fillRect(0, 0, WIDTH, HEIGHT); + // 5.画随机字符 + Random ran = new Random(); + for (int i = 0; i < SIZE; i++) { + // 取随机字符索引 + int n = ran.nextInt(chars.length); + // 设置随机颜色 + graphic.setColor(getRandomColor()); + // 设置字体大小 + graphic.setFont(new Font( + null, Font.BOLD + Font.ITALIC, FONT_SIZE)); + // 画字符 + graphic.drawString( + chars[n] + "", i * WIDTH / SIZE, HEIGHT * 2 / 3); + // 记录字符 + sb.append(chars[n]); + } + // 6.画干扰线 + for (int i = 0; i < LINES; i++) { + // 设置随机颜色 + graphic.setColor(getRandomColor()); + // 随机画线 + graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT), + ran.nextInt(WIDTH), ran.nextInt(HEIGHT)); + } + // 7.返回验证码和图片 + return new Object[]{sb.toString(), image}; + } + + /** + * 随机取色 + */ + public static Color getRandomColor() { + Random ran = new Random(); + Color color = new Color(ran.nextInt(256), + ran.nextInt(256), ran.nextInt(256)); + return color; + } + + +} + + diff --git a/src/main/resources/application-openSource.properties b/src/main/resources/application-openSource.properties new file mode 100644 index 0000000..ad00d95 --- /dev/null +++ b/src/main/resources/application-openSource.properties @@ -0,0 +1,75 @@ +server.port=8081 + +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=update +# 上传单个文件的大小 +spring.servlet.multipart.max-file-size=10MB +# 上传文件的总大小 +spring.servlet.multipart.max-request-size=10MB +##null字段不显示 +spring.jackson.default-property-inclusion=non_null + + +################# 数据库 ################## +#请先填写下面的配置 +spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +spring.datasource.url= +spring.datasource.username= +spring.datasource.password= +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + + +################## mybatis ################## +mybatis.mapper-locations=classpath:mapper/*.xml +mybatis.type-aliases-package=cn.celess.blog.entity + + +pagehelper.helper-dialect=mysql +pagehelper.reasonable=true +pagehelper.support-methods-arguments=true +pagehelper.params=count=countSql + + + + +################ email ############## +#请先填写下面的配置,不然可能运行不起来 +spring.mail.host= +spring.mail.username= +spring.mail.password= +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.starttls.required=true +spring.mail.default-encoding=UTF-8 +spring.mail.port=465 +spring.mail.properties.mail.smtp.socketFactory.port=465 +spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory +spring.mail.properties.mail.smtp.socketFactory.fallback=false + + +#### 用于nginx的代理 获取真实ip +server.use-forward-headers = true +server.tomcat.remote-ip-header = X-Real-IP +server.tomcat.protocol-header = X-Forwarded-Proto + + +############### redis ############## +# REDIS (RedisProperties) +# Redis数据库索引(默认为0) +spring.redis.database=0 +# Redis服务器地址 +spring.redis.host= +# Redis服务器连接端口 +spring.redis.port=6379 +# Redis服务器连接密码(默认为空) +spring.redis.password= +# 连接池最大连接数(使用负值表示没有限制) +spring.redis.jedis.pool.max-active=-1 +# 连接池最大阻塞等待时间(使用负值表示没有限制) +spring.redis.jedis.pool.max-wait=-1 +# 连接池中的最大空闲连接 +spring.redis.jedis.pool.max-idle=8 +# 连接池中的最小空闲连接 +spring.redis.jedis.pool.min-idle=0 +# 连接超时时间(毫秒) +spring.redis.timeout=5000 \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties new file mode 100644 index 0000000..6fa9d06 --- /dev/null +++ b/src/main/resources/application-test.properties @@ -0,0 +1,80 @@ +server.port=8081 + +sitemap.path=C:\\Users\\zh564\\Desktop\\sitemap.xml + +##spring.jpa.show-sql=false +##spring.jpa.hibernate.ddl-auto=update + +mybatis.type-handlers-package=cn.celess.blog.mapper.typehandler + +# 上传单个文件的大小 +spring.servlet.multipart.max-file-size=10MB +# 上传文件的总大小 +spring.servlet.multipart.max-request-size=10MB + +spring.jackson.default-property-inclusion=non_null + + +################# 数据库 ################## +spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +spring.datasource.url=jdbc:mysql://localhost:3306/test_blog?serverTimezone=UCT&allowPublicKeyRetrieval=true&useSSL=false +spring.datasource.username=root +spring.datasource.password=zhenghai +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + + +################## mybatis ################## +mybatis.mapper-locations=classpath:mapper/*.xml +mybatis.type-aliases-package=cn.celess.blog.entity + + +pagehelper.helper-dialect=mysql +pagehelper.reasonable=true +pagehelper.support-methods-arguments=true +pagehelper.params=count=countSql + + + +#### 用于nginx的代理 获取真实ip +server.use-forward-headers = true +server.tomcat.remote-ip-header = X-Real-IP +server.tomcat.protocol-header = X-Forwarded-Proto + + +############### email ############## +spring.mail.host=smtp.163.com +spring.mail.username= +spring.mail.password= +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.starttls.required=true +spring.mail.default-encoding=UTF-8 +spring.mail.port=465 +spring.mail.properties.mail.smtp.socketFactory.port=465 +spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory +spring.mail.properties.mail.smtp.socketFactory.fallback=false + + + +############### redis ############## + +# REDIS (RedisProperties) +# Redis数据库索引(默认为0) +spring.redis.database=1 +# Redis服务器地址 +spring.redis.host=127.0.0.1 +# Redis服务器连接端口 +spring.redis.port=6379 +# Redis服务器连接密码(默认为空) +spring.redis.password= +# 连接池最大连接数(使用负值表示没有限制) +spring.redis.jedis.pool.max-active=-1 +# 连接池最大阻塞等待时间(使用负值表示没有限制) +spring.redis.jedis.pool.max-wait=-1 +# 连接池中的最大空闲连接 +spring.redis.jedis.pool.max-idle=8 +# 连接池中的最小空闲连接 +spring.redis.jedis.pool.min-idle=0 +# 连接超时时间(毫秒) +spring.redis.timeout=5000 + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..37a7772 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.profiles.active=prod +####七牛的配置 +####cn.celess.blog.service.serviceimpl.QiniuServiceImpl +logging.level.cn.celess.blog=debug +logging.level.cn.celess.blog.mapper=info \ No newline at end of file diff --git a/src/main/resources/mapper/CategoryMapper.xml b/src/main/resources/mapper/CategoryMapper.xml new file mode 100644 index 0000000..0a0c444 --- /dev/null +++ b/src/main/resources/mapper/CategoryMapper.xml @@ -0,0 +1,84 @@ + + + + + + + + + + insert into category (c_name, articles) + values (#{name}, #{articles}); + + SELECT LAST_INSERT_ID() AS id + + + + + update category + set c_name=#{name}, + articles=#{articles} + where c_id = #{id} + + + + delete + from category + where c_id = #{id} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/CommentMapper.xml b/src/main/resources/mapper/CommentMapper.xml new file mode 100644 index 0000000..94c9973 --- /dev/null +++ b/src/main/resources/mapper/CommentMapper.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + insert into comment (co_article_id, is_comment, author_id, co_content, co_date, co_pid) + VALUES (#{articleID}, #{type}, #{authorID}, #{content}, #{date}, #{pid}) + + SELECT LAST_INSERT_ID() AS id + + + + + update comment + set co_content=#{content} + where co_id = #{id} + + + + update comment + set co_response_id =#{responder} + where co_id = #{id} + + + delete + from comment + where co_id = #{id} + + + delete + from comment + where co_article_id = #{articleId} + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/PartnerSiteMapper.xml b/src/main/resources/mapper/PartnerSiteMapper.xml new file mode 100644 index 0000000..563521d --- /dev/null +++ b/src/main/resources/mapper/PartnerSiteMapper.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + insert into links (site_name, is_open, site_url) + values (#{name}, #{open}, #{url}) + + SELECT LAST_INSERT_ID() AS id + + + + + update links set + site_name=#{name}, + site_url=#{url}, + is_open=#{open} + where site_id=#{id} + + + + delete + from links + where site_id = #{id} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..b9888b8 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + insert into user(u_email, u_pwd) + values (#{email}, #{pwd}) + + + + update user set + `u_desc`=#{desc}, + `display_name`=#{displayName} + where u_id=#{id} + + + update user + set `recently_landed_time`=#{date} + where `u_email` = #{email} + + + update user + set `u_avatar`=#{avatarImgUrl} + where `u_id` = #{id} + + + update user + set `email_status`=#{status} + where `u_email` = #{email} + + + update user + set `u_pwd`=#{pwd} + where `u_email` = #{email} + + + update user + set role=#{role} + where u_id = #{uid} + + + update user + set `u_email` = #{email}, + `u_pwd` = #{pwd}, + `email_status` = #{emailStatus}, + `u_desc` = #{desc}, + `display_name` = #{displayName}, + `role` = #{role} + where `u_id` = #{id} + + + delete + from user + where u_id = #{id} + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/VisitorMapper.xml b/src/main/resources/mapper/VisitorMapper.xml new file mode 100644 index 0000000..d1899ce --- /dev/null +++ b/src/main/resources/mapper/VisitorMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + insert into visitor (v_date, v_ip, v_user_agent) + values (#{date}, #{ip}, #{ua}) + + SELECT LAST_INSERT_ID() AS id + + + + delete + from visitor + where v_id = #{id} + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/WebUpdateInfoMapper.xml b/src/main/resources/mapper/WebUpdateInfoMapper.xml new file mode 100644 index 0000000..db9f799 --- /dev/null +++ b/src/main/resources/mapper/WebUpdateInfoMapper.xml @@ -0,0 +1,50 @@ + + + + + + + + + + insert into web_update(update_info, update_time) + values (#{updateInfo}, #{updateTime}) + + SELECT LAST_INSERT_ID() AS id + + + + + update web_update + set update_info=#{info} + where update_id = #{id}; + + + + + delete + from web_update + where update_id = #{id} + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/articleMapper.xml b/src/main/resources/mapper/articleMapper.xml new file mode 100644 index 0000000..e994535 --- /dev/null +++ b/src/main/resources/mapper/articleMapper.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + insert into article (a_author_id, a_category_id, a_tags_id, a_md_content, a_publish_date, + a_summary, a_title, a_url) + values (#{authorId}, #{categoryId}, #{tagsId}, #{mdContent}, #{publishDate}, + #{summary}, #{title}, #{url}) + + SELECT LAST_INSERT_ID() AS id + + + + delete + from article + where a_id = #{id} + + + + update article + set + a_title=#{title}, + a_md_content=#{mdContent}, + a_summary=#{summary}, + a_is_original=#{type}, + a_url=#{url}, + a_update_date=#{updateDate}, + a_category_id=#{categoryId}, + a_tags_id=#{tagsId}, + next_a_id=#{nextArticleId}, + pre_a_id=#{preArticleId}, + a_is_open=#{open} + where a_id = #{id} + + + + update article + set next_a_id=#{nextArticleID} + where a_id = #{targetArticleID} + + + + update article + set pre_a_id=#{preArticleID} + where a_id = #{targetArticleID} + + + update article + set a_reading_number=#{number} + where a_id = #{id} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/tagMapper.xml b/src/main/resources/mapper/tagMapper.xml new file mode 100644 index 0000000..7c976e9 --- /dev/null +++ b/src/main/resources/mapper/tagMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + insert into tag (tag_name, articles) + VALUES (#{name}, #{articles}); + + SELECT LAST_INSERT_ID() AS id + + + + + update tag + set tag_name=#{name}, + articles=#{articles} + where tag_id = #{id} + + + + delete + from tag + where tag_id = #{id} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/BaseTest.java b/src/test/java/cn/celess/blog/BaseTest.java new file mode 100644 index 0000000..d29e89e --- /dev/null +++ b/src/test/java/cn/celess/blog/BaseTest.java @@ -0,0 +1,97 @@ +package cn.celess.blog; + +import cn.celess.blog.entity.request.LoginReq; +import net.sf.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.http.Cookie; + +import static org.junit.Assert.*; + +/** + * @Author: 小海 + * @Date: 2019/08/22 12:46 + * @Description: 测试基类 + */ +@SpringBootTest +@RunWith(SpringRunner.class) +@WebAppConfiguration +@ActiveProfiles("test") +public class BaseTest { + + protected MockMvc mockMvc; + protected final static String Code = "code"; + protected final static String Result = "result"; + + @Autowired + private WebApplicationContext wac; + + @Before + public void before() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + System.out.println("==========> 开始测试 <========="); + } + + @After + public void after() { + System.out.println("==========> 测试结束 <========="); + } + + + protected String adminLogin() { + try { + LoginReq req = new LoginReq(); + req.setEmail("a@celess.cn"); + req.setPassword("123456789"); + req.setIsRememberMe(false); + JSONObject loginReq = JSONObject.fromObject(req); + String str = mockMvc.perform(MockMvcRequestBuilders.post("/login").content(loginReq.toString()).contentType("application/json")) +// .andDo(MockMvcResultHandlers.print()) + .andReturn().getResponse().getContentAsString(); + String token = JSONObject.fromObject(str).getJSONObject(Result).getString("token"); + assertNotNull(token); + return token; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + protected String userLogin() { + try { + LoginReq req = new LoginReq(); + req.setEmail("zh56462271@qq.com"); + req.setPassword("123456789"); + req.setIsRememberMe(false); + JSONObject loginReq = JSONObject.fromObject(req); + String str = mockMvc.perform(MockMvcRequestBuilders.post("/login").content(loginReq.toString()).contentType("application/json")) +// .andDo(MockMvcResultHandlers.print()) + .andReturn().getResponse().getContentAsString(); + String token = JSONObject.fromObject(str).getJSONObject(Result).getString("token"); + assertNotNull(token); + return token; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Test + public void test() { + + } +} diff --git a/src/test/java/cn/celess/blog/controller/ArticleControllerTest.java b/src/test/java/cn/celess/blog/controller/ArticleControllerTest.java new file mode 100644 index 0000000..de811ce --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/ArticleControllerTest.java @@ -0,0 +1,407 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.Article; +import cn.celess.blog.entity.Response; +import cn.celess.blog.entity.model.ArticleModel; +import cn.celess.blog.entity.request.ArticleReq; +import cn.celess.blog.mapper.ArticleMapper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + + +import java.util.Arrays; +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static cn.celess.blog.enmu.ResponseEnum.*; + +public class ArticleControllerTest extends BaseTest { + @Autowired + ArticleMapper articleMapper; + + @Test + public void create() { + ArticleReq articleReq = new ArticleReq(); + // 应该正常通过 + articleReq.setTitle("test-" + UUID.randomUUID().toString()); + articleReq.setMdContent("# test title"); + articleReq.setCategory("随笔"); + articleReq.setTags("test,SpringMvc"); + articleReq.setOpen(true); + articleReq.setType(true); + articleReq.setUrl("http://xxxx.com"); + JSONObject jsonObject = JSONObject.fromObject(articleReq); + + try { + // 未登录 + mockMvc.perform(post("/admin/article/create") + .content(jsonObject.toString()) + .contentType("application/json")) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), + JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code) + ); + }); + // User权限 + String token = userLogin(); + mockMvc.perform(post("/admin/article/create") + .content(jsonObject.toString()) + .contentType("application/json") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(PERMISSION_ERROR.getCode(), + JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code) + ); + }); + + // Admin权限 + token = adminLogin(); + mockMvc.perform(post("/admin/article/create") + .content(jsonObject.toString()) + .contentType("application/json") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + ArticleModel articleModel = (ArticleModel) JSONObject.toBean(object.getJSONObject(Result), ArticleModel.class); + assertNotNull(articleModel.getId()); + assertNotNull(articleModel.getTitle()); + assertNotNull(articleModel.getSummary()); + assertNotNull(articleModel.getOriginal()); + assertNotNull(articleModel.getTags()); + assertNotNull(articleModel.getCategory()); + assertNotNull(articleModel.getPublishDateFormat()); + assertNotNull(articleModel.getMdContent()); + assertNotNull(articleModel.getNextArticleId()); + assertNotNull(articleModel.getNextArticleTitle()); + assertNotNull(articleModel.getPreArticleId()); + assertNotNull(articleModel.getPreArticleTitle()); + assertNotNull(articleModel.getOpen()); + assertNotNull(articleModel.getReadingNumber()); + assertNotNull(articleModel.getAuthorName()); + assertNotNull(articleModel.getUrl()); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void delete() { + long articleId = articleMapper.getLastestArticleId(); + + try { + // 未登录删除文章 + mockMvc.perform(MockMvcRequestBuilders.delete("/admin/article/del?articleID=" + articleId) + ).andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), + JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code) + ); + }); + // user 权限删除文章 + String token = userLogin(); + mockMvc.perform(MockMvcRequestBuilders.delete("/admin/article/del?articleID=" + articleId) + .header("Authorization", token)) + .andDo(result -> assertEquals(PERMISSION_ERROR.getCode(), + JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + // admin 权限删除文章 + token = adminLogin(); + mockMvc.perform(MockMvcRequestBuilders.delete("/admin/article/del?articleID=" + articleId) + .header("Authorization", token)) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + // 断言删除成功 + assertTrue(object.getBoolean(Result)); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void update() { + Article article = articleMapper.getLastestArticle(); + ArticleReq articleReq = new ArticleReq(); + articleReq.setId(article.getId()); + articleReq.setUrl("http://www.test.test"); + articleReq.setType(!article.getType()); + articleReq.setCategory("test"); + articleReq.setMdContent("test-" + article.getMdContent()); + articleReq.setOpen(!article.getOpen()); + articleReq.setTags("tag"); + articleReq.setTitle("test-" + article.getTitle()); + try { + mockMvc.perform(put("/admin/article/update") + .content(JSONObject.fromObject(articleReq).toString()) + .contentType("application/json")) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // User 权限 + String token = userLogin(); + mockMvc.perform(put("/admin/article/update") + .content(JSONObject.fromObject(articleReq).toString()) + .contentType("application/json") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // Admin 权限 + token = adminLogin(); + mockMvc.perform(put("/admin/article/update") + .content(JSONObject.fromObject(articleReq).toString()) + .contentType("application/json") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject jsonObject = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), jsonObject.getInt(Code)); + ArticleModel a = (ArticleModel) JSONObject.toBean(jsonObject.getJSONObject(Result), ArticleModel.class); + assertEquals(articleReq.getCategory(), a.getCategory()); + assertEquals(articleReq.getUrl(), a.getUrl()); + assertEquals(articleReq.getMdContent(), a.getMdContent()); + assertEquals(articleReq.getTitle(), a.getTitle()); + assertEquals(articleReq.getType(), a.getOriginal()); + // Tag 暂时不支持更新 +// assertEquals(articleReq.getTags(), Arrays.toString(a.getTags()).replaceAll(" ", "".replace("[", "".replace("]", "")))); + assertEquals(articleReq.getOpen(), a.getOpen()); + assertEquals(articleReq.getId(), a.getId()); + }); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void retrieveOneById() { + try { + long articleID = 3; + mockMvc.perform(MockMvcRequestBuilders.get("/article/articleID/" + articleID)) + .andExpect(status().is(200)); + mockMvc.perform(MockMvcRequestBuilders.get("/article/articleID/" + articleID + "?update=true")) + .andExpect(status().is(200)); + + // 文章不存在 + mockMvc.perform(MockMvcRequestBuilders.get("/article/articleID/-1")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject jsonObject = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(ARTICLE_NOT_EXIST.getCode(), jsonObject.getInt(Code)); + }); + + // 正常情况 + mockMvc.perform(MockMvcRequestBuilders.get("/article/articleID/" + articleID + "?update=false")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject articleJson = JSONObject.fromObject(result.getResponse().getContentAsString()); + // 断言获取数据成功 + assertEquals(SUCCESS.getCode(), articleJson.getInt(Code)); + assertNotNull(articleJson.getJSONObject(Result)); + + ArticleModel a = (ArticleModel) JSONObject.toBean(articleJson.getJSONObject(Result), ArticleModel.class); + assertNotNull(a.getTitle()); + assertNotNull(a.getId()); + assertNotNull(a.getSummary()); + assertNotNull(a.getMdContent()); + assertNotNull(a.getUrl()); + assertNotNull(a.getUpdateDateFormat()); + assertNotNull(a.getPreArticleId()); + assertNotNull(a.getPreArticleId()); + assertNotNull(a.getNextArticleId()); + assertNotNull(a.getNextArticleTitle()); + assertNotNull(a.getReadingNumber()); + // assertNotNull(a.getOpen()); + assertNotNull(a.getOriginal()); + assertNotNull(a.getPublishDateFormat()); + assertNotNull(a.getCategory()); + assertNotNull(a.getTags()); + assertNotNull(a.getAuthorName()); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void articles() { + try { + // 测试不带参数访问 + mockMvc.perform(MockMvcRequestBuilders.get("/articles")) + .andExpect(status().is(200)); + + mockMvc.perform(MockMvcRequestBuilders.get("/articles?page=1&count=5")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject articlesJSON = JSONObject.fromObject(result.getResponse().getContentAsString()); + Response response = (Response) JSONObject.toBean(articlesJSON, Response.class); + // 断言获取数据成功 + assertEquals(SUCCESS.getCode(), response.getCode()); + // 结果集非空 + assertNotNull(response.getResult()); + // 判断pageInfo是否包装完全 + JSONObject resultJson = JSONObject.fromObject(response.getResult()); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(resultJson, PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(5, pageInfo.getPageSize()); + // 内容完整 + for (Object arc : pageInfo.getList()) { + ArticleModel a = (ArticleModel) JSONObject.toBean(JSONObject.fromObject(arc), ArticleModel.class); + assertNotNull(a.getTitle()); + assertNotNull(a.getId()); + assertNotNull(a.getSummary()); + assertNotNull(a.getOriginal()); + assertNotNull(a.getPublishDateFormat()); + assertNotNull(a.getCategory()); + assertNotNull(a.getTags()); + assertNotNull(a.getAuthorName()); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void adminArticles() { + String token; + try { + // 未登录 + mockMvc.perform(get("/admin/articles?page=1&count=10")) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), + JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code) + ); + }); + + // User权限登陆 + token = userLogin(); + mockMvc.perform(get("/admin/articles?page=1&count=10") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(PERMISSION_ERROR.getCode(), object.getInt(Code)); + }); + + token = adminLogin(); + // admin权限登陆 + mockMvc.perform(get("/admin/articles?page=1&count=10") + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject adminLogin = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), adminLogin.getInt(Code)); + assertNotNull(adminLogin.getString(Result)); + // 判断pageInfo是否包装完全 + PageInfo pageInfo = (PageInfo) JSONObject.toBean(adminLogin.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + // 内容完整 + for (Object arc : pageInfo.getList()) { + ArticleModel a = (ArticleModel) JSONObject.toBean(JSONObject.fromObject(arc), ArticleModel.class); + assertNotNull(a.getTitle()); + assertNotNull(a.getId()); + assertNotNull(a.getOriginal()); + assertNotNull(a.getPublishDateFormat()); + assertNotNull(a.getOpen()); + assertNotNull(a.getReadingNumber()); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void findByCategory() { + try { + // 分类不存在 + String categoryName = "NoSuchCategory"; + mockMvc.perform(MockMvcRequestBuilders.get("/articles/category/" + categoryName + "?page=1&count=10")) + .andExpect(status().is(200)) + .andDo(result -> { + assertEquals(CATEGORY_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // 正常查询 + categoryName = "linux"; + mockMvc.perform(MockMvcRequestBuilders.get("/articles/category/" + categoryName + "?page=1&count=10")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject jsonObject = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), jsonObject.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(jsonObject.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + for (Object arc : pageInfo.getList()) { + JSONObject jsonObject1 = JSONObject.fromObject(arc); + assertNotEquals(0, jsonObject1.getInt("id")); + assertNotNull(jsonObject1.getString("title")); + assertNotNull(jsonObject1.getString("summary")); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void findByTag() { + try { + // 分类不存在 + String tagName = "NoSuchTag"; + mockMvc.perform(MockMvcRequestBuilders.get("/articles/tag/" + tagName + "?page=1&count=10")) + .andExpect(status().is(200)) + .andDo(result -> { + assertEquals(TAG_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // 正常查询 + tagName = "linux"; + mockMvc.perform(MockMvcRequestBuilders.get("/articles/tag/" + tagName + "?page=1&count=10")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject jsonObject = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), jsonObject.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(jsonObject.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + + for (Object arc : pageInfo.getList()) { + JSONObject jsonObject1 = JSONObject.fromObject(arc); + assertNotEquals(0, jsonObject1.getInt("id")); + assertNotNull(jsonObject1.getString("title")); + assertNotNull(jsonObject1.getString("summary")); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/CategoryControllerTest.java b/src/test/java/cn/celess/blog/controller/CategoryControllerTest.java new file mode 100644 index 0000000..258778f --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/CategoryControllerTest.java @@ -0,0 +1,132 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.Category; +import cn.celess.blog.mapper.CategoryMapper; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static cn.celess.blog.enmu.ResponseEnum.*; + +public class CategoryControllerTest extends BaseTest { + + @Autowired + CategoryMapper categoryMapper; + + @Test + public void addOne() throws Exception { + String categoryName = UUID.randomUUID().toString().substring(0, 4); + System.out.println("categoryName: ==> " + categoryName); + // 未登录 + mockMvc.perform(post("/admin/category/create?name=" + categoryName)).andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // User权限 + String token = userLogin(); + mockMvc.perform(post("/admin/category/create?name=" + categoryName) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // Admin权限 + token = adminLogin(); + mockMvc.perform(post("/admin/category/create?name=" + categoryName) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + Category category = (Category) JSONObject.toBean(object.getJSONObject(Result), Category.class); + assertEquals(categoryName, category.getName()); + assertNotNull(category.getId()); + assertNotNull(category.getArticles()); + }); + } + + @Test + public void deleteOne() throws Exception { + Category category = categoryMapper.getLastestCategory(); + // 未登录 + mockMvc.perform(delete("/admin/category/del?id=" + category.getId())).andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // User权限 + String token = userLogin(); + mockMvc.perform(delete("/admin/category/del?id=" + category.getId()) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // Admin权限 + token = adminLogin(); + mockMvc.perform(delete("/admin/category/del?id=" + category.getId()) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.getBoolean(Result)); + }); + } + + @Test + public void updateOne() throws Exception { + Category category = categoryMapper.getLastestCategory(); + String name = UUID.randomUUID().toString().substring(0, 4); + // 未登录 + mockMvc.perform(put("/admin/category/update?id=" + category.getId() + "&name=" + name)).andExpect(status().isOk()) + .andDo(result -> { + assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // User权限 + String token = userLogin(); + mockMvc.perform(put("/admin/category/update?id=" + category.getId() + "&name=" + name) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + // Admin权限 + token = adminLogin(); + mockMvc.perform(put("/admin/category/update?id=" + category.getId() + "&name=" + name) + .header("Authorization", token)) + .andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + Category c = (Category) JSONObject.toBean(object.getJSONObject(Result), Category.class); + assertEquals(name, c.getName()); + assertNotNull(c.getArticles()); + assertNotNull(c.getId()); + }); + } + + @Test + public void getPage() throws Exception { + mockMvc.perform(get("/categories")).andExpect(status().isOk()) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONArray jsonArray = object.getJSONArray(Result); + assertNotNull(jsonArray); + jsonArray.forEach(o -> { + Category c = (Category) JSONObject.toBean(JSONObject.fromObject(o), Category.class); + assertNotNull(c.getName()); + assertNotNull(c.getId()); + assertNotNull(c.getArticles()); + }); + }); + + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/CommentControllerTest.java b/src/test/java/cn/celess/blog/controller/CommentControllerTest.java new file mode 100644 index 0000000..7a1dd87 --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/CommentControllerTest.java @@ -0,0 +1,329 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.Article; +import cn.celess.blog.entity.Comment; +import cn.celess.blog.entity.model.CommentModel; +import cn.celess.blog.entity.request.CommentReq; +import cn.celess.blog.mapper.ArticleMapper; +import cn.celess.blog.mapper.CommentMapper; +import cn.celess.blog.mapper.UserMapper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; +import static cn.celess.blog.enmu.ResponseEnum.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +public class CommentControllerTest extends BaseTest { + @Autowired + ArticleMapper articleMapper; + @Autowired + CommentMapper commentMapper; + + @Test + public void addOne() throws Exception { + CommentReq commentReq = new CommentReq(); + // 测试留言 + commentReq.setArticleID(null); + commentReq.setComment(false); + commentReq.setContent(UUID.randomUUID().toString()); + commentReq.setPid(-1L); + commentReq.setResponseId(null); + String token = userLogin(); + CommentModel PC = null; + mockMvc.perform(post("/user/comment/create") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(JSONObject.fromObject(commentReq).toString()) + .header("Authorization", token) + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + CommentModel model = (CommentModel) JSONObject.toBean(object.getJSONObject(Result), CommentModel.class); + assertNotEquals(0, model.getId()); + assertEquals(commentReq.getPid().longValue(), model.getPid()); + assertEquals(-1, model.getPid()); + assertEquals(commentReq.getComment(), model.isComment()); + assertEquals(commentReq.getContent(), model.getContent()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + }); + + Article article = articleMapper.getLastestArticle(); + // 测试评论 + commentReq.setArticleID(article.getId()); + commentReq.setComment(true); + commentReq.setContent(UUID.randomUUID().toString()); + commentReq.setPid(-1L); + commentReq.setResponseId(null); + mockMvc.perform(post("/user/comment/create") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(JSONObject.fromObject(commentReq).toString()) + .header("Authorization", token) + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + CommentModel model = (CommentModel) JSONObject.toBean(object.getJSONObject(Result), CommentModel.class); + // 响应数据的完整性 + assertNotEquals(0, model.getId()); + assertEquals(commentReq.getPid().longValue(), model.getPid()); + assertEquals(-1, model.getPid()); + assertEquals(commentReq.getComment(), model.isComment()); + assertEquals(commentReq.getContent(), model.getContent()); + assertEquals(commentReq.getArticleID().longValue(), model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + }); + + // 测试二级回复 + Comment lastestComment = commentMapper.getLastestComment(); + commentReq.setArticleID(lastestComment.getArticleID()); + commentReq.setComment(lastestComment.getType()); + commentReq.setContent(UUID.randomUUID().toString()); + commentReq.setPid(lastestComment.getId()); + commentReq.setResponseId(null); + mockMvc.perform(post("/user/comment/create") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(JSONObject.fromObject(commentReq).toString()) + .header("Authorization", token) + ).andDo(MockMvcResultHandlers.print()).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + CommentModel model = (CommentModel) JSONObject.toBean(object.getJSONObject(Result), CommentModel.class); + // 重新获取父评论信息 + Comment pCommon = commentMapper.findCommentById(lastestComment.getId()); + assertEquals(pCommon.getId().longValue(), model.getPid()); + // 判断父评论中是否有写入当前新增的评论的id + String[] ids = pCommon.getResponseId().split(","); + boolean contain = false; + for (String id : ids) { + if (!id.isEmpty() && Long.parseLong(id) == model.getId()) { + contain = true; + break; + } + } + assertTrue(contain); + }); + } + + @Test + public void deleteTest() throws Exception { + // 准备数据 + Comment c = new Comment(); + c.setArticleID(-1L); + c.setType(true); + c.setAuthorID(2L); + c.setDate(new Date()); + c.setPid(-1L); + commentMapper.insert(c); + Comment comment = commentMapper.getLastestComment(); + // 接口测试 + long id = comment.getId(); + assertNotEquals(0, id); + String token = userLogin(); + mockMvc.perform(delete("/user/comment/del?id=" + id).header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.getBoolean(Result)); + }); + mockMvc.perform(delete("/user/comment/del?id=" + id).header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(COMMENT_NOT_EXIST.getCode(), object.getInt(Code)); + }); + } + + @Test + public void update() throws Exception { + Comment comment = commentMapper.getLastestComment(); + CommentReq commentReq = new CommentReq(); + commentReq.setId(comment.getId()); + commentReq.setPid(comment.getPid()); + commentReq.setContent(UUID.randomUUID().toString()); + commentReq.setArticleID(comment.getArticleID()); + // 不合法数据 setResponseId + commentReq.setResponseId("xxxx"); + commentReq.setComment(comment.getType()); + mockMvc.perform(put("/user/comment/update") + .content(JSONObject.fromObject(commentReq).toString()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .header("Authorization", userLogin()) + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + CommentModel c = (CommentModel) JSONObject.toBean(object.getJSONObject(Result), CommentModel.class); + assertEquals(commentReq.getContent(), c.getContent()); + assertEquals(commentReq.getResponseId(), c.getResponseId()); + assertNotNull(c.getAuthorAvatarImgUrl()); + assertNotNull(c.getAuthorName()); + assertNotNull(c.getDate()); + assertNotEquals(0, c.getPid()); + assertNotEquals(0, c.getArticleID()); + }); + } + + @Test + public void commentsOfArticle() throws Exception { + mockMvc.perform(get("/comments?articleId=3&page=1&count=10")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertEquals(3, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getArticleTitle()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + }); + }); + } + + @Test + public void retrievePage() throws Exception { + long pid = 17; + mockMvc.perform(get("/comment/pid/" + pid + "?articleId=3&page=1&count=10")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertEquals(3, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getArticleTitle()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + }); + }); + } + + @Test + public void retrievePageOfLeaveMsg() throws Exception { + mockMvc.perform(get("/leaveMsg?page=1&count=10")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertEquals(-1, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + assertFalse(model.isComment()); + }); + }); + } + + @Test + public void retrievePageAdmin() throws Exception { + mockMvc.perform(get("/admin/comment/type/1?page=1&count=10").header("Authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertNotEquals(-1, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + assertTrue(model.isComment()); + }); + }); + mockMvc.perform(get("/admin/comment/type/0?page=1&count=10").header("Authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertEquals(-1, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + assertFalse(model.isComment()); + }); + }); + } + + @Test + public void retrievePageByAuthor() throws Exception { + mockMvc.perform(get("/user/comment/type/1?page=1&count=10").header("Authorization", userLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertNotEquals(-1, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + assertTrue(model.isComment()); + }); + }); + mockMvc.perform(get("/user/comment/type/0?page=1&count=10").header("Authorization", userLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + CommentModel model = (CommentModel) JSONObject.toBean(JSONObject.fromObject(o), CommentModel.class); + assertEquals(-1, model.getArticleID()); + assertNotNull(model.getDate()); + assertNotNull(model.getAuthorName()); + assertNotNull(model.getAuthorAvatarImgUrl()); + assertNotNull(model.getContent()); + assertNotNull(model.getResponseId()); + assertFalse(model.isComment()); + }); + }); + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/LinksControllerTest.java b/src/test/java/cn/celess/blog/controller/LinksControllerTest.java new file mode 100644 index 0000000..7c78ac1 --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/LinksControllerTest.java @@ -0,0 +1,178 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.PartnerSite; +import cn.celess.blog.entity.request.LinkReq; +import cn.celess.blog.mapper.PartnerMapper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; + +import java.util.UUID; + +import static cn.celess.blog.enmu.ResponseEnum.*; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +public class LinksControllerTest extends BaseTest { + + @Autowired + PartnerMapper mapper; + + @Test + public void create() throws Exception { + LinkReq linkReq = new LinkReq(); + linkReq.setName(UUID.randomUUID().toString().substring(0, 4)); + linkReq.setOpen(false); + linkReq.setUrl("https://example.com"); + String token = adminLogin(); + mockMvc.perform( + post("/admin/links/create") + .content(JSONObject.fromObject(linkReq).toString()) + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PartnerSite site = (PartnerSite) JSONObject.toBean(object.getJSONObject(Result), PartnerSite.class); + assertNotNull(site.getId()); + assertEquals(linkReq.getName(), site.getName()); + assertEquals(linkReq.getUrl(), site.getUrl()); + assertEquals(linkReq.isOpen(), site.getOpen()); + }); + + // https/http + linkReq.setName(UUID.randomUUID().toString().substring(0, 4)); + linkReq.setOpen(false); + linkReq.setUrl("example.com"); + mockMvc.perform( + post("/admin/links/create") + .content(JSONObject.fromObject(linkReq).toString()) + .header("Authorization", token) + .contentType("application/json") + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PartnerSite site = (PartnerSite) JSONObject.toBean(object.getJSONObject(Result), PartnerSite.class); + assertEquals("http://example.com", site.getUrl()); + }); + + // 测试已存在的数据 + mockMvc.perform( + post("/admin/links/create") + .content(JSONObject.fromObject(linkReq).toString()) + .header("Authorization", token) + .contentType("application/json") + ).andDo(result -> assertEquals(DATA_HAS_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + } + + @Test + public void del() throws Exception { + PartnerSite partnerSite = new PartnerSite(); + partnerSite.setName(UUID.randomUUID().toString().substring(0, 4)); + partnerSite.setOpen(true); + partnerSite.setUrl("https://www.celess.cn"); + mapper.insert(partnerSite); + PartnerSite lastest = mapper.getLastest(); + assertNotNull(lastest.getId()); + String token = adminLogin(); + mockMvc.perform(delete("/admin/links/del/" + lastest.getId()).header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.getBoolean(Result)); + }); + long id = lastest.getId(); + do { + id += 1; + } while (mapper.existsById(id)); + System.out.println("删除ID=" + id + "的数据"); + mockMvc.perform(delete("/admin/links/del/" + id).header("Authorization", token)).andDo(result -> + assertEquals(DATA_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + } + + @Test + public void update() throws Exception { + // 增数据 + PartnerSite partnerSite = new PartnerSite(); + partnerSite.setName(UUID.randomUUID().toString().substring(0, 4)); + partnerSite.setOpen(true); + partnerSite.setUrl("https://www.celess.cn"); + mapper.insert(partnerSite); + // 查数据 + PartnerSite lastest = mapper.getLastest(); + assertNotNull(lastest.getId()); + String token = adminLogin(); + // 构建请求 + LinkReq linkReq = new LinkReq(); + linkReq.setUrl(lastest.getUrl()); + linkReq.setOpen(!lastest.getOpen()); + linkReq.setName(UUID.randomUUID().toString().substring(0, 4)); + linkReq.setId(lastest.getId()); + mockMvc.perform( + put("/admin/links/update") + .content(JSONObject.fromObject(linkReq).toString()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", token) + ).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PartnerSite site = (PartnerSite) JSONObject.toBean(object.getJSONObject(Result), PartnerSite.class); + assertNotNull(site.getId()); + assertEquals(linkReq.getId(), site.getId().longValue()); + assertEquals(linkReq.getUrl(), site.getUrl()); + assertEquals(linkReq.getName(), site.getName()); + assertEquals(linkReq.isOpen(), site.getOpen()); + }); + } + + @Test + public void allForOpen() throws Exception { + mockMvc.perform(get("/links")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + object.getJSONArray(Result).forEach(o -> { + PartnerSite site = (PartnerSite) JSONObject.toBean(JSONObject.fromObject(o), PartnerSite.class); + assertNotNull(site.getUrl()); + assertNull(site.getOpen()); + assertNotNull(site.getName()); + }); + }); + } + + @Test + public void all() throws Exception { + mockMvc.perform(get("/admin/links?page=1&count=10").header("Authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + PartnerSite site = (PartnerSite) JSONObject.toBean(JSONObject.fromObject(o), PartnerSite.class); + assertNotNull(site.getUrl()); + assertNotNull(site.getName()); + assertNotNull(site.getOpen()); + }); + }); + } + + @Test + public void apply() throws Exception { + long l = System.currentTimeMillis(); + String url = "https://www.example.com"; + mockMvc.perform(post("/apply?name=小海博客Api测试,请忽略&url=" + url)).andDo(result -> { + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + System.out.println("耗时:" + (System.currentTimeMillis() - l) / 1000 + "s"); + url = "xxxxxxxxxm"; + mockMvc.perform(post("/apply?name=小海博客Api测试,请忽略&url=" + url)).andDo(result -> { + assertEquals(PARAMETERS_URL_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + }); + + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/TagControllerTest.java b/src/test/java/cn/celess/blog/controller/TagControllerTest.java new file mode 100644 index 0000000..f742c55 --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/TagControllerTest.java @@ -0,0 +1,143 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.Tag; +import cn.celess.blog.mapper.TagMapper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static cn.celess.blog.enmu.ResponseEnum.*; + +public class TagControllerTest extends BaseTest { + @Autowired + TagMapper tagMapper; + + @Test + public void addOne() throws Exception { + String name = UUID.randomUUID().toString().substring(0, 4); + mockMvc.perform(post("/admin/tag/create?name=" + name)).andDo(result -> assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(post("/admin/tag/create?name=" + name).header("authorization", userLogin())).andDo(result -> assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(post("/admin/tag/create?name=" + name).header("authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONObject resJson = object.getJSONObject(Result); + Tag tag = (Tag) JSONObject.toBean(resJson, Tag.class); + assertNotNull(tag.getId()); + assertEquals(name, tag.getName()); + }); + + + } + + @Test + public void delOne() throws Exception { + Tag lastestTag = tagMapper.getLastestTag(); + assertNotNull(lastestTag.getId()); + String token = adminLogin(); + mockMvc.perform(delete("/admin/tag/del?id=" + lastestTag.getId()).header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.getBoolean(Result)); + }); + long id = lastestTag.getId() * 2; + mockMvc.perform(delete("/admin/tag/del?id=" + id).header("Authorization", token)).andDo(result -> + assertEquals(TAG_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + + } + + @Test + public void updateOne() throws Exception { + Tag tag = tagMapper.getLastestTag(); + assertNotNull(tag.getId()); + String name = UUID.randomUUID().toString().substring(0, 4); + mockMvc.perform(put("/admin/tag/update?id=" + tag.getId() + "&name=" + name).header("Authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertNotNull(object.getJSONObject(Result)); + Tag t = (Tag) JSONObject.toBean(object.getJSONObject(Result), Tag.class); + assertEquals(name, t.getName()); + assertEquals(tag.getArticles(), t.getArticles()); + assertEquals(tag.getId(), t.getId()); + }); + + } + + @Test + public void retrieveOneById() throws Exception { + Tag tag = tagMapper.getLastestTag(); + assertNotNull(tag.getId()); + mockMvc.perform(get("/tag/id/" + tag.getId())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertNotNull(object.getJSONObject(Result)); + Tag t = (Tag) JSONObject.toBean(object.getJSONObject(Result), Tag.class); + assertEquals(tag.getId(), t.getId()); + assertNotNull(t.getName()); + }); + } + + @Test + public void retrieveOneByName() throws Exception { + Tag tag = tagMapper.getLastestTag(); + assertNotNull(tag.getName()); + mockMvc.perform(get("/tag/name/" + tag.getName())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertNotNull(object.getJSONObject(Result)); + Tag t = (Tag) JSONObject.toBean(object.getJSONObject(Result), Tag.class); + assertEquals(tag.getName(), t.getName()); + assertNotNull(t.getId()); + }); + } + + @Test + public void getPage() throws Exception { + mockMvc.perform(get("/tags?page=1&count=5")) + .andExpect(status().is(200)) + .andDo(result -> { + JSONObject articlesJSON = JSONObject.fromObject(result.getResponse().getContentAsString()); + // 断言获取数据成功 + assertEquals(SUCCESS.getCode(), articlesJSON.getInt(Code)); + // 结果集非空 + assertNotNull(articlesJSON.getJSONObject(Result)); + // 判断pageInfo是否包装完全 + JSONObject resultJson = JSONObject.fromObject(articlesJSON.getJSONObject(Result)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(resultJson, PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(5, pageInfo.getPageSize()); + // 内容完整 + for (Object tag : pageInfo.getList()) { + Tag t = (Tag) JSONObject.toBean(JSONObject.fromObject(tag), Tag.class); + assertNotNull(t.getId()); + assertNotNull(t.getName()); + } + }); + } + + @Test + public void getTagNameAndCount() throws Exception { + mockMvc.perform(get("/tags/nac")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONArray jsonArray = object.getJSONArray(Result); + assertNotNull(jsonArray); + jsonArray.forEach(o -> { + JSONObject json = JSONObject.fromObject(o); + assertTrue(json.containsKey("size")); + assertTrue(json.containsKey("name")); + }); + }); + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/UserControllerTest.java b/src/test/java/cn/celess/blog/controller/UserControllerTest.java new file mode 100644 index 0000000..d404191 --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/UserControllerTest.java @@ -0,0 +1,247 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.User; +import cn.celess.blog.entity.model.UserModel; +import cn.celess.blog.entity.request.LoginReq; +import cn.celess.blog.entity.request.UserReq; +import cn.celess.blog.mapper.UserMapper; +import cn.celess.blog.util.MD5Util; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.io.File; +import java.io.FileInputStream; +import java.util.*; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static cn.celess.blog.enmu.ResponseEnum.*; + +public class UserControllerTest extends BaseTest { + + @Autowired + UserMapper userMapper; + + @Test + public void login() throws Exception { + assertNotNull(userLogin()); + assertNotNull(adminLogin()); + // 用户不存在 + LoginReq req = new LoginReq(); + req.setEmail("zh@celess.cn"); + req.setPassword("123456789"); + req.setIsRememberMe(false); + JSONObject loginReq = JSONObject.fromObject(req); + mockMvc.perform(post("/login").content(loginReq.toString()).contentType("application/json")) + .andDo(result -> + assertEquals(USER_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + } + + @Test + public void registration() { + // 自行手动测试! + // TODO : + } + + @Test + public void logout() throws Exception { + mockMvc.perform(get("/logout")).andDo(result -> assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(get("/logout").header("Authorization", userLogin())).andDo(result -> assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + } + + @Test + public void updateInfo() throws Exception { + String desc = UUID.randomUUID().toString().substring(0, 4); + String disPlayName = UUID.randomUUID().toString().substring(0, 4); + mockMvc.perform(put("/user/userInfo/update?desc=" + desc + "&displayName=" + disPlayName).header("Authorization", userLogin())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + UserModel u = (UserModel) JSONObject.toBean(object.getJSONObject(Result), UserModel.class); + assertEquals(desc, u.getDesc()); + assertEquals(disPlayName, u.getDisplayName()); + assertNotNull(u.getId()); + }); + } + + @Test + public void getUserInfo() throws Exception { + mockMvc.perform(get("/user/userInfo").header("Authorization", userLogin())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + UserModel u = (UserModel) JSONObject.toBean(object.getJSONObject(Result), UserModel.class); + assertNotNull(u.getId()); + assertNotNull(u.getEmail()); + assertNotNull(u.getDisplayName()); + assertNotNull(u.getEmailStatus()); + assertNotNull(u.getAvatarImgUrl()); + assertNotNull(u.getDesc()); + assertNotNull(u.getRecentlyLandedDate()); + assertNotNull(u.getRole()); + }); + } + + @Test + public void upload() throws Exception { + File logoFile = new File("C:\\Users\\zh564\\Pictures\\logo.png"); + MockMultipartFile file = new MockMultipartFile("file", "logo.png", MediaType.IMAGE_PNG_VALUE, new FileInputStream(logoFile)); + mockMvc.perform(multipart("/user/imgUpload").file(file)).andDo(result -> assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(multipart("/user/imgUpload").file(file).header("Authorization", userLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertNotNull(object.getString(Result)); + }); + } + + @Test + public void sendResetPwdEmail() { + // ignore + } + + @Test + public void sendVerifyEmail() { + // ignore + } + + @Test + public void emailVerify() { + // ignore + } + + @Test + public void resetPwd() { + // ignore + } + + @Test + public void multipleDelete() throws Exception { + List userList = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + String s = UUID.randomUUID().toString(); + String email = s.substring(s.length() - 4) + "@celess.cn"; + String pwd = MD5Util.getMD5("123456789"); + int i1 = userMapper.addUser(email, pwd); + if (i1 == 0) { + continue; + } + userList.add(userMapper.findByEmail(email)); + if (i == 9) { + //设置一个管理员 + userMapper.setUserRole(userMapper.findByEmail(email).getId(), "admin"); + } + } + List idList = new ArrayList<>(); + userList.forEach(user -> idList.add(user.getId())); + System.out.println("id :: == > " + idList.toString()); + mockMvc.perform(delete("/admin/user/delete").content(idList.toString()).contentType("application/json")) + .andDo(result -> assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(delete("/admin/user/delete").content(idList.toString()).contentType("application/json").header("Authorization", userLogin())) + .andDo(result -> assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(delete("/admin/user/delete").content(idList.toString()).contentType("application/json").header("Authorization", adminLogin())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONArray jsonArray = object.getJSONArray(Result); + jsonArray.forEach(o -> { + JSONObject json = JSONObject.fromObject(o); + // 判断响应数据中是否包含输入的id + assertTrue(idList.contains((long) json.getInt("id"))); + // 判断处理状态 + boolean status = json.getBoolean("status"); + if (json.containsKey("msg")) + assertFalse(status); + else + assertTrue(status); + }); + }); + + } + + @Test + public void updateInfoByAdmin() throws Exception { + UserReq userReq = new UserReq(); + String email = UUID.randomUUID().toString().substring(0, 4) + "@celess.cn"; + userMapper.addUser(email, MD5Util.getMD5("123456789")); + User userByDb = userMapper.findByEmail(email); + userReq.setId(userByDb.getId()); + userReq.setPwd(UUID.randomUUID().toString().replaceAll("-", "").substring(0, 10)); + userReq.setDesc(UUID.randomUUID().toString()); + userReq.setEmailStatus(new Random().nextBoolean()); + userReq.setRole("admin"); + userReq.setDisplayName(UUID.randomUUID().toString().substring(0, 4)); + userReq.setEmail(UUID.randomUUID().toString().substring(0, 5) + "@celess.cn"); + mockMvc.perform(put("/admin/user").contentType("application/json").content(JSONObject.fromObject(userReq).toString())) + .andDo(result -> assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(put("/admin/user").contentType("application/json").header("Authorization", userLogin()).content(JSONObject.fromObject(userReq).toString())) + .andDo(result -> assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(put("/admin/user").contentType("application/json").header("Authorization", adminLogin()).content(JSONObject.fromObject(userReq).toString())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + UserModel user = (UserModel) JSONObject.toBean(object.getJSONObject(Result), UserModel.class); + assertEquals(userReq.getId(), user.getId()); + assertEquals(userReq.getRole(), user.getRole()); + assertEquals(userReq.getEmail(), user.getEmail()); + assertEquals(userReq.getDesc(), user.getDesc()); + assertEquals(userReq.getDisplayName(), user.getDisplayName()); + }); + } + + @Test + public void getAllUser() throws Exception { + mockMvc.perform(get("/admin/users?page=1&count=10")) + .andDo(result -> assertEquals(HAVE_NOT_LOG_IN.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(get("/admin/users?page=1&count=10").header("authorization", userLogin())) + .andDo(result -> assertEquals(PERMISSION_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + mockMvc.perform(get("/admin/users?page=1&count=10").header("Authorization", adminLogin())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + // 结果集非空 + assertNotNull(object.getJSONObject(Result)); + // 判断pageInfo是否包装完全 + JSONObject resultJson = JSONObject.fromObject(object.getJSONObject(Result)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(resultJson, PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + // 内容完整 + for (Object user : pageInfo.getList()) { + UserModel u = (UserModel) JSONObject.toBean(JSONObject.fromObject(user), UserModel.class); + assertNotNull(u.getId()); + assertNotNull(u.getEmail()); + assertNotNull(u.getRole()); + assertNotNull(u.getEmailStatus()); + assertNotNull(u.getDisplayName()); + } + }); + } + + @Test + public void getEmailStatus() throws Exception { + String email = UUID.randomUUID().toString().substring(0, 4) + "@celess.cn"; + mockMvc.perform(get("/emailStatus/" + email)).andDo(result -> { + String content = result.getResponse().getContentAsString(); + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(content).getInt(Code)); + assertFalse(JSONObject.fromObject(content).getBoolean(Result)); + }); + email = "a@celess.cn"; + mockMvc.perform(get("/emailStatus/" + email)).andDo(result -> { + String content = result.getResponse().getContentAsString(); + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(content).getInt(Code)); + assertTrue(JSONObject.fromObject(content).getBoolean(Result)); + }); + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/VisitorControllerTest.java b/src/test/java/cn/celess/blog/controller/VisitorControllerTest.java new file mode 100644 index 0000000..457613d --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/VisitorControllerTest.java @@ -0,0 +1,82 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.model.VisitorModel; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static cn.celess.blog.enmu.ResponseEnum.*; + +public class VisitorControllerTest extends BaseTest { + + @Test + public void getVisitorCount() throws Exception { + mockMvc.perform(get("/visitor/count")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.containsKey(Result)); + }); + } + + @Test + public void page() throws Exception { + int count = 10; + int page = 1; + mockMvc.perform(get("/admin/visitor/page?count=" + count + "&page=" + page).header("Authorization", adminLogin())) + .andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONObject resultJson = JSONObject.fromObject(object.getJSONObject(Result)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(resultJson, PageInfo.class); + assertNotEquals(0, pageInfo.getTotal()); + assertNotEquals(0, pageInfo.getStartRow()); + assertNotEquals(0, pageInfo.getEndRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + for (Object ver : pageInfo.getList()) { + VisitorModel v = (VisitorModel) JSONObject.toBean(JSONObject.fromObject(ver), VisitorModel.class); + assertNotEquals(0, v.getId()); + assertNotNull(v.getDate()); + } + }); + } + + @Test + public void add() throws Exception { + mockMvc.perform(post("/visit")).andDo(MockMvcResultHandlers.print()).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + VisitorModel visitorModel = (VisitorModel) JSONObject.toBean(object.getJSONObject(Result), VisitorModel.class); + assertNotEquals(0, visitorModel.getId()); + assertNotNull(visitorModel.getIp()); + }); + } + + @Test + public void dayVisitCount() throws Exception { + mockMvc.perform(get("/dayVisitCount")).andDo(MockMvcResultHandlers.print()).andDo(result -> + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + } + + @Test + public void ipLocation() throws Exception { + String ip = "127.0.0.1"; + mockMvc.perform(get("/ip/" + ip)).andDo(MockMvcResultHandlers.print()).andDo(result -> { + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + assertTrue(JSONObject.fromObject(result.getResponse().getContentAsString()).containsKey(Result)); + }); + } + + @Test + public void getIp() throws Exception { + mockMvc.perform(get("/ip")).andDo(MockMvcResultHandlers.print()).andDo(result -> { + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + assertEquals("127.0.0.1", JSONObject.fromObject(result.getResponse().getContentAsString()).getString(Result)); + }); + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/controller/WebUpdateInfoControllerTest.java b/src/test/java/cn/celess/blog/controller/WebUpdateInfoControllerTest.java new file mode 100644 index 0000000..ca42063 --- /dev/null +++ b/src/test/java/cn/celess/blog/controller/WebUpdateInfoControllerTest.java @@ -0,0 +1,125 @@ +package cn.celess.blog.controller; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.WebUpdate; +import cn.celess.blog.entity.model.WebUpdateModel; +import cn.celess.blog.mapper.WebUpdateInfoMapper; +import com.github.pagehelper.PageInfo; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import static cn.celess.blog.enmu.ResponseEnum.*; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +public class WebUpdateInfoControllerTest extends BaseTest { + @Autowired + WebUpdateInfoMapper mapper; + + @Test + public void create() throws Exception { + String info = UUID.randomUUID().toString(); + mockMvc.perform(post("/admin/webUpdate/create?info=" + info).header("Authorization", adminLogin())).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertTrue(object.containsKey(Result)); + WebUpdateModel webUpdateModel = (WebUpdateModel) JSONObject.toBean(object.getJSONObject(Result), WebUpdateModel.class); + assertEquals(info, webUpdateModel.getInfo()); + assertNotNull(webUpdateModel.getTime()); + assertNotEquals(0, webUpdateModel.getId()); + }); + } + + @Test + public void del() throws Exception { + // 新增数据 + WebUpdate webUpdate = new WebUpdate(); + webUpdate.setUpdateInfo(UUID.randomUUID().toString()); + webUpdate.setUpdateTime(new Date()); + mapper.insert(webUpdate); + // 接口测试 + List updateList = mapper.findAll(); + WebUpdate update = updateList.get(updateList.size() - 1); + assertNotEquals(0, update.getId()); + + long id = update.getId(); + mockMvc.perform(delete("/admin/webUpdate/del/" + id).header("Authorization", adminLogin())).andDo(result -> { + assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); + assertTrue(JSONObject.fromObject(result.getResponse().getContentAsString()).getBoolean(Result)); + }); + do { + id += 2; + } while (mapper.existsById(id)); + System.out.println("准备删除ID=" + id + "的不存在记录"); + mockMvc.perform(delete("/admin/webUpdate/del/" + id).header("Authorization", adminLogin())).andDo(result -> + assertEquals(DATA_NOT_EXIST.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)) + ); + } + + @Test + public void update() throws Exception { + // 新增数据 + WebUpdate webUpdate = new WebUpdate(); + webUpdate.setUpdateInfo(UUID.randomUUID().toString()); + webUpdate.setUpdateTime(new Date()); + mapper.insert(webUpdate); + List all = mapper.findAll(); + WebUpdate update = all.get(all.size() - 1); + assertNotEquals(0, update.getId()); + assertNotNull(update.getUpdateInfo()); + String info = UUID.randomUUID().toString(); + mockMvc.perform(put("/admin/webUpdate/update?id=" + update.getId() + "&info=" + info).header("Authorization", adminLogin())).andDo(result -> { + List list = mapper.findAll(); + WebUpdate up = list.get(list.size() - 1); + assertEquals(update.getId(), up.getId()); + assertEquals(update.getUpdateTime(), up.getUpdateTime()); + assertEquals(info, up.getUpdateInfo()); + }); + } + + @Test + public void findAll() throws Exception { + mockMvc.perform(get("/webUpdate")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + JSONArray jsonArray = object.getJSONArray(Result); + jsonArray.forEach(o -> { + WebUpdateModel webUpdate = (WebUpdateModel) JSONObject.toBean(JSONObject.fromObject(o), WebUpdateModel.class); + assertNotEquals(0, webUpdate.getId()); + assertNotNull(webUpdate.getTime()); + assertNotNull(webUpdate.getInfo()); + }); + }); + } + + @Test + public void page() throws Exception { + mockMvc.perform(get("/webUpdate/pages?page=1&count=10")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + assertNotNull(object.getJSONObject(Result)); + PageInfo pageInfo = (PageInfo) JSONObject.toBean(object.getJSONObject(Result), PageInfo.class); + assertNotEquals(0, pageInfo.getEndRow()); + assertNotEquals(0, pageInfo.getStartRow()); + assertEquals(1, pageInfo.getPageNum()); + assertEquals(10, pageInfo.getPageSize()); + pageInfo.getList().forEach(o -> { + WebUpdateModel model = (WebUpdateModel) JSONObject.toBean(JSONObject.fromObject(o), WebUpdateModel.class); + assertNotEquals(0, model.getId()); + assertNotNull(model.getTime()); + assertNotNull(model.getInfo()); + }); + }); + } + + @Test + public void lastestUpdateTime() throws Exception { + mockMvc.perform(get("/lastestUpdateTime")).andDo(result -> assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code))); + } +} \ No newline at end of file diff --git a/src/test/java/cn/celess/blog/filter/AuthorizationFilter.java b/src/test/java/cn/celess/blog/filter/AuthorizationFilter.java new file mode 100644 index 0000000..0ff4b3a --- /dev/null +++ b/src/test/java/cn/celess/blog/filter/AuthorizationFilter.java @@ -0,0 +1,64 @@ +package cn.celess.blog.filter; + +import cn.celess.blog.BaseTest; +import net.sf.json.JSONObject; +import org.junit.Test; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.junit.Assert.*; +import static cn.celess.blog.enmu.ResponseEnum.*; + +/** + * @Author: 小海 + * @Date: 2019/11/28 16:05 + * @Description: 授权拦截器的测试类 + */ +public class AuthorizationFilter extends BaseTest { + + @Test + public void UserAccess() throws Exception { + String token = ""; + // 未登录 + mockMvc.perform(get("/user/userInfo").header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(HAVE_NOT_LOG_IN.getCode(), object.getInt(Code)); + }); + token = userLogin(); + mockMvc.perform(get("/user/userInfo").header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + }); + } + + @Test + public void AdminAccess() throws Exception { + String token = ""; + // 未登录 + mockMvc.perform(get("/admin/articles?page=1&count=1").header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(HAVE_NOT_LOG_IN.getCode(), object.getInt(Code)); + }); + token = userLogin(); + mockMvc.perform(get("/admin/articles?page=1&count=1").header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(PERMISSION_ERROR.getCode(), object.getInt(Code)); + }); + token = adminLogin(); + mockMvc.perform(get("/admin/articles?page=1&count=1").header("Authorization", token)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(SUCCESS.getCode(), object.getInt(Code)); + }); + } + + @Test + public void VisitorAccess() throws Exception { + mockMvc.perform(get("/user/userInfo")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(HAVE_NOT_LOG_IN.getCode(), object.getInt(Code)); + }); + mockMvc.perform(get("/admin/articles?page=1&count=1")).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + assertEquals(HAVE_NOT_LOG_IN.getCode(), object.getInt(Code)); + }); + } +} diff --git a/src/test/java/cn/celess/blog/filter/MultipleSubmitFilter.java b/src/test/java/cn/celess/blog/filter/MultipleSubmitFilter.java new file mode 100644 index 0000000..5eca79b --- /dev/null +++ b/src/test/java/cn/celess/blog/filter/MultipleSubmitFilter.java @@ -0,0 +1,42 @@ +package cn.celess.blog.filter; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.enmu.ResponseEnum; +import net.sf.json.JSONObject; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; + +import javax.servlet.http.Cookie; + +/** + * @Author: 小海 + * @Date: 2019/11/28 16:17 + * @Description: 测试重复请求 + */ +public class MultipleSubmitFilter extends BaseTest { + + private MockHttpSession session = null; + + @Test + public void submitTest() throws Exception { + session = new MockHttpSession(); + sendRequest(ResponseEnum.SUCCESS); + sendRequest(ResponseEnum.FAILURE, "重复请求"); + } + + + private void sendRequest(ResponseEnum expectResponse, String... msg) throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/counts").session(session)).andDo(result -> { + JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); + Assert.assertEquals(expectResponse.getCode(), object.getInt(Code)); + if (msg.length != 0) { + Assert.assertEquals(msg[0], object.getString("msg")); + } + }); + } +} diff --git a/src/test/java/cn/celess/blog/util/JwtUtilTest.java b/src/test/java/cn/celess/blog/util/JwtUtilTest.java new file mode 100644 index 0000000..2a41ec4 --- /dev/null +++ b/src/test/java/cn/celess/blog/util/JwtUtilTest.java @@ -0,0 +1,52 @@ +package cn.celess.blog.util; + +import cn.celess.blog.BaseTest; +import cn.celess.blog.entity.User; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.junit.Assert.*; + +public class JwtUtilTest extends BaseTest { + + @Autowired + JwtUtil jwtUtil; + + + @Test + public void generateToken() { + User user = new User(); + user.setEmail("a@celess.cn"); + String s = jwtUtil.generateToken(user, true); + System.out.println(s); + assertNotNull(s); + } + + @Test + public void validateToken() { + User user = new User(); + user.setEmail("a@celess.cn"); + assertTrue(jwtUtil.validateToken(createToken(), user)); + } + + @Test + public void isTokenExpired() { + assertFalse(jwtUtil.isTokenExpired(createToken())); + } + + @Test + public void getUsernameFromToken() { + assertEquals("a@celess.cn", jwtUtil.getUsernameFromToken(createToken())); + } + + @Test + public void getExpirationDateFromToken() { + assertNotNull(jwtUtil.getExpirationDateFromToken(createToken())); + } + + private String createToken() { + User user = new User(); + user.setEmail("a@celess.cn"); + return jwtUtil.generateToken(user, true); + } +} \ No newline at end of file