这里提前讲解一下,我们创建一个项目
1. 如果是平时开发的,首先都要导入如下依赖
<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>
2. 如果创建的项目报如下错误的话,请你降低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的导
org.springframework.boot
spring-boot-starter-parent
2.5.0
UTF-8
UTF-8
1.8
b--然后再导入其他的依赖
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-mail
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.76
mysql
mysql-connector-java
org.springframework.boot
spring-boot-configuration-processor
true
org.apache.commons
commons-lang3
commons-collections
commons-collections
3.2.2
commons-codec
commons-codec
com.baomidou
mybatis-plus-boot-starter
3.4.3
org.projectlombok
lombok
joda-time
joda-time
2.10.10
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-maven-plugin
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-mail
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.76
mysql
mysql-connector-java
org.springframework.boot
spring-boot-configuration-processor
true
org.apache.commons
commons-lang3
commons-collections
commons-collections
3.2.2
commons-codec
commons-codec
com.baomidou
mybatis-plus-boot-starter
3.4.3
org.projectlombok
lombok
joda-time
joda-time
2.10.10
org.springframework.boot
spring-boot-starter-security
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=111
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#然后就是Mybatis-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,args);
}
}
package cn.mldn.Blog.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("cn.mldn.Blog.mapper")
public class MybatiesPlusConfig {
}
package cn.mldn.Blog.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("cn.mldn.Blog.Dao.mapper")
public class MybatiesPlusConfig {
//这里定义了,Mybatis分页的一个插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
package cn.mldn.Blog.config;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class WebMvcConfig implements WebMvcConfigurer {
//做跨域配置,为什么要做这个跨域的配置呢,因为比如:我前端的端口号是8080,而我后端接口是8888
@Override
public void addCorsMappings(CorsRegistry registry) {
//addMapping就是所有的文件,allowedOrigins指的是可以那个地址可以访问
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
}
1. 首先就是接口说明
接口url:/articles
请求方式:post请求
请求参数:
参数名称 参数类型 说明
page int 当前页数
pageSize int 每页显示的数量
2. 返回的数据,JSON类型{这里的数据做一个说明就是它返回,然后会在上面显示的数据}
返回数据:
~~~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": "2609-06-26 15:58",
"author": "12",
"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
}
]
}
~~~
3.既然我们有了前端和页面要返回的数据,那我们的用户什么的,肯定都要和类有关系撒
返回数据的文章数据表
~~~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;
4. 有了上面的内容之后,接下来进行开发了
5. DAO的开发(有三个)
package cn.mldn.Blog.Dao;
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;
}
package cn.mldn.Blog.Dao;
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;
}
package cn.mldn.Blog.Dao;
import lombok.Data;
@Data
public class Tag {
private Long id;
private String avatar;
private String tagName;
}
6. 然后就是对应的Mapper的创建了,以前我们用的是Mybatis,这次用的是MybatisPlus有着些许的差别。
package cn.mldn.Blog.Dao.mapper;
import cn.mldn.Blog.Pojo.Article;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
//首先这个继承的BaseMapper是MybatisPlus提供的,可以方便的查看传入的表名的表
//为什么我们导入进去类就行了呢?因为它自动给我们做了很多东西
}
package cn.mldn.Blog.Dao.mapper;
import cn.mldn.Blog.Pojo.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysUserMapper extends BaseMapper<SysUser> {
}
package cn.mldn.Blog.Dao.mapper;
import cn.mldn.Blog.Pojo.Tag;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface TagMapper extends BaseMapper<Tag> {
}
7. mapper创建好了,就该接下来的Controller了
创建ArticleController,这个代表的是文章类的控制器
import cn.mldn.Blog.vo.PageParams;
import cn.mldn.Blog.vo.Result;
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;
//我们都是用JSON数据进行交互
@RestController
@RequestMapping("articles")
public class ArticleController {
//这里为什么用POST请求,因为在前面的接口说明的用POST请求
//对于另外的参数,我们建立一个vo下面的PageParams类专门表示参数
/**
* 首页文章列表
* @param pageParams
* return 返回的是承担返回数据的Result的类的一个类
*/
@PostMapping
public Result listArticle(@RequestBody PageParams pageParams) {
//对于接受的参数问题,这里用的是RequestBody来接受
return Result.succes(pageParams);
}
}
1)这里就有补充了,我们传入的参数,是PageParms类代表,在
cn.mldn.Blog下面创建vo的目录,然后创建PageParms的类
import lombok.Data;
//承担着返回页数和数量的类
@Data
public class PageParams {
private int page = 1;
private int pageSize = 10;
}
2)返回的数据Result的类
package cn.mldn.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);
}
}
**
·有很多人可能跟我一样没有学习过MybatisPlus,其实这样理解就对了
1)首先我们编写的DAO层和数据库里面的表名字要对应起来。
2)然后就是编写的XXX-Mapper层层要继承BaseMapper
3)然后就是我们一般不再编写他的xxxMapper.xml而是要配置的是 xxxService+xxxServiceImpl,如果要编写xxxMapper.xml和Mybatis一样的配置
**
8. 从上面中,我们就可以看出,我们已经开发差不多了撒,但是想返回数据了,但是
listArticle这个方法却没有Service来读取数据,所以来开发Service和数据读取的方法
1)首先来编写这个Service层嘛,在src/main./java下面建立service文件夹并且在下面
ArticleService文件和在到Impl文件夹
package cn.mldn.Blog.Service;
import cn.mldn.Blog.vo.PageParams;
import cn.mldn.Blog.vo.Result;
import org.springframework.stereotype.Service;
@Service
public interface ArticleService {
Result listArticle(PageParams pageParams);
}
2)再来编写他的Impl文件ArticleServiceImpl文件
package cn.mldn.Blog.Service.Impl;
import cn.mldn.Blog.Dao.mapper.ArticleMapper;
import cn.mldn.Blog.Pojo.Article;
import cn.mldn.Blog.Service.ArticleService;
import cn.mldn.Blog.vo.ArticleVo;
import cn.mldn.Blog.vo.PageParams;
import cn.mldn.Blog.vo.Result;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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;
/**
* 分页查询方法
* @param pageParams
* @return
*/
@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();
//因为页面展示出来的数据不一定和数据库一样,所有我们要做一个转换,反正我不懂
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;
}
}
a.这里补充一下这个articleVo,这个类,因为平时我们开发出来的东西,到时候要
到数据库里面找数据嘛,然后找出来不一定一样,要把它一样的拷贝,不一样的返回null
总结:有了以上的配置之后,页面的内容就可以展示了(如果你也是按着正确的方式来
编写的上面部分内容,可以在test下面测试,康康能不能把数据读取出来,如果行的话,
可能是你没有跨域的问题,或者就是你把那个security依赖注释掉)
对于下面这二到七的内容暂时不做处理
接口说明
1)a. 请求方式:GET
b. 请求参数:无
c.返回数据:json
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id":1,
"tagName":"4444"
}
2)这里暂时不需要执行SQL语句
3)然后就是编码了
//对添加了Controller了的注解进行拦截
@ControllerAdvice
public class AllExceptionHandler {
@ExceptionHandler(Exception.class)
public Result doException(Exception e) {
e.printStackTrace();
return Result.fail(-999,"系统异常");
}
}
1. ## 1.2 JWT
登录使用JWT技术。
jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。
请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。
jwt 有三部分组成:A.B.C
A:Header,{"type":"JWT","alg":"HS256"} 固定
B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。
jwt 验证,主要就是验证C部分 是否合法。
2. 依赖包:
~~~xml
io.jsonwebtoken
jjwt
0.9.1
~~~
在我跟着他视频学习的同时我自己有一点小小的变动,就是在md5加密的
1.接口介绍
## 1.1 接口说明
接口url:/login
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
account | string | 账号 |
password | string | 密码 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
2. 工具类,我要使用的JWT技术的对应的类
![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 = "123456Mszlu!@#$$";
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 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
3. 编写Controller,单独建立一个LoginService来完成登录的功能
~~~java
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.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 LoginService loginService;
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
return loginService.login(loginParam);
}
}
这个LoginParam,是我们用来接受参数的,package cn.mldn.Blog.vo;这下面创建的一个类
public class PageParams {
private int page = 1;
private int pageSize = 10;
}
4. Service层
package com.mszlu.blog.service;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
public interface LoginService {
/**
* 登录
* @param loginParam
* @return
*/
Result login(LoginParam loginParam);
}
5.然后就是LoginServiceImpl的
下面的ErrorCode是我们在src\main\java\cn\mldn\Blog\vo\ErrorCode.java创建的一
个类:内容如下,错误的问题单独一个类
package cn.mldn.Blog.vo;
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
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;
}
}
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.utils.JWTUtils;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
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 {
private static final String slat = "mszlu!@#";
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Result login(LoginParam loginParam) {
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());
}
String pwd = DigestUtils.md5Hex(password + slat);
SysUser sysUser = sysUserService.findUser(account,pwd);
if (sysUser == null){
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
//登录成功,使用JWT生成token,返回token和redis中
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
public static void main(String[] args) {
System.out.println(DigestUtils.md5Hex("admin"+slat));
}
}
6. 然后就是SYSUserService的逻辑实现,其中的一个方法
@Override
public SysUser findUser(String account, String pwd) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,pwd);
queryWrapper.select(SysUser::getId,SysUser::getAccount,SysUser::getAvatar,SysUser::getNickname);
queryWrapper.last("limit 1");
SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
return sysUser;
}
7. 然后就是Redis的文件配置
~~~properties
spring.redis.host=localhost
spring.redis.port=6379
~~~
8. 然后用Postman测试
1. 接口信息
## 2.1 接口说明
接口url:/users/currentUser
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
Authorization | string | 头部信息(TOKEN) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": {
"id":1,
"account":"1",
"nickaname":"1",
"avatar":"ss"
}
}
2. Controller编写
@RestController
@RequestMapping(“users”)
public class UserController {
@Autowired
private SysUserService sysUserService;
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token){
return sysUserService.getUserInfoByToken(token);
}
}
3. LoginService层
SysUser checkToken(String token);
4. 对应的Impl中的编写
~~~java
@Override
public SysUser checkToken(String token) {
if (StringUtils.isBlank(token)) {
//在这里判断,到时候再到对饮的Service里面判断
return null;
}
Map<String, Object> map = JWTUtils.checkToken(token);
if (map == null) {
return null;
}
String s = redisTemplate.opsForValue().get("TOKEN_" + token);
if (StringUtils.isBlank(s)) {
return null;
}
return JSON.parseObject(s,SysUser.class);
}
4. 然后就是对饮的SysUserService编写
public Result findUserByToken(String token) {
/**
* 1. token合法性验证
* 是否为空,解析是否成功,Redis是否存在
* 2. 如果验证失败,返回错误
* 3. 如果成功,返回对饮的结果,LoginUserVo
*/
SysUser sysUser = loginService.checkToken(token);
//根据上面这个方法的处理,如果为null,登录不成功
if (sysUser == null) {
Result.fail(ErrorCode.Token_ERROR.getCode(),ErrorCode.Token_ERROR.getMsg());
}
LoginUserVo vo = new LoginUserVo();
vo.setId(sysUser.getId());
vo.setNickname(sysUser.getNickname());
vo.setAvatar(sysUser.getAvatar());
vo.setAccount(sysUser.getAccount());
return Result.success(vo);
}
1. 接口介绍
接口url:/logout
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
Authorization | string | 头部信息(TOKEN) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": null
}
2. 编写Controller
@RestController
@RequestMapping("logout")
public class LogoutController {
@Autowired
private LoginService loginService;
@GetMapping
public Result logout(@RequestHeader("Authorization") String token){
return loginService.logout(token);
}
}
3. 然后就是编写Service层
@Override
public Result logout(String token) {
redisTemplate.delete("TOKEN_"+token);
return Result.success(null);
}
然后就是可以在页面中的登录测试了
1. 这次我们先来看看业务逻辑
首先要知道他的是这个表单有三个字段同时也是使用token进行传送什么的
2. 接口介绍
接口url:/register
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
account | string | 账号 |
password | string | 密码 |
nickname | string | 昵称 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
3. Controller层
介绍一下这个参数类
@Data
public class LoginParam {
private String account;
private String password;
private String nickname;
}
@RestController
@RequestMapping("register")
public class RegisterController {
@Autowired
private LoginService loginService;
@PostMapping
public Result register(@RequestBody LoginParam loginParam){
return loginService.register(loginParam);
}
}
4. 再来到LoginService层进行如下
@Override
public Result register(LoginParam loginParam) {
String account = loginParam.getAccount();
String password = loginParam.getPassword();
String nickname = loginParam.getNickname();
if (StringUtils.isBlank(account)
|| StringUtils.isBlank(password)
|| StringUtils.isBlank(nickname)
){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
SysUser sysUser = this.sysUserService.findUserByAccount(account);
if (sysUser != null){
return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg());
}
sysUser = new SysUser();
sysUser.setNickname(nickname);
sysUser.setAccount(account);
sysUser.setPassword(DigestUtils.md5Hex(password+slat));
sysUser.setCreateDate(System.currentTimeMillis());
sysUser.setLastLogin(System.currentTimeMillis());
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setAdmin(1); //1 为true
sysUser.setDeleted(0); // 0 为false
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);
}
5. 再到ErrorCode里面编写
ACCOUNT_EXIST(10004,"账号已存在"),
6. SysUserService编
SysUser findUserByAccount(String account);
void save(SysUser sysUser);
7. SysUserServiceImp编写
@Override
public SysUser findUserByAccount(String account) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.last("limit 1");
return sysUserMapper.selectOne(queryWrapper);
}
@Override
public void save(SysUser sysUser) {
//注意 默认生成的id 是分布式id 采用了雪花算法
this.sysUserMapper.insert(sysUser);
}
8.加事务
为什么要加事务,因为我们在添加用户的时候,如果他的数据有问题,还有就是其
他一些的问题的时候,数据库里面是不能这样添加用户的,但是不加事务的话,可
能会自动加上。--->所以要添加事务
~~~java
@Service
@Transactional
public class LoginServiceImpl implements LoginService {}
~~~
1. 首先拦截器的实现有多种方式(SpringSecurity,继承HandlerInterceptor,还有shiro
都可以实现),这里我们选择的是继承HandlerInterceptor
2. 这种方式有两步,第一编写继承类,第二步在webMVC里面配置即可
3. 具体实现
1)编写继承类
//下面这个注解不用说,编写后,让SpringBoot能扫描到他
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
//为什么叫proHandle:因为在Spring里面Handler(就是指代Controller),pre代表什么什么之前的
@Override=
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 为什么要Handler instanceof handlerMethod呢?(因为他是要确保访问的方法是在Controller里面,否则是不能拦截的),
//2. 判断token是否为空,很明显未登录
//3. 如果token不为空就要登录验证
//4. 如果认证成功,放行
if (!(handler instanceof HandlerMethod)){
//其实说简单的就是Handler是controller里面的某一个方法
//handler 可能是RequestResourceHandler,SpringBoot程序,默认资源访问的是CLASSPATH下面的Resource下面的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===========================");
if (token == null){
//以下是错误返回的一些问题
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
//要告诉浏览器我们要返回的是这种类型
response.setContentType("application/json;charset=utf-8");
//返回的东西是result类型,要转换为JSON类型才行
response.getWriter().print(JSON.toJSONString(result));
return false;
}
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//是登录状态,放行
return true;
}
}
2)在WebMVC里面配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
registry.addInterceptor(loginInterceptor)
//一般情况下是addPathPatterns("xxx").excludePathPatterns()的形式,
// 然后这里我们先这养配置,后期再进行改变
.addPathPatterns("/test");
}
1. src\main\java\cn\mldn\Blog\utils创建UserThreadLocal.java
package cn.mldn.Blog.utils;
import cn.mldn.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();
}
}
2. 既然我们是让他来保存我们用户信息的,那么我们从哪里添加哦
对LoginIntercept修改,既然在这里验证,就在这里进行添加
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
//为什么叫proHandle:因为在Spring里面Handler(就是指代Controller),pre代表什么什么之前的
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 为什么要Handler instanceof handlerMethod呢?(因为他是要确保访问的方法是在Controller里面,否则是不能拦截的),
//2. 判断token是否为空,很明显未登录
//3. 如果token不为空就要登录验证
//4. 如果认证成功,放行
if (!(handler instanceof HandlerMethod)){
//handler 可能是RequestResourceHandler,SpringBoot程序,默认资源访问的是CLASSPATH下面的Resource下面的static目录
//其实说简单的就是Handler是controller里面的某一个方法
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===========================");
if (token == null){
//以下是错误返回的一些问题
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
//要告诉浏览器我们要返回的是这种类型
response.setContentType("application/json;charset=utf-8");
//返回的东西是result类型,要转换为JSON类型才行
response.getWriter().print(JSON.toJSONString(result));
return false;
}
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//我希望在controller中 直接获取用户的信息 怎么获取?
//然后ThreadLocal保存信息
UserThreadLocal.put(sysUser);
//是登录状态,放行
return true;
}
//上面的是UserThreadLocal.put(sysUser);
// 添加在ThreadLocal里面,既然已经添加,那么一定得删除,不然可能存在内存泄漏的问题
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}
3. 然后可以进行测试了,在controller下面创建TestController的类
@RestController
@RequestMapping("test")
public class TestController {
@RequestMapping
public Result test(){
// SysUser
SysUser sysUser = UserThreadLocal.get();
System.out.println(sysUser);
return Result.success(null);
}
}
1. 这样说吧,就比如我们的一个请求,当你启动某一个进程的时候,你让他和你对应的
进程进行绑定的话,会深入的绑定到一起(以达到绑定用户信息的目的)。
2. 为什么在那个后面一定要删除,这个是很有技术含量的哦,因为一旦内存泄漏是很严重的
2. 具体的可以康康下面这篇
文章
这要知道的是一个线程可以存在多个ThreadLocal
每一个Thread维护一个ThreadLocalMap, key为使用**弱引用**的ThreadLocal实例,
value为线程变量的副本。
**强引用**,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存
空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这
种对象。
**如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使
JVM在合适的时间就会回收该对象。**
**弱引用**,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
在java中,用java.lang.ref.WeakReference类来表示。
上面的那个 key为使用**弱引用**的ThreadLocal实例,当我们的线程中的那个
ThreadLocal被垃圾回收机制干掉之后,是不是这个弱引用的Key不存在了,但是这个是
Map集合呀,Value会永远的存在,所有要手动的删除
1. 在后端把数据返回给我们前端之后,我们会把内容展示出来,所以肯定要和数据表产
生关系
在这里插入代码片~~~sql
//内容表
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;
~~~
~~~java
import lombok.Data;
@Data
public class ArticleBody {
private Long id;
private String content;
private String contentHtml;
private Long articleId;
}
~~~sql
//类别表
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;
~~~
~~~java
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Category {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
~~~
2. 对接口的说明
## 2.1 接口说明
接口url:/articles/view/{id}
请求方式:POST
请求参数:
| 参数名称 | 参数类型 | 说明 |
| -------- | -------- | ------------------ |
| id | long | 文章id(路径参数) |
| | | |
| | | |
返回数据:
~~~json
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
~~~
3. 对ArticleServiceImpl做如下修改
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private TagService tagService;
@Autowired
private SysUserService sysUserService;
@Autowired
private CategoryService categoryService;
@Autowired
private ArticleBodyMapper articleBodyMapper;
/**
* 分页查询方法
* @param pageParams
* @return
*/
@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();
//因为页面展示出来的数据不一定和数据库一样,所有我们要做一个转换,反正我不懂
List<ArticleVo> articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}
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) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor,isBody,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;
}
//这个方法是主要点是BeanUtils,由Spring提供的,专门用来拷贝的,想Article和articlevo相同属性的拷贝过来返回
//isTag是否有标签,是否有作者信息
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){
ArticleBodyVo articleBody = findArticleBody(article.getId());
articleVo.setBody(articleBody);
}
if (isCategory){
CategoryVo categoryVo = findCategory(article.getCategoryId());
articleVo.setCategory(categoryVo);
}
return articleVo;
}
private CategoryVo findCategory(Long categoryId) {
return categoryService.findCategoryById(categoryId);
}
private ArticleBodyVo findArticleBody(Long articleId) {
LambdaQueryWrapper<ArticleBody> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ArticleBody::getArticleId,articleId);
ArticleBody articleBody = articleBodyMapper.selectOne(queryWrapper);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
@Override
public ArticleVo findArticleById(Long id) {
Article article = articleMapper.selectById(id);
return copy(article,true,true,true,true);
}
}
4. 编写controller
## 2.3 Controller
~~~java
@PostMapping("view/{id}")
public Result findArticleById(@PathVariable("id") Long id) {
ArticleVo articleVo = articleService.findArticleById(id);
return Result.success(articleVo);
}
~~~
5. 在ArticleService中编写方法
ArticleVo findArticleById(Long id);
6. 然后就是其他的Category的一些东西了
@Data
public class CategoryVo {
private Long id;
private String avatar;
private String categoryName;
}
~~~
~~~java
@Data
public class ArticleBodyVo {
private String content;
}
~~~
~~~java
public interface CategoryService {
CategoryVo findCategoryById(Long id);
}
~~~
~~~java
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public CategoryVo findCategoryById(Long id){
Category category = categoryMapper.selectById(id);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
return categoryVo;
}
}
~~~
~~~java
public interface ArticleBodyMapper extends BaseMapper<ArticleBody> {
}
~~~
~~~java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface CategoryMapper extends BaseMapper<Category> {
}
~~~
1. 问题介绍
@Override
public ArticleVo findArticleById(Long id) {
Article article = articleMapper.selectById(id);
//查完文章了,新增阅读数,有没有问题呢?
//答案是是有的,本应该直接返回数据,这时候做了一个更新操作,更新时间时加写锁,阻塞其他的读操作,新能就会比较低,
//而且更新增加了此次的耗时,一旦更新出问题,不能影响我们其他的如:看文章呀什么的
//那要怎么样去优化呢?,---->所有想到了线程池
//可以把更新操作扔到线程池里面,就不会影响了
return copy(article,true,true,true,true);
}
2. 编写自己的线程池
package cn.mldn.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;
@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("码神之路博客项目");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
3. 相当于编写线程池的服务层,使用我们的线程池
package cn.mldn.Blog.Service;
import cn.mldn.Blog.Dao.Pojo.Article;
import cn.mldn.Blog.Dao.mapper.ArticleMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class ThreadService {
@Async("taskExecutor")
public void updateViewCount(ArticleMapper articleMapper, Article article){
Article articleUpdate = new Article();
articleUpdate.setViewCounts(article.getViewCounts() + 1);
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Article::getId,article.getId());
queryWrapper.eq(Article::getViewCounts,article.getViewCounts());
articleMapper.update(articleUpdate,queryWrapper);
try {
//睡眠5秒 证明不会影响主线程的使用
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 修改之前的ArticleServiceImpl
/* @Override
public ArticleVo findArticleById(Long id) {
Article article = articleMapper.selectById(id);
//查完文章了,新增阅读数,有没有问题呢?
//答案是是有的,本应该直接返回数据,这时候做了一个更新操作,更新时间时加写锁,阻塞其他的读操作,新能就会比较低,
//而且更新增加了此次的耗时,一旦更新出问题,不能影响我们其他的如:看文章呀什么的
//那要怎么样去优化呢?,---->所有想到了线程池
//可以把更新操作扔到线程池里面,就不会影响了
return copy(article,true,true,true,true);
}*/
@Autowired
private ThreadService threadService;
@Override
public ArticleVo findArticleById(Long id) {
Article article = articleMapper.selectById(id);
threadService.updateViewCount(articleMapper,article);
return copy(article,true,true,true,true);
}
}
这里把文章分开一下,可以看我另外一篇博客项目跟着走一(码神之路)。