Gitee地址 :文章些许混乱,以库为准
修改数据库ms_comment的articleId为bigInt,不然文章id会超出范围
前端项目运行命令
npm install
npm run build
npm run dev
创建一个项目,如果是平时开发的,首先都要导入如下依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
如果创建的项目报如下错误的话,请你降低SpringBoot版本
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-08-15 20:58:31.515 ERROR 4684 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties': Lookup method resolution failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:289) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1284) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1201) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.7.jar:5.3.7]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.7.jar:5.3.7]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.0.jar:2.5.0]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) [spring-boot-2.5.0.jar:2.5.0]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) [spring-boot-2.5.0.jar:2.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:337) [spring-boot-2.5.0.jar:2.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) [spring-boot-2.5.0.jar:2.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) [spring-boot-2.5.0.jar:2.5.0]
at cn.mldn.admin.AdminApp.main(AdminApp.java:11) [classes/:na]
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:267) ~[spring-beans-5.3.7.jar:5.3.7]
... 18 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/sql/init/DatabaseInitializationMode
at java.lang.Class.getDeclaredMethods0(Native Method) ~[na:1.8.0_241]
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) ~[na:1.8.0_241]
at java.lang.Class.getDeclaredMethods(Class.java:1975) ~[na:1.8.0_241]
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463) ~[spring-core-5.3.7.jar:5.3.7]
... 20 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.sql.init.DatabaseInitializationMode
at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_241]
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_241]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[na:1.8.0_241]
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_241]
... 24 common frames omitted
下载前端项目(这个的话,可以去QQ群下载)
搭建项目(用idea创建项目)
1)idea create new xxxx在里面一系列的操作
2)导入依赖
a -----:parent和properties的导入 ```
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
b--导入其他的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.lumgroupId>
<artifactId>blog-parentartifactId>
<version>1.0-SNAPSHOTversion>
<modules>
<module>blogmodule>
modules>
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.2.2version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.10.10version>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blog-parentartifactId>
<groupId>com.lumgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>blogartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.2.2version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.10.10version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
<dependency>
<groupId>com.qiniugroupId>
<artifactId>qiniu-java-sdkartifactId>
<version>[7.7.0, 7.7.99]version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-extensionartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>commons-configurationgroupId>
<artifactId>commons-configurationartifactId>
<version>1.10version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigureartifactId>
<version>2.5.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-bootartifactId>
<version>2.5.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.8version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.3.8version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
dependencies>
project>
server.port=8888
#配置项目名称
spring.application.name=blog
#数据库的设置
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#Mybaties-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class);
}
}
package com.lum.blog.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.lum.blog.mapper")
public class MyBatiesPlusConfig {
}
在项目中,肯定要用到分页的,所有要用到Mybatis的分页插件。对MybatisPlusConfig做出如下修改:
//Mybatis-plus分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor( new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
package com.lum.blog.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//做跨域配置,为什么要做这个跨域的配置呢,因为比如:我前端的端口号是8080,而我后端接口是8888
@Override
public void addCorsMappings(CorsRegistry registry) {
//addMapping就是所有的文件,allowedOrigins指的是可以那个地址可以访问
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
}
1.1接口说明
接口url:/articles
请求方式:post请求
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
page | int | 当前页 |
pagesize | int | 每页显示的数量 |
返回数据:
~~~json
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
"summary": "通过Spring Boot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过`java -jar`命令就可以运行起来。\r\n\r\n这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。",
"commentCounts": 2,
"viewCounts": 54,
"weight": 1,
"createDate": "2021-06-26 15:58",
"author": "lum",
"body": null,
"tags": [
{
"id": 5,
"avatar": null,
"tagName": "444"
},
{
"id": 7,
"avatar": null,
"tagName": "22"
},
{
"id": 8,
"avatar": null,
"tagName": "11"
}
],
"categorys": null
},
{
"id": 9,
"title": "Vue.js 是什么",
"summary": "Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。",
"commentCounts": 0,
"viewCounts": 3,
"weight": 0,
"createDate": "2609-06-27 11:25",
"author": "12",
"body": null,
"tags": [
{
"id": 7,
"avatar": null,
"tagName": "22"
}
],
"categorys": null
},
{
"id": 10,
"title": "Element相关",
"summary": "本节将介绍如何在项目中使用 Element。",
"commentCounts": 0,
"viewCounts": 3,
"weight": 0,
"createDate": "2609-06-27 11:25",
"author": "12",
"body": null,
"tags": [
{
"id": 5,
"avatar": null,
"tagName": "444"
},
{
"id": 6,
"avatar": null,
"tagName": "33"
},
{
"id": 7,
"avatar": null,
"tagName": "22"
},
{
"id": 8,
"avatar": null,
"tagName": "11"
}
],
"categorys": null
}
]
}
1.2表结构
既然我们有了前端和页面要返回的数据,那我们的用户什么的,肯定都要和类有关系
返回数据的文章数据表
~~~sql
CREATE TABLE `blog`.`ms_article` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`comment_counts` int(0) NULL DEFAULT NULL COMMENT '评论数量',
`create_date` bigint(0) NULL DEFAULT NULL COMMENT '创建时间',
`summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '简介',
`title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
`view_counts` int(0) NULL DEFAULT NULL COMMENT '浏览数量',
`weight` int(0) NOT NULL COMMENT '是否置顶',
`author_id` bigint(0) NULL DEFAULT NULL COMMENT '作者id',
`body_id` bigint(0) NULL DEFAULT NULL COMMENT '内容id',
`category_id` int(0) NULL DEFAULT NULL COMMENT '类别id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
~~~
//标签表,由文章可以查看其他的
~~~sql
CREATE TABLE `blog`.`ms_tag` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`article_id` bigint(0) NOT NULL,
`tag_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE,
INDEX `tag_id`(`tag_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
~~~
//用户数据表
~~~sql
CREATE TABLE `blog`.`ms_sys_user` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
`admin` bit(1) NULL DEFAULT NULL COMMENT '是否管理员',
`avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
`create_date` bigint(0) NULL DEFAULT NULL COMMENT '注册时间',
`deleted` bit(1) NULL DEFAULT NULL COMMENT '是否删除',
`email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`last_login` bigint(0) NULL DEFAULT NULL COMMENT '最后登录时间',
`mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
`nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加密盐',
`status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
1.3 Dao开发
com.lum.blog.dao.pojo.Article.java
package com.lum.blog.dao.pojo;
import lombok.Data;
/*
博客管理
*/
@Data
public class Article {
public static final int Article_TOP = 1;
public static final int Article_Common = 0;
private Long id;
private String title;
private String summary;
private int commentCounts;
private int viewCounts;
/**
* 作者id
*/
private Long authorId;
/**
* 内容id
*/
private Long bodyId;
/**
*类别id
*/
private Long categoryId;
/**
* 置顶
*/
private int weight = Article_Common;
/**
* 创建时间
*/
private Long createDate;
}
com.lum.blog.dao.pojo.SysUser.java
package com.lum.blog.dao.pojo;
import lombok.Data;
/*
用户管理
*/
@Data
public class SysUser {
private Long id;
private String account;
private Integer admin;
private String avatar;
private Long createDate;
private Integer deleted;
private String email;
private Long lastLogin;
private String mobilePhoneNumber;
private String nickname;
private String password;
private String salt;
private String status;
}
com.lum.blog.dao.pojo.Tag.java
package com.lum.blog.dao.pojo;
import lombok.Data;
/*
标签管理
*/
@Data
public class Tag {
private Long id;
private String avatar;
private String tagName;
}
1.4对应的Mapper的创建
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.Article;
public interface ArticleMapper extends BaseMapper<Article> {
}
*******************************************************************
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.SysUser;
public interface SysUserMapper extends BaseMapper<SysUser> {
}
*******************************************************************
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.Tag;
public interface TagMapper extends BaseMapper<Tag> {
}
1.5mapper创建好了,该Controller了
创建ArticleController,这个代表的是文章类的控制器
package com.lum.blog.controller;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.PageParams;
import org.springframework.web.bind.annotation.*;
//使用json数据进行交互
@RestController
@RequestMapping("articles")
public class ArticleController {
//为什么用post请求,因为前面的接口说明post请求
//对于另外的参数,建立vo下面的PageParams类专门表示参数
/**
* 首页文章列表
* @param pageParams
* return 返回承担返回数据Result的类
*/
@PostMapping
public Result listArticle(@RequestBody PageParams pageParams){
//对于接受的参数问题,这里是RequestBody接收
return articleService.listArticle(pageParams);
}
}
1)这里就有补充了,我们传入的参数,是PageParms类代表,在
com.lum.Blog下面创建vo的目录,然后创建PageParms的类
package com.lum.blog.vo.params;
import lombok.Data;
//承担着返回页数和数量的类
@Data
public class PageParams {
private int page = 1;
private int pageSize = 10;
}
2)返回的数据Result的类
package com.lum.blog.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//承担着返回首页文章列表的类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
//代表是否成功
private boolean success;
//代表我们的编码
private int code;
//代表的是消息
private String msg;
//代表的是数据
private Object data;
//代表成功
public static Result success(Object data) {
return new Result(true , 200, "success" ,data);
}
//代表失败的
public static Result Fail(int code, String msg) {
//没有数据可以返回,所有data是null
return new Result(false , code, msg,null);
}
}
1)首先我们编写的DAO层和数据库里面的表名字要对应起来。
2)然后就是编写的XXX-Mapper层层要继承BaseMapper
3)然后就是我们一般不再编写他的xxxMapper.xml而是要配置的是 xxxService+xxxServiceImpl,如果要编写xxxMapper.xml和Mybatis一样的配置
1.6 service
从上面中,我们就可以看出,我们已经开发差不多了,但是想返回数据了,但是listArticle这个方法却没有Service来读取数据,所以来开发Service和数据读取的方法
1)首先来编写这个Service层,在src/main/java下面建立service文件夹并且在下面ArticleService文件和Impl文件夹
package com.lum.blog.service;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.PageParams;
public interface ArticleService {
/**
* 分页查询 文章列表
* @param pageParams
* @return
*/
Result listArticle(PageParams pageParams);
}
2)再来编写他的Impl文件ArticleServiceImpl文件
package com.lum.blog.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lum.blog.dao.mapper.ArticleMapper;
import com.lum.blog.dao.pojo.Article;
import com.lum.blog.service.ArticleService;
import com.lum.blog.vo.ArticleVo;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Override
public Result listArticle(PageParams pageParams) {
//1. 这个是分页查询的类(代表着分离模式),要传入的是页面的页数和页面总数
Page<Article> page = new Page<Article>(pageParams.getPage(),pageParams.getPageSize());
//2. LambdaQueryWrapper是MybatisPlus提供的,需要就导入这个包就可以了
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//3. 这里是根据字体排序
//queryWrapper.orderByDesc(Article::getWeight);
//4. 这里设置的是根据时间排序
//queryWrapper.orderByDesc(Article::getCreateDate);
//5. 这个方法 default Children orderByDesc(boolean condition, R column, R... columns) {是可变长参数的
queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
// 因为articleMapper继承了BaseMapper了的,所有设查询的参数和查询出来的排序方式
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
//这个就是我们查询出来的数据的数组了
List<Article> records = articlePage.getRecords();
//因为页面展示出来的数据不一定和数据库一样,所有我们要做一个转换
//将在查出数据库的数组复制到articleVo中实现解耦合,vo和页面数据交互
List<ArticleVo> articleVoList = copyList(records);
return Result.success(articleVoList);
}
private List<ArticleVo> copyList(List<Article> records) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record));
}
return articleVoList;
}
//这个方法是主要点是BeanUtils,又Spring提供的,专门用来拷贝的,想Article和articlevo相同属性的拷贝过来返回
private ArticleVo copy(Article article) {
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
return articleVo;
}
}
其中需要创建ArticleVo和TagVo
package com.lum.blog.vo;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
private Long id;
private String title;
private String summary; //简介
private int commentCounts;
private int ViewCounts;
private int weight; //置顶
private String createDate; //创建时间
private String author;
//暂时不需要
// private ArticleBodyVo body;
private List<TagVo> tags;
//暂时不需要
// private List categories;
}
package com.lum.blog.vo;
import lombok.Data;
@Data
public class TagVo {
private Long id;
private String tagName;
}
vo和页面交互的数据不应该和数据库映射对象进行耦合,最好分开
这里补充一下这个articleVo,这个类,因为平时我们开发出来的东西,到时候要
到数据库里面找数据嘛,然后找出来不一定一样,要把它一样的拷贝,不一样的返回null
总结:有了以上的配置之后,页面的内容就可以展示了
问题引入:在之前开发的首页内容显示中,文章下面是没有标签,作者的信息等内容的,要开发下面有内容
2.1在ArticleServiceImpl中实现
思考:并不是所有的接口都需要标签,作者信息
增加两个boolean isTag,isAuthor来进行判断
在copyList中增加代码
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor));
}
return articleVoList;
}
在copy中增加代码
//这个方法是主要点是BeanUtils,又Spring提供的,专门用来拷贝的,想Article和articleVo相同属性的拷贝过来返回
private ArticleVo copy(Article article,boolean isTag,boolean isAuthor) {
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
/*
并不是所有的接口都需要标签,作者信息
增加两个参数boolean isTag,boolean isAuthor
*/
//需要开发tagService
if (isTag) {
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsByArticleId(articleId));
}
//需要开发authorService
if (isAuthor) {
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
return articleVo;
}
2.2标签tag
编写TagService
package com.lum.blog.service;
import com.lum.blog.vo.TagVo;
import java.util.List;
public interface TagService {
List<TagVo> findTagsByArticleId(Long articleId);
}
编写实现类TagServiceImpl
package com.lum.blog.service.Impl;
import com.lum.blog.dao.mapper.TagMapper;
import com.lum.blog.dao.pojo.Tag;
import com.lum.blog.service.TagService;
import com.lum.blog.vo.TagVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
/*copyList传递tag*/
public TagVo copy(Tag tag){
TagVo tagVo = new TagVo();
BeanUtils.copyProperties(tag,tagVo);
return tagVo;
}
public List<TagVo> copyList(List<Tag> tagList) {
List<TagVo> tagVoList = new ArrayList<>();
for (Tag tag : tagList) {
tagVoList.add(copy(tag));
}
return tagVoList;
}
/**********************************/
@Override
public List<TagVo> findTagsByArticleId(Long articleId) {
/* MyBatisPlus无法实现多表查询 */
List<Tag> tags=tagMapper.findTagsByArticleId(articleId);
return copyList(tags);
}
}
编写TagMapper
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.Tag;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TagMapper extends BaseMapper<Tag> {
/**
* 根据文章id查询标签列表
* @param articleId
* @return
*/
List<Tag> findTagsByArticleId(Long articleId);
}
在resourse下建立TagMapper.xml
路径与接口包一致(com.lum.blog.dao.mapper)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lum.blog.dao.mapper.TagMapper">
<select id="findTagsByArticleId" parameterType="long" resultType="com.lum.blog.dao.pojo.Tag">
select id,avatar,tag_name as tagName from ms_tag
where id in
(select tag_id from ms_article_tag where article_id=#{articleId})
select>
mapper>
parameterType="long"对应List findTagsByArticleId(Long articleId)的articleId
select id,avatar,tag_name as tagName from ms_tag
where id in
(select tag_id from ms_article_tag where article_id=#{articleId})
在关联表中查询标签的id,auatar,tagName
可以在application.properties中mybatis-plus开启驼峰命名
mybatis-plus.configuration.map-underscore-to-camel-case=true
这样SQL语句就不需要as别名。
接下来将TagService注入到ArticleImpl中实现
if (isTag) {
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsByArticleId(articleId));
}
2.3作者author
建立接口SysUserService
package com.lum.blog.service;
import com.lum.blog.dao.pojo.SysUser;
public interface SysUserService {
SysUser findUserById(Long id);
}
编写实现类SysUserServiceImpl
package com.lum.blog.service.Impl;
import com.lum.blog.dao.mapper.SysUserMapper;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser findUserById(Long id) {
/*防止出现id为空的情况*/
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null) {
sysUser = new SysUser();
sysUser.setNickname("鹿鸣");
}
return sysUser;
}
}
注入SysUserMapper,然后编写查询
将SysUserService注入到文章实现类ArticleServiceImpl中
if (isAuthor) {
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
在SysUserImpl中编写如果出现空的情况处理办法
public SysUser findUserById(Long id) {
/*防止出现id为空的情况*/
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null) {
sysUser = new SysUser();
sysUser.setNickname("lum");
}
return sysUser;
}
在copyList中加入返回值istag,isauthor
List<ArticleVo> articleVoList = copyList(records,true,true);
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor));
}
在前方代码中已经有体现在哪加
1.标签所拥有的文章数量最多
2.查询 根据tag_id分组,技术,从大到小排,取前limit个
3.1 接口说明
接口url:/tag/hot
请求方式:Get
请求参数:无
返回数据:
{
"successs":true
"code"200
"msg":"success"
"data"[
{
"id":1,
"tagName":"最热"
}
]
}
3.2编码
先写controller
创建TagsController
package com.lum.blog.controller;
import com.lum.blog.service.TagService;
import com.lum.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("tags")
public class TagsController {
@Autowired
private TagService tagService;
@GetMapping("hot")
public Result hot() {
int limit =6; //最热6个
return tagService.hots(limit);
}
}
在tagService实现hots方法
package com.lum.blog.service;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.TagVo;
import java.util.List;
public interface TagService {
List<TagVo> findTagsByArticleId(Long articleId);
Result hots(int limit);
}
首先分析SQL语句
select tag_id
from ms_article_tag
group by tag_id
order by count(*) limit 2
TagServiceImpl实现 Result hots(int limit)
@Override
public Result hots(int limit) {
/**
* 1.标签所拥有的文章数量最多
* 2.查询 根据tag_id分组,技术,从大到小排序,取前limit个
*/
List<Long> tagIds= tagMapper.findHotsTagId(limit);
return null;
}
在TagMapper中创建findHostTagId(limit)方法
/**
* 查询最热标签前limit条
* @param limit
* @return
*/
List<Long> findHotsTagId(int limit);
在资源文件TagMapper中生成findHotsTagId并添加==parameterType=“int”==属性以及sql语句
<select id="findHotsTagId" parameterType="int" resultType="java.lang.Long">
select tag_id
from ms_article_tag
group by tag_id
order by count(*) limit 6
select>
这时查询出最热的tagId,需要根据tagid查询tagName,Tag
继续Result hot
@Override
public Result hots(int limit) {
/**
* 1.标签所拥有的文章数量最多
* 2.查询 根据tag_id分组,技术,从大到小排序,取前limit个
*/
List<Long> tagIds= tagMapper.findHotsTagId(limit);
/*判断tagIds是否为空*/
if(CollectionUtils.isEmpty(tagIds)){
return Result.success(Collections.emptyList());
}
// 需求的是tagId和tagName tag对象
// select * from tag where id in (1,2,3)
List<Tag> tagList= tagMapper.findTagsByIds(tagIds);
return Result.success(tagList);
}
所以需要在TagMapper.java中添加方法
/**
* 根据tagId查询Tag对象
* @param tagIds
* @return
*/
List<Tag> findTagsByIds(List<Long> tagIds);
在资源TagMapper.xml中添加
<select id="findTagsByIds" parameterType="list" resultType="com.lum.blog.dao.pojo.Tag">
select id,tag_name from ms_tag
where id in
<foreach collection="tagIds" item="tagId" separator="," open="(" close=")">
#{tagId}
foreach>
select>
创建handler(com.lum.blog下.)包并创建AllExceptionHandler类
package com.lum.blog.handler;
import com.lum.blog.vo.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
//对加了@ControllerAdvice的方法进行拦截处理 AOP实现
public class AllExceptionHandler {
@ExceptionHandler(Exception.class) //进行异常处理,处理Exception.class异常
@ResponseBody //返回Json数据
public Result doExceptionHandler(Exception e){
e.printStackTrace();
return Result.fail(-999,"系统异常");
}
}
在controller代码中加入错误代码,进行测试
5.1接口说明
接口url: /articles/hot
请求方式:POST
请求参数:
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
},
{
"id": 2,
"title": "springboot介绍以及入门案例",
}
]
}
5.2 Controller
在ArticleController中增加PostMapper,返回最热的文章
/**
* 首页最热文章
* @return
*/
@PostMapping("hot")
public Result hotArticle(){
int limit=5;
return articleService.hotArticle(limit);
}
5.3Service
在articleService创建对应的方法.hotArticle(limit);
Result hotArticle(int limit);
在articleServiceImpl中实现 Result hotArticle(int limit) 进行sql查询并copyList返回
@Override
public Result hotArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getViewCounts);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit"+limit);
//select id,title from article order by view_counts desc limit 5
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
6.1接口说明
接口url: /articles/new
请求方式:POST
请求参数:
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
},
{
"id": 2,
"title": "springboot介绍以及入门案例",
}
]
}
6.2 Controller
在ArticleController中增加PostMapper,返回最热的文章
/**
* 首页最热文章
* @return
*/
@PostMapping("new")
public Result newArticle(){
int limit=5;
return articleService.newArticle(limit);
}
6.3Service
在articleService创建对应的方法.hotArticle(limit);
Result newArticle(int limit);
在articleServiceImpl中实现 Result hotArticle(int limit) 进行sql查询并copyList返回
@Override
public Result newArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getCreateDate);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit"+limit);
//select id,title from article order by CreateDate desc limit 5
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
文章根据日期的年月进行归档处理
因为数据库的create_date为bigint类型需要/1000得到时间戳,再经过FROM_UNIXTIME进行格式转换
SQL语句为
select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month;
7.1 接口说明
接口url: /articles/listArtchives
请求方式:POST
请求参数:
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"year": "2021",
"mouth": "6",
"count"2
}
]
}
7.2 Controller
ArticleController类
/**
* 首页文章归档
* @return
*/
@PostMapping("listArchives")
public Result listArchives(){
return articleService.listArchives();
}
7.3 Service
ArticleService
/**
* 文章归档
* @return
*/
Result listArchives();
在ArticleServiceImpl中实现
/*文章归档*/
@Override
public Result listArchives() {
List<Archives> archivesList = articleMapper.listArchives();
return Result.success(archivesList);
}
7.4 mapper
因为文章归档返回的数据不是数据库的直接数据,临时使用,不属于pojo对象
所以创建dos包存放非持久化数据
创建Archives类归档信息
package com.lum.blog.dao.dos;
import lombok.Data;
@Data
public class Archives {
private Integer year;
private Integer month;
private Long count;
}
创建Articlemapper
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.dos.Archives;
import com.lum.blog.dao.pojo.Article;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
List<Archives> listArchives();
}
在资源包mapper创建ArticleMapper.xml实现listArchives()
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lum.blog.dao.mapper.ArticleMapper">
<select id="listArchives" resultType="com.lum.blog.dao.dos.Archives">
select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count
from ms_article
group by year,month;
select>
mapper>
8.1接口说明
接口url:/login
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
account | string | 账号 |
password | string | 密码 |
返回的数据
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
8.2JWT技术实现
JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案
JWT的精髓在于:“去中心化”,数据是保存在客户端的。
jwt可以生成一个加密token,作为用户登陆的令牌,当用户登陆成功后,发放给客户端.
请求需要登陆的资源和接口时,将token携带,后端验证token是否合法.
jwt有三部分组成:
Header,{“type”:“JWT”,“alg”:“HS256”}固定
playload,存放自定义信息 比如,用户id,过期时间等,可以被解密,不能存放敏感信息
签证 前两点加上密钥加密组成,只要密钥不丢失,可以认为是安全的
(alg:HS256是算法)
jwt验证,主要就是验证签证部分,是否合法
导入依赖包
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
创建工具类JWTUtils
package com.lum.blog.utils;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtils {
private static final String jwtToken = "12345Lum!@#$%"; //密钥
public static String createToken(Long userId){
Map<String, Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,jwtToken) //签发算法,密钥为jwtToken
.setClaims(claims)//body数据,唯一,自行设置
.setIssuedAt(new Date()) //设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 *1000)); //一天都有效时间
return jwtBuilder.compact();
}
public static Map<String,Object> checkToken(String token){
try {
Jwt parser = Jwts.parser().setSigningKey(jwtToken).parse(token); //解析jwtToken
return (Map<String, Object>) parser.getBody();
}catch ( Exception e) {
e.printStackTrace();
}
return null;
}
/*测试jwt*/
public static void main(String[] args) {
String token = JWTUtils.createToken(100L);
System.out.println(token);
Map<String, Object> map = JWTUtils.checkToken(token);
System.out.println(map.get("userId"));
}
}
8.3Controller
在controller层创建LoginController进行登陆控制
package com.lum.blog.controller;
import com.lum.blog.service.LoginService;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/login")
public class LoginController {
// @Autowired
// private SysUserService sysUserService;
// 不建议,每个Service都有单独的业务
@Autowired
private LoginService loginService;
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
//登陆验证用户 访问用户表
return loginService.login(loginParam);
}
}
需要在Service层编写业务
8.4Service
创建LoginService编写业务
package com.lum.blog.service;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.LoginParam;
public interface LoginService {
/**
* 登陆功能
* @param loginParam
* @return
*/
Result login(LoginParam loginParam);
}
在vo包的param参数包编写登录用到的参数登录参数
在LoginServiceImpl中实现login方法
package com.lum.blog.service.Impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.service.LoginService;
import com.lum.blog.service.SysUserService;
import com.lum.blog.utils.JWTUtils;
import com.lum.blog.vo.ErrorCode;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SysUserService sysUserService;//需要用到用户表
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static final String salt="lum!@#";
@Override
public Result login(LoginParam loginParam) {
/**
* 1.检查参数是否合法
* 2.根据用户名和密码区user表中查询是否存在
* 3.如果不存在 登陆失败
* 4.如果存在,使用jwt 生成 token 返回给前端
* 5.token放入redis中,redis映射token和user信息,设置过期时间,先认证token是否合法,再认证redis中是否存在
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account)||StringUtils.isBlank(password)) {
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
password = DigestUtils.md5Hex(password + salt); //密码加盐
SysUser sysUser = sysUserService.findUser(account,password);
if (sysUser == null) {
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); //过期时间
return Result.success(token);
}
}
在这里先判断用户名或者密码是否为空,空的话返回统一错误码
if (StringUtils.isBlank(account)||StringUtils.isBlank(password)) {
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
再对密码进行加盐处理
private static final String salt="lum!@#";
password = DigestUtils.md5Hex(password + salt); //密码加盐
需要用到redis来作缓存和数据库的中介
进行application.properties中redis配置
设置token和过期时间
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
8.5,登录参数,redis配置,统一错误码
LoginParam登录参数
package com.lum.blog.vo.params;
import lombok.Data;
@Data
public class LoginParam {
private String account;
private String password;
}
redis配置
#redis配置
spring.redis.host=localhost
spring.redis.port=6379
统一错误码
在vo包下创建ErrorCode
package com.lum.blog.vo;
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户密码不存在喔!"),
TOKEN_ERROR(10003,"Token不合法"),
ACCOUNT_EXIST(10004,"账号已存在"),
NO_PERMISSION(70001,"无访问权限"),
SESSION_TIME_OUT(90001,"会话超时"),
NO_LOGIN(90002,"未登录"),;
private int code;
private String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
8.6 进行测试
使用postman进行测试,因为登录后,需要跳转页面,进行token认证,有接口未完成,换端会出问题,
token前端获取到之后,会存储storage中b5,本地存储
redis-ci中使用 key *
也可查询token
9.1接口说明
接口url:/users/currentUser
请求方式:Get
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
AuthorZation | string | 头部信息(Token) |
9.2 Controller
创建UserController进行用户信息返回
package com.lum.blog.controller;
import com.lum.blog.service.SysUserService;
import com.lum.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("users")
public class UserController {
@Autowired
private SysUserService sysUserService;
//users/currentUser
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token) { //请求头
return sysUserService.findUserByToken(token);
}
}
9.3 Service
在SysUserService接口添加findUserByToken(String token)方法
/*根据token查询用户信息*/
Result findUserByToken(String token);
在实现类中编写findUserByToken(String token),需要在vo包创建LoginUserVo
@Override
public Result findUserByToken(String token) {
/**
* 1.根据token合法性
* 是否为空,解析是否成功,redis是否存在
* 2.如果校验失败,返回错误
* 3.如果成功,返回对应结果 LoginUserVo
*
*/
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null) {
return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg());
}
//理论上应该写TokenService处理,此处简单处理
LoginUserVo loginUserVo = new LoginUserVo();
loginUserVo.setId(sysUser.getId());
loginUserVo.setNickname(sysUser.getNickname());
loginUserVo.setAccount(sysUser.getAccount());
loginUserVo.setAvatar(sysUser.getAvatar());
return Result.success(loginUserVo);
}
LoginUserVo
package com.lum.blog.vo;
import lombok.Data;
@Data
public class LoginUserVo {
private Long id;
private String account;
private String nickname;
private String avatar; //头像
}
编写LoginService接口添加checkToken检查Token是否为空,不为空继续判断redis中userJson是否为空,不为空返回user对象
SysUser checkToken(String token);
在LoginServiceImpl中实现方法checkToken
@Override
public SysUser checkToken(String token) {
if (StringUtils.isBlank(token)){
return null;
}
Map<String, Object> checkToken = JWTUtils.checkToken(token);
if (checkToken == null) {
return null;
}
String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
if(StringUtils.isBlank(userJson)){
return null;
}
SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
return sysUser;
}
在SysUserServiceImpl中添加查找用户
@Override
public SysUser findUser(String account, String password) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,password);//进行加密
queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAvatar,SysUser::getNickname);//查询需要的
queryWrapper.last("limit 1");//为了加快速度,只查一个
return sysUserMapper.selectOne(queryWrapper);
}
前端清除token,后端清除redis中数据
10.1接口说明
接口url:/logout
请求方式:Get
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
AuthorZation | string | 头部信息(Token) |
10.2 Controller
创建LogoutController
package com.lum.blog.controller;
import com.lum.blog.service.LoginService;
import com.lum.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/logout")
public class LogoutController {
@Autowired
private LoginService loginService;
@GetMapping
public Result logout(@RequestHeader("Authorization") String token){
return loginService.logout(token);
}
}
10.3Service
在LoginService接口中创建logout方法
/**
* 退出登录
* @param token
* @return
*/
Result logout(String token);
在实现类中完成方法
将redis中的token删除即可
@Override
public Result logout(String token) {
redisTemplate.delete("TOKEN_" + token);
return Result.success(null);
}
sso(single sign on) 单点登录,后期把登录注册提出去,单独服务,可以独立提供接口
11.1接口说明
接口url:/register
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
account | string | 账号 |
password | string | 密码 |
nickname | string | 昵称 |
返回的数据
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
11.2 Controller
创建RegisterController
package com.lum.blog.controller;
import com.lum.blog.service.LoginService;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("register")
public class RegisterController {
@Autowired
private LoginService loginService;
@PostMapping
public Result register(@RequestBody LoginParam loginParam) {
return loginService.register(loginParam);
}
}
11.3 Service
LoginParam增加属性nickname
在LoginService接口中增加方法
private String nickname;
/**
* 注册
* @param loginParam
* @return
*/
Result register(LoginParam loginParam)
在LoginServiceImpl中实现register方法
@Override
public Result register(LoginParam loginParam) {
/**
* 1.判断参数是否合法
* 2.判断账户是否存在
* 3.账户不存在注册用户
* 4.生成token
* 5.存入redis并返回
* 6.注意 加上事务,一旦中间出现任何问题,注册用户需要回滚
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
String nickname = loginParam.getNickname();
if (StringUtils.isBlank(account)
||StringUtils.isBlank(nickname)
||StringUtils.isBlank(password)
){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
SysUser sysUser = sysUserService.findUserByAccount(account);
if (sysUser != null) {
return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(), "账号已经被用了欧");
}
sysUser=new SysUser();
sysUser.setAccount(account); //账户名
sysUser.setNickname(nickname); //昵称
sysUser.setPassword(DigestUtils.md5Hex(password+salt)); //密码加盐md5
sysUser.setCreateDate(System.currentTimeMillis()); //创建时间
sysUser.setLastLogin(System.currentTimeMillis()); //最后登录时间
sysUser.setAvatar("/static/img/logo.b3a48c0.png"); //头像
sysUser.setAdmin(1); //管理员权限
sysUser.setDeleted(0); //假删除
sysUser.setSalt(""); //盐
sysUser.setStatus(""); //状态
sysUser.setEmail(""); //邮箱
this.sysUserService.save(sysUser);
//传token
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
因为需要查找账户和将账户写入数据库,需要在SysUserService新增方法
/**
* 根据账户名查询用户
* @param account
* @return
*/
SysUser findUserByAccount(String account);
/**
* 保存用户
* @param sysUser
*/
void save(SysUser sysUser);
实现findUserByAccount(String account)
@Override
public SysUser findUserByAccount(String account) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.last("limit 1");
return this.sysUserMapper.selectOne(queryWrapper);
}
实现save(SysUser sysUser)
public void save(SysUser sysUser) {
//保存用户id会自动生成
//默认生成分布式id,采用雪花算法
//mybatis-plus
this.sysUserMapper.insert(sysUser);
}
开启数据库事务
在SysUserServiceImpl开启 @Transactional
每次访问需要登录的资源的时候,都需要代码中进行判断,一旦登录逻辑有所改变,代码都得进行改变,不合适
那么可以统一进行登录判断么
可以,使用拦截器,进行登录拦截,如果遇到登录需求才能访问的接口,拦截器直接返回,并跳转登录页面.
1. 拦截器的实现有多种方式(SpringSecurity,继承HandlerInterceptor,还有shiro
都可以实现),这里我们选择的是继承HandlerInterceptor
2. 这种方式有两步,第一编写继承类,第二步在webMVC里面配置即可
12.1 拦截器实现
package com.lum.blog.handler;
import com.alibaba.fastjson.JSON;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.service.LoginService;
import com.lum.blog.vo.ErrorCode;
import com.lum.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在执行Controller方法(Handle)前进行执行
// pre代表什么什么之前的
/**
* 1.需要判断,请求的接口路径是否为HandleMethod(controller方法)
* 2.判断token是否为空,为空未登录
* 3.不为空,登录验证 loginService checkToken
* 4.如果认证成功放行
*/
if (!(handler instanceof HandlerMethod)) {
//说简单的就是Handler是controller里面的某一个方法
//handle 可能是 RequestResourceHandle(访问资源handle) springboot程序访问静态资源 默认去classpath下的static目录查询
return true;
}
得去拿Token,为什么这样呢,因为前端传东西过来的时候是,我们用@RequestHeader("Authorization") 传过来的
String token = request.getHeader("Authorization");
//日志问题,需要导入lombok下的@slf4
log.info("=============request start=================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}",token);
log.info("=============request end===================");
/*token为空,拦截*/
if(StringUtils.isBlank(token)){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
response.setContentType("application/json;charset=utf8");
response.getWriter().print(JSON.toJSONString(result)); //返回json信息(fastjson进行转化)
return false;
}
/*用户为空,拦截*/
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
//以下是错误返回的信息
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
//要告诉浏览器我们要返回的是这种类型
response.setContentType("application/json;charset=utf8");
//返回的东西是result类型,要转换为JSON类型才行
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//登录验证成功,放行
return true;
}
}
12.2 配置拦截路径
WebMvcConfig中配置拦截路径
//拦截器注入
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置拦截接口
// 除了登录注册的所有接口
// registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/register");
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test");
}
想在controller中直接获取用户信息怎么获取?
什么是ThreadLocal
**ThreadLocal是什么**
从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
13.1使用ThreadLocal
现在utils包下创建UserThreadLocal类
package com.lum.blog.utils;
import com.lum.blog.dao.pojo.SysUser;
public class UserThreadLocal {
//这句话的意思是声明为私有
private UserThreadLocal (){
}
//实例化一个ThreadLocal的类,也就是启用
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove() {
LOCAL.remove();
}
}
既然是保存用户信息
对LoginIntercept修改,既然在这里验证,就在这里进行添加
package com.lum.blog.handler;
import com.alibaba.fastjson.JSON;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.service.LoginService;
import com.lum.blog.utils.UserThreadLocal;
import com.lum.blog.vo.ErrorCode;
import com.lum.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在执行Controller方法(Handle)前进行执行
// pre代表什么什么之前的
/**
* 1.需要判断,请求的接口路径是否为HandleMethod(controller方法)
* 2.判断token是否为空,为空未登录
* 3.不为空,登录验证 loginService checkToken
* 4.如果认证成功放行
*/
if (!(handler instanceof HandlerMethod)) {
//说简单的就是Handler是controller里面的某一个方法
//handle 可能是 RequestResourceHandle(访问资源handle) springboot程序访问静态资源 默认去classpath下的static目录查询
return true;
}
得去拿Token,为什么这样呢,因为前端传东西过来的时候是,我们用@RequestHeader("Authorization") 传过来的
String token = request.getHeader("Authorization");
log.info("=============request start=================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}",token);
log.info("=============request end===================");
/*token为空,拦截*/
if(StringUtils.isBlank(token)){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
response.setContentType("application/json;charset=utf8");
response.getWriter().print(JSON.toJSONString(result)); //返回json信息(fastjson进行转化)
return false;
}
/*用户为空,拦截*/
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
//以下是错误返回的信息
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
//要告诉浏览器我们要返回的是这种类型
response.setContentType("application/json;charset=utf8");
//返回的东西是result类型,要转换为JSON类型才行
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//登录验证成功,放行
//在controller直接获取用户信息
UserThreadLocal.put(sysUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//如果不删除ThreadLocal中的信息,会有内训泄露的风险
UserThreadLocal.remove();
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//如果不删除ThreadLocal中的信息,会有内训泄露的风险
UserThreadLocal.remove();
}
13.2 进行测试
在TsetCroller中进行测试
package com.lum.blog.controller;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.utils.UserThreadLocal;
import com.lum.blog.vo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("test")
public class TestController {
/**
* 用来测试拦截器
*/
@RequestMapping
public Result test(){
SysUser sysUser = UserThreadLocal.get();
System.out.println(sysUser);
return Result.success(null);
}
}
13.3ThreadLocal(本地的线程)到底有什么用
这要知道的是一个线程可以存在多个ThreadLocal
每一个Thread维护一个ThreadLocalMap, key为使用**弱引用**的ThreadLocal实例,
value为线程变量的副本。
**强引用**,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存
空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这
种对象。
**如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使
JVM在合适的时间就会回收该对象。**
**弱引用**,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
在java中,用java.lang.ref.WeakReference类来表示。
上面的那个 key为使用**弱引用**的ThreadLocal实例,当我们的线程中的那个
ThreadLocal被垃圾回收机制干掉之后,是不是这个弱引用的Key不存在了,但是这个是
Map集合呀,Value会永远的存在,所有要手动的删除
14.1 接口说明
接口url:/articles/view/(id)
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | long | 文章id(路径参数) |
返回的数据
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
14.2 涉及的表及对应的pojo
CREATE TABLE `blog`.`ms_article_body` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
#这个是文章内容
`content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
#文章内容页面
`content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`article_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci
ROW_FORMAT = Dynamic;
ArticleBody
package com.lum.blog.dao.pojo;
import lombok.Data;
@Data
public class ArticleBody {
private Long id;
private String content;
private String contentHtml;
private Long articleId;
}
类别表
//类别表
CREATE TABLE `blog`.`ms_category` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
#分类的图标
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
#分类图标的名字
`category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
Category
package com.lum.blog.dao.pojo;
import lombok.Data;
@Data
public class Category {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
14.3Controller
在ArticleConller中添加方法查找文章
/**
* 查看文章详情
* @param articleId
* @return
*/
@PostMapping("view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId){
return articleService.findArticleById(articleId);
}
14.4 Service
返回文章的哪些内容需要用到articleVo中添加ArticleBodyVo和CategoryVo
package com.lum.blog.vo;
import lombok.Data;
import java.util.List;
@Data
public class ArticleVo {
private Long id;
private String title;
private String summary; //简介
private int commentCounts;
private int ViewCounts;
private int weight; //置顶
private String createDate; //创建时间
private String author;
private ArticleBodyVo body;
private List<TagVo> tags;
private CategoryVo category;
}
package com.lum.blog.vo;
import lombok.Data;
@Data
public class CategoryVo {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
在articleService中添加查找文章方法接口
/**
* 查询文章详情
* @param articleId
* @return
*/
Result findArticleById(Long articleId);
以下全在ArticleServiceImpl中编写
在articleServiceImpl中实现方法
/*文章详情*/
@Override
public Result findArticleById(Long articleId) {
/**
* 1.根据id查询文章信息
* 2.根据bodyId和categoryId 去做关联查询
*/
Article article = this.articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article, true, true,true,true);
return Result.success(articleVo);
}
/*文章体显示*/
private ArticleBodyVo findArticleBodyById(Long bodyId) {
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
想要显示文章部分内容就要根据文章找到详情
想要显示内容需要ArticleVo
重载cpoyList可以根据参数显示不同文章内容
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor,false,false));
}
return articleVoList;
}
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory));
}
return articleVoList;
}
文章articleVo显示 copyList重载后的copy方法
private ArticleVo copy(Article article,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory) {
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
if (isTag) {
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsByArticleId(articleId));
}
if (isAuthor) {
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
if(isBody){
Long bodyId = article.getBodyId();
articleVo.setBody(findArticleBodyById(bodyId));
}
if(isCategory){
Long categoryId = article.getCategoryId();
articleVo.setCategory(categoryService.findCategoryById(categoryId));
}
return articleVo;
}
需要创建CategoryService
package com.lum.blog.service;
import com.lum.blog.vo.CategoryVo;
import com.lum.blog.vo.Result;
public interface CategoryService {
CategoryVo findCategoryById(Long categoryId);
Result findAll();
/**
* 文章分类
* @return
*/
Result findAllDetail();
Result categoryDetailById(Long id);
}
实现CategoryServiceImpl
package com.lum.blog.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lum.blog.dao.mapper.CategoryMapper;
import com.lum.blog.dao.pojo.Category;
import com.lum.blog.service.CategoryService;
import com.lum.blog.vo.CategoryVo;
import com.lum.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public CategoryVo findCategoryById(Long categoryId) {
Category category = categoryMapper.selectById(categoryId);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category, categoryVo);
return categoryVo;
}
不要忘记注入关系
@Autowired
private ArticleMapper articleMapper;
@Autowired
private TagService tagService;
@Autowired
private SysUserService sysUserService;
@Autowired
private CategoryService categoryService;
@Autowired
private ArticleBodyMapper articleBodyMapper;
想要文章体需要添加articleBodyMapper
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.ArticleBody;
public interface ArticleBodyMapper extends BaseMapper<ArticleBody> {
}
14.5 进行测试
线程池的使用
问题介绍
@Override
public ArticleVo findArticleById(Long id) {
Article article = articleMapper.selectById(id);
//查完文章了,新增阅读数,有没有问题呢?
//答案是是有的,本应该直接返回数据,这时候做了一个更新操作,更新时间时加写锁,阻塞其他的读操作,新能就会比较低,
//而且更新增加了此次接口的耗时,一旦更新出问题,不能影响我们其他的如:看文章呀什么的
//那要怎么样去优化呢?,---->所有想到了线程池
//可以把更新操作扔到线程池里面,就不会影响了,和主线程就不相关了
return copy(article,true,true,true,true);
}
线程池的配置
com.lum.blog.config下新建ThreadPoolConfig开启线程池并进行相关配置
package com.lum.blog.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* @author lum
* @date 2021/9/3
*/
@Configuration
@EnableAsync
//开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置线程核心数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
// 配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间
executor.setKeepAliveSeconds(60);
// 设置线程名称
executor.setThreadNamePrefix("Lum博客");
//等待所有任务及结束后关闭线程
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
15.1 Controller
属于文章业务,无controller
15.2 Service
在com.lum.blog.service.Impl下新建ThreadService完成线程池的使用
package com.lum.blog.service.Impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.lum.blog.dao.mapper.ArticleMapper;
import com.lum.blog.dao.pojo.Article;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @author lum
* @date 2021/9/3
*/
@Component
public class ThreadService {
//期望此操作在线程池中 执行 不会影响主线程
@Async("taskExecutor")
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
int viewCounts = article.getViewCounts();
Article articleUpdate = new Article();
articleUpdate.setViewCounts(viewCounts+1);
LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Article::getId,article.getId());
// 设置一个为了在多线程的条件下 线程安全
updateWrapper.eq(Article::getViewCounts,viewCounts);
// update article set view_count = 100 where view_count =99 and id = 1
articleMapper.update(articleUpdate, updateWrapper);
try {
Thread.sleep(2000);
System.out.println("更新完成!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这是会出现一个bug
更新阅次数时会吧评论数更新为0
这是因为在Article的pojo与数据库的关系映中,int基本类型的默认值为0,会影响sql语句
所以在pojo中不要使用基本类型
package com.lum.blog.dao.pojo;
import lombok.Data;
/*
博客管理
*/
@Data
public class Article {
public static final Integer Article_TOP = 1;
public static final Integer Article_Common = 0;
private Long id;
private String title;
private String summary;
private Integer commentCounts;
private Integer viewCounts;
/**
* 作者id
*/
private Long authorId;
/**
* 内容id
*/
private Long bodyId;
/**
*类别id
*/
private Long categoryId;
/**
* 置顶
*/
private Integer weight;
/**
* 创建时间
*/
private Long createDate;
}
16.1 接口说明
接口url:/comments/article/(id)
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | long | 文章id(路径参数) |
返回的数据
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id":12,
"author":{
"nickname":"赵云",
"avatar":"",
"id":1
},
"content":"111"
"childrens":[],
"createDate":"2021-9-1 08:35",
"level":2,
"toUser":{
"id":12,
"nickname":"赵云",
"id":1
}
}
],
"createDate":"2021-9-1 08:35",
"level":1,
"toUser":null
}
]
}
新建pojo包下Comment映射关系
package com.lum.blog.dao.pojo;
import lombok.Data;
/**
* @author lum
* @date 2021/9/3
*/
@Data
public class Comment {
private Long id;
private String content;
private Long createDate;
private Long articleId;
private Long authorId;
private Long parentId;
private Long toUid;
private Integer level;
}
创建两个Vo用来显示
CommentVo
package com.lum.blog.vo;
import lombok.Data;
import java.util.List;
/**
* @author lum
* @date 2021/9/3
*/
@Data
public class CommentVo {
private Long id;
private UserVo author;
private String content;
private List<CommentVo> childrens;
private String createDate;
private Integer level;
private UserVo toUser;
}
UserVo
package com.lum.blog.vo;
import lombok.Data;
/**
* @author lum
* @date 2021/9/3
*/
@Data
public class UserVo {
private String nickname;
private String avatar;
private Long id;
}
16.2 Controller
创建CommentController
package com.lum.blog.controller;
import com.lum.blog.service.CommentsService;
import com.lum.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author lum
*/
@RestController
@RequestMapping("comments")
public class CommentController {
@Autowired
private CommentsService commentsService;
@GetMapping("article/{id}")
public Result findArticleById(@PathVariable("id") Long articleId){
return commentsService.commentsByArticleId(articleId);
}
}
16.3 Service
创建接口CommentService
package com.lum.blog.service;
import com.lum.blog.vo.Result;
/**
* @author lum
* @date 2021/9/3
*/
public interface CommentsService {
/**
* 根据文章id查找评论
* @param articleId
* @return
*/
Result commentsByArticleId(Long articleId);
}
CommentServiceImpl实现方法
package com.lum.blog.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lum.blog.dao.mapper.CommentMapper;
import com.lum.blog.dao.pojo.Comment;
import com.lum.blog.service.CommentsService;
import com.lum.blog.service.SysUserService;
import com.lum.blog.vo.CommentVo;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.UserVo;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author lum
* @date 2021/9/3
*/
@Service
public class CommentsServiceImpl implements CommentsService {
@Autowired
private CommentMapper commentMapper;
@Autowired
private SysUserService sysUserService;
@Override
public Result commentsByArticleId(Long articleId) {
/*
1.根据文章id查询评论列表,从 comment 中查询
2.根据作者id查询作者信息
3.如果 level=1,查询有没有子评论,\
4.如果有 根据评论id进行查询
*/
LambdaQueryWrapper<Comment> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getLevel,1);
List<Comment> comments = commentMapper.selectList(queryWrapper);
List<CommentVo> commentVoList= copyList(comments);
return Result.success(commentVoList);
}
private List<CommentVo> copyList(List<Comment> comments) {
List<CommentVo> commentVoList = new ArrayList<>();
for (Comment comment : comments) {
commentVoList.add(copy(comment));
}
return commentVoList;
}
private CommentVo copy(Comment comment) {
CommentVo commentVo = new CommentVo();
//将类型相同的copy到commentVo中
BeanUtils.copyProperties(comment, commentVo);
//时间格式化
commentVo.setCreateDate(new DateTime(comment.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
//作者信息
Long authorId = comment.getAuthorId();
UserVo userVo = this.sysUserService.findUserVoById(authorId);
commentVo.setAuthor(userVo);
//子评论
Integer level = comment.getLevel();
if (level == 1){
Long id = comment.getId();
List<CommentVo> commentVoList = findCommentByParentId(id);
commentVo.setChildrens(commentVoList);
}
//toUser向谁评论
if (level > 1) {
Long toUid = comment.getToUid();
UserVo toUserVo = this.sysUserService.findUserVoById(toUid);
commentVo.setToUser(toUserVo);
}
return commentVo;
}
//子评论查询
private List<CommentVo> findCommentByParentId(Long id) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getParentId,id);
queryWrapper.eq(Comment::getLevel,2);
return copyList(commentMapper.selectList(queryWrapper));
}
}
需要向SysUserServiceImpl中增加findUserVoById(Long id)方法
@Override
public UserVo findUserVoById(Long id) {
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null) {
sysUser = new SysUser();
sysUser.setId(1L);
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setNickname("空");
}
UserVo userVo = new UserVo();
BeanUtils.copyProperties(sysUser,userVo);
//设置userVo的id
userVo.setId(String.valueOf(sysUser.getId()));
return userVo;
}
进行测试
17.1 接口说明
接口url:/comments/create/change
请求方式: POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
articleid | long | 文章id |
content | lstring | 评论内容 |
parent | long | 父评论id |
toUserid | long | 被评论用户id |
创建CommentParam
package com.lum.blog.vo.params;
/**
* @author lum
* @date 2021/9/3
*/
@Data
public class CommentParam {
private Long articleId;
private String content;
private Long parentId;
private Long toUserId;
}
17.2 需要加入到登录拦截器中
登录后才可以评论
WebMvcConfig
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置拦截接口
// 除了登录注册的所有接口
// registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/register");
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/comments/create/change")
.addPathPatterns("/test");
}
因为分布式id 的Long过长前端解析精度损失,会将值改变,需要进行Json序列化
CommentVo
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
17.3 Controller
在commentController中添加接口
@PostMapping("create/change")
public Result comment(@RequestBody CommentParam commentParam){
return commentsService.comment(commentParam);
}
17.4 Service
CommentService
/**
* 评论
* @param commentParam
* @return
*/
Result comment(CommentParam commentParam);
实现方法CommentServiceImpl
//发表评论
@Override
public Result comment(CommentParam commentParam) {
SysUser sysUser = UserThreadLocal.get();
Comment comment = new Comment();
comment.setArticleId(commentParam.getArticleId());
comment.setAuthorId(sysUser.getId());
comment.setContent(commentParam.getContent());
comment.setCreateDate(System.currentTimeMillis());
Long parent = commentParam.getParentId();
//如果父id为空,则父评论,否则为子评论
if (parent == null || parent == 0) {
comment.setLevel(1);
} else {
comment.setLevel(2);
}
comment.setParentId(parent == null ? 0 : parent);
Long toUserId = commentParam.getToUserId();
comment.setToUid(toUserId == null ? 0 : toUserId);
this.commentMapper.insert(comment);
//插入到数据库
return Result.success(null);
}
需要三个接口:
18.1 所有文章分类
接口说明
接口url:/categorys
请求方式: GET
请求参数:
|参数名称| 参数类型 |说明|
Controller
CategoryController
//categroys
@GetMapping
public Result categories(){
return categoryService.findAll();
}
Service
CategroyService
//查找分类
Result findAll();
CategoryServiceImpl
@Override
public Result findAll() {
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//queryWrapper.select(Category::getId,Category::getCategoryName);
List<Category> categories = categoryMapper.selectList(queryWrapper);
//页面交互的对象
return Result.success(copyList(categories));
}
public CategoryVo copy(Category category){
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
//categoryVo.setId(String.valueOf(category.getId()));
return categoryVo;
}
public List<CategoryVo> copyList(List<Category> categoryList){
List<CategoryVo> categoryVoList = new ArrayList<>();
for (Category category : categoryList) {
categoryVoList.add(copy(category));
}
return categoryVoList;
}
18.2 所有文章标签
接口说明
接口url:/tags
请求方式: GET
请求参数:
|参数名称| 参数类型 |说明|
Controller
TagsController
/**
* 所有文章标签
* @return
*/
@GetMapping
public Result findAll() {
return tagService.findAll();
}
TagService
/**
* 查询所有文章标签
* @return
*/
Result findAll();
TagsServiceImpl
@Override
public Result findAll() {
List<Tag> tags = this.tagMapper.selectList(new LambdaQueryWrapper<>());
return Result.success(copyList(tags));
}
接口说明
接口url:/articles/publish
请求方式: POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
title | string | 文章标题 |
id | long | 文章id(编辑有值) |
body | object | 文章内容 |
category | json | 文章类别 |
summary | string | 文章概述 |
tags | json | 文章标签 |
创建ArticleParam
@Data
public class ArticleParam {
private Long id;
private ArticleBodyParam body;
private CategoryVo category;
private String summary;
private List<TagVo> tags;
private String title;
}
创建ArticBodyParam
@Data
public class ArticleBodyParam {
private String content;
private String contentHtml;
}
创建pojo下article和tag关联表对象
@Data
public class ArticleTag {
private Long id;
private Long articleId;
private Long tagId;
新建ArticleTagMapper接口
package com.lum.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lum.blog.dao.pojo.ArticleTag;
/**
* @author lum
* @date 2021/9/4
*/
public interface ArticleTagMapper extends BaseMapper<ArticleTag> {
}
将ArticleVo中id进行序列化,防止前段解析损失
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
ArticleController
@PostMapping("publish")
public Result publish(@RequestBody ArticleParam articleParam){
return articleService.publish(articleParam);
}
ArticleService
/**
* 文章发布
* @param articleParam
* @return
*/
Result publish(ArticleParam articleParam);
ArticleServiceImpl
@Autowired
private ArticleTagMapper articleTagMapper;
/**
* 1.发布文章 目的构建Article对象
* 2. 作者id 当前登陆用户
* 3. 标签 将标签加入关联表中
* 4. body内容存储 article bodyId
* @param articleParam
* @return
* 此接口要加入登陆拦截中
*/
@Override
public Result publish(ArticleParam articleParam) {
SysUser sysUser = UserThreadLocal.get();
Article article = new Article();
article.setAuthorId(sysUser.getId());
article.setWeight(Article.Article_Common);
article.setViewCounts(0);
article.setTitle(articleParam.getTitle());
article.setSummary(articleParam.getSummary());
article.setCommentCounts(0);
article.setCreateDate(System.currentTimeMillis());
article.setCategoryId((articleParam.getCategory().getId()));
//插入之后 会生成一个文章id
this.articleMapper.insert(article);
//tag
List<TagVo> tags = articleParam.getTags();
if (tags != null){
for (TagVo tag : tags) {
Long articleId = article.getId();
ArticleTag articleTag = new ArticleTag();
articleTag.setTagId((tag.getId()));
articleTag.setArticleId(articleId);
articleTagMapper.insert(articleTag);
}
}
//body
ArticleBody articleBody = new ArticleBody();
articleBody.setArticleId(article.getId());
articleBody.setContent(articleParam.getBody().getContent());
articleBody.setContentHtml(articleParam.getBody().getContentHtml());
articleBodyMapper.insert(articleBody);
article.setBodyId(articleBody.getId());
articleMapper.updateById(article);
//将id转换成string放入map
Map<String,String> map = new HashMap<>();
map.put("id",article.getId().toString());
return Result.success(map);
}
创建commom.aop包
创建LogAnnotation自定义注解
package com.lum.blog.common.aop;
/**
* @author lum
* @date 2021/9/4
*/
import java.lang.annotation.*;
//Type 代表可以放在类上面 Method 代表可以放在方法上
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
String module() default "";
String operator() default "";
}
同时创建LogAspect用来定义切入点及日志
package com.lum.blog.common.aop;
import com.alibaba.fastjson.JSON;
import com.lum.blog.utils.HttpContextUtils;
import com.lum.blog.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @author lum
* @date 2021/9/4
*/
@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.lum.blog.common.aop.LogAnnotation)")
public void pt(){}
//环绕通知
@Around("pt()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = joinPoint.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
recordLog(joinPoint, time);
return result;
}
private void recordLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
log.info("=====================log start================================");
log.info("module:{}",logAnnotation.module());
log.info("operation:{}",logAnnotation.operator());
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
log.info("request method:{}",className + "." + methodName + "()");
// //请求的参数
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args[0]);
log.info("params:{}",params);
//获取request 设置IP地址
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
log.info("ip:{}", IpUtils.getIpAddr(request));
log.info("excute time : {} ms",time);
log.info("=====================log end================================");
}
}
接下来就可以到Controller任意方法中添加注解来进行记录
@PostMapping
@LogAnnotation(module = "文章",operator = "获取文章列表")
public Result listArticle(@RequestBody PageParams pageParams){
//对于接受的参数问题,这里是RequestBody接收
// int i=10/0; 测试异常
return articleService.listArticle(pageParams);
}
接口名称
接口url:/upload
请求方式: POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
image | file | 上传的文件名称 |
Controller
新建UploadController
package com.lum.blog.controller;
import com.lum.blog.utils.QiniuUtils;
import com.lum.blog.vo.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
/**
* @author lum
* @date 2021/9/4
*/
@RestController
@RequestMapping("upload")
public class UploadController {
@Autowired
private QiniuUtils qiniuUtils;
@PostMapping
public Result upload(@RequestParam("image") MultipartFile file) {
//原始文件名称 比如 aa.png
String originalFilename = file.getOriginalFilename();
//唯一的文件名称
String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename, ".");
//上传文件 上传到哪呢? 七牛云 云服务器 按量付费 速度快 把图片发放到离用户最近的服务器上
// 降低 我们自身应用服务器的带宽消耗
boolean upload = qiniuUtils.upload(file, fileName);
if (upload){
return Result.success(QiniuUtils.url + fileName);
}
return Result.fail(20001,"上传失败");
}
}
在appliation.properties中配置上传文件大小
#上传文件的最大值
spring.servlet.multipart.max-request-size=20MB
#单个文件的最大值
spring.servlet.multipart.max-file-size=2MB
使用七牛云存储
maven导包
<dependency>
<groupId>com.qiniugroupId>
<artifactId>qiniu-java-sdkartifactId>
<version>[7.7.0, 7.7.99]version>
dependency>
新建七牛Utils
package com.lum.blog.utils;
/**
* @author lum
* @date 2021/9/4
*/
import com.alibaba.fastjson.JSON;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class QiniuUtils {
//配置自己的域名
public static final String url = "https://qywco434a.hd-bkt.clouddn.com/";
//配置自己的密钥
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.accessSecretKey}")
private String accessSecretKey;
public boolean upload(MultipartFile file,String fileName){
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.huabei());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
// 配置空间名称
String bucket = "lumblog";
//默认不指定key的情况下,以文件内容的hash值作为文件名
try {
byte[] uploadBytes = file.getBytes();
Auth auth = Auth.create(accessKey, accessSecretKey);
String upToken = auth.uploadToken(bucket);
Response response = uploadManager.put(uploadBytes, fileName, upToken);
//解析上传成功的结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
}
接口名称
接口url:/categorys/detail
请求方式: GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|
CategoryController
@GetMapping("detail")
public Result categoriesDetail(){
return categoryService.findAllDetail();
}
CategoryService
/**
* 文章分类
* @return
*/
Result findAllDetail();
CategoryServiceImpl
@Override
public Result findAllDetail() {
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
List<Category> categories = categoryMapper.selectList(queryWrapper);
//页面交互的对象
return Result.success(copyList(categories));
}
与findAll区别在于
findAll只查询id和分类名
findAllDetail查询全部信息
接口说明
接口url:/categorys/detail
请求方式: GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|
23.2
TagVo新增avatar属性
package com.lum.blog.vo;
import lombok.Data;
@Data
public class TagVo {
private Long id;
private String tagName;
private String avatar;
}
CategroyVo
@Data
public class CategoryVo {
private Long id;
private String avatar;
private String categoryName;
private String description;
TagsController
/**
* 导航栏所有标签
* @return
*/
@GetMapping("detail")
public Result findAllDetail() {
return tagService.findAllDetail();
}
TagService
/**
* 问导航栏标签
* @return
*/
Result findAllDetail();
TagServiceImpl
更改之前findAll方法,增加条件只查询id和tagName
需要设么查询什么
@Override
public Result findAll() {
LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(Tag::getId,Tag::getTagName);
List<Tag> tags = this.tagMapper.selectList(queryWrapper);
return Result.success(copyList(tags));
}
@Override
public Result findAllDetail() {
LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
List<Tag> tags = this.tagMapper.selectList(queryWrapper);
return Result.success(copyList(tags));
}
接口名称
接口url:/categorys/detail/{id}
请求方式: GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | 分类id | 路径 |
CategoryController
@GetMapping("detail/{id}")
public Result categoryDetailById(@PathVariable("id") Long id){
return categoryService.categoryDetailById(id);
}
CategoryService
Result categoryDetailById(Long id);
CategoryServiceImpl
@Override
public Result categoryDetailById(Long id) {
Category category = categoryMapper.selectById(id);
return Result.success(copy(category));
}
因为CategoryVo的tagId属性改为String
CategoryVo copy代码修改
public CategoryVo copy(Category category){
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
categoryVo.setId(String.valueOf(category.getId()));
return categoryVo;
}
需要增加ArticleServiceImpl中的ListArticle条件实现根据CategoryId分类
PageParam
package com.lum.blog.vo.params;
import lombok.Data;
//承担着返回页数和数量的类
@Data
public class PageParams {
private int page = 1;
private int pageSize = 10;
private Long categoryId;
private Long tagId;
}
ArticleServiceImpl
//and category_id = #{categoryId}
if(pageParams.getCategoryId() != null){
queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
}
接口名称
接口url:/tags/detail/{id}
请求方式: GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | 标签id | 路径 |
TagsController
/**
* 标签对应文章
* @return
*/
@GetMapping("detail/{id}")
public Result findDetailById(@PathVariable("id") Long id) {
return tagService.findDetailById(id);
}
tagService
/**
* 标签对应文章
* @param id
* @return
*/
Result findDetailById(Long id);
tagServiceimpl
因为tagVo的tagId属性改为String
tagVo copy方法修改
public TagVo copy(Tag tag){
TagVo tagVo = new TagVo();
BeanUtils.copyProperties(tag,tagVo);
tagVo.setId(String.valueOf(tag.getId()));
return tagVo;
}
@Override
public Result findDetailById(Long id) {
Tag tag = tagMapper.selectById(id);
return Result.success(copy(tag));
}
标签对应文章
在ArticleServiceImpl增加查询文章列表条件(listArticle)
List<Long> articleIdList = new ArrayList<>();
if (pageParams.getTagId() !=null) {
//加入标签 条件查询
//article表中 并没有tag字段一篇文章有多个标签
//article_tag 中一个articleId对应多个tagId
LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper);
//查出所有的文章标签放入数组
for (ArticleTag articleTag : articleTags) {
articleIdList.add(articleTag.getArticleId());
//将文章标签id和标签id相等的放入数组
}
if (articleIdList.size() > 0){
//如果有文章id则查询文章id是否在符合条件数组中
queryWrapper.in(Article::getId,articleIdList);
}
}
在PageParam中添加属性
private String year;
private String month;
public String getMonth(){
if (this.month != null && this.month.length() == 1){
return "0"+this.month;
}
return this.month;
}
因为需要按时间查询文章列表,所以需要修改articleServiceImpl中的 listArticle 查询条件
@Override
public Result listArticle(PageParams pageParams) {
Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
IPage<Article> articleIPage = articleMapper.listAticle(
page,
pageParams.getCategoryId(),
pageParams.getTagId(),
pageParams.getYear(),
pageParams.getMonth());
List<Article> recordes = articleIPage.getRecords();
return Result.success(copyList(recordes,true,true));
}
将之前的注释掉
在articlemapper.java中增加mybatis-plus分页查询文章属性
//mybatis-plus分页
IPage<Article> listAticle(Page<Article> page,
Long categoryId,
Long tagId,
String year,
String month);
修改articleMapper.xml中对应的sql语句
增加articleMap映射语句,属性和数据库对应
因为开启了驼峰命名映射ResultMap,所以可删除映射表,两种方法都可以
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lum.blog.dao.mapper.ArticleMapper">
<resultMap id="articleMap" type="com.lum.blog.dao.pojo.Article">
<id column="id" property="id" />
<result column="author_id" property="authorId"/>
<result column="comment_counts" property="commentCounts"/>
<result column="create_date" property="createDate"/>
<result column="summary" property="summary"/>
<result column="title" property="title"/>
<result column="view_counts" property="viewCounts"/>
<result column="weight" property="weight"/>
<result column="body_id" property="bodyId"/>
<result column="category_id" property="categoryId"/>
resultMap>
<select id="listArchives" resultType="com.lum.blog.dao.dos.Archives">
select FROM_UNIXTIME(create_date/1000,'%Y') as year,
FROM_UNIXTIME(create_date/1000,'%m') as month,
count(*) as count
from ms_article
group by year,month
select>
<select id="listAticle" resultMap="articleMap" resultType="com.lum.blog.dao.pojo.Article">
select * from ms_article
<where>
1=1
<if test="categoryId != null">
and category_id=#{categoryId}
if>
<if test="tagId != null">
and id in (select article_id from ms_article_tag where tag_id=#{tagId})
if>
<if test="year != null and year.length>0 and month != null and month.length>0">
and (From_UNIXTIME(create_date/1000,'%Y') = #{year} and From_UNIXTIME(create_date/1000,'%m') = #{month})
if>
where>
order by weight desc,create_date desc
select>
mapper>
防止解析误差修改tagVo中id和categoryVo中id为String类型
修改相关copy方法代码.getid用String.valueOf()转换