博客项目跟着走一(码神之路)

这里提前讲解一下,我们创建一个项目
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

第一天. 资源下载和搭建搭建

  1. 下载前端项目(这个的话,可以去下载)里面有QQ群

  2. 搭建项目(用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
            
        
    


  1. 创建子模块
    1)创建blog-api子模块(为什么创建子模块,方便以后可能的分模块开发)
    2)导入刚刚和父依赖一样的依赖

    
        
            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
        
    
    

  1. 编写application.peorpertie文件
    在这一步可能有很多人的这个application.properties文件不行,
    解决办法是看我另外一篇文章
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_
  1. 编写启动类
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);
    }
}

  1. 然后因为引入了上面的Mybatis-plus,所有有如下配置
    在项目中创建config文件夹,然后创建Mybatis_plusConfig.javaa并且写如下代码:
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 {
}

博客项目跟着走一(码神之路)_第1张图片

  1. 现在可以毕竟也写了那么多了,启动一下试一试,我反正上面的子模块是有问题的,后来删除了又创建的,后来才好啦
    博客项目跟着走一(码神之路)_第2张图片
  2. 我们在项目中,肯定要用到分页的,所有要用到Mybatis的分页插件。对MybatisPlusConfig做出如下修改:
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;
    }

}

  1. 接下来就创建WebMmvConfig.java文件
    1)这里先配置跨域的问题,因为前端和后端是分离了的,前端前端端口访问是跨域的,所有要配置跨域的问题。
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. 28结束,明天继续

第二天. 登录功能前的配置

一 . 首页文章列表页----1

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);
    }
}
    1. 29 结束,明天继续

**
·有很多人可能跟我一样没有学习过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依赖注释掉)

博客项目跟着走一(码神之路)_第3张图片

对于下面这二到七的内容暂时不做处理

二. 首页文章列表页----2

  1. 问题引入:在之前开发的首页内容显示中,文章下面是没有作者的信息等内容的,要开发下面有内容
    博客项目跟着走一(码神之路)_第4张图片
  2. 对于我自己这个功能暂时不实现

三.首页—最热标签

  1. 问题所在:要实现最热标签的功能
    博客项目跟着走一(码神之路)_第5张图片

  2. 接口说明

     1)a. 请求方式:GET
    
     	b. 请求参数:无
    
     	c.返回数据:json
     		{
     		"success": true,
     		"code": 200,
     		"msg": "success",
     		"data": [
    	 	{
        	 		"id":1,
         		"tagName":"4444"
    		 }
     2)这里暂时不需要执行SQL语句
     3)然后就是编码了
    

四.最热标签

五.最热文章

六.最新文章

七.文章归档

八.统一异常处理

  1. 在平时,我们浏览浏览器,它会给我们(有异常)显示不一样的画面,我们开发的不可能就一穿显示撒,所有做一下统一的异常处理
  2. src\main\java\cn\mldn\Blog\handler建立AllExceptionHandler
//对添加了Controller了的注解进行拦截
@ControllerAdvice
public class AllExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result doException(Exception e) {
        e.printStackTrace();
        return Result.fail(-999,"系统异常");
    }
}

中场休息一下,对前面的开发一些感受

  1. 我觉得首先基本就是先开发数据库,然后编写好对应的数据表对应的类
  2. 然后再次开发Controller,这个去调用Service层
  3. 这个Service层(如果用的Mybatis)然后就是去调用Mapper层(DAO),然后就是相对应的XXXMapper去数据查找
  4. 然后我们再到业务具体的情况具体看

第六天.登录功能的实现

一. JWT技术的使用

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测试

博客项目跟着走一(码神之路)_第6张图片

三. 登录上去之后—>加载用户信息

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进行传送什么的

博客项目跟着走一(码神之路)_第7张图片

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");
    }

七.ThreadLocal(用来保存登录用户的信息)

(一)先来看一下怎么使用ThreadLocal

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);
    }
}

(二)ThreadLocal(本地的,线程)到底有什么用

1. 这样说吧,就比如我们的一个请求,当你启动某一个进程的时候,你让他和你对应的
	进程进行绑定的话,会深入的绑定到一起(以达到绑定用户信息的目的)。
2. 为什么在那个后面一定要删除,这个是很有技术含量的哦,因为一旦内存泄漏是很严重的
2. 具体的可以康康下面这篇

文章

(三)来看看上面讲的内存泄漏问题博客项目跟着走一(码神之路)_第8张图片

这要知道的是一个线程可以存在多个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);
    }
}

这里说明一下,我们在编写一个数据库表和对应的映射类的时候,一定不要用简单类型int 什么的,都使用对应的类Integer,等。

这里把文章分开一下,可以看我另外一篇博客项目跟着走一(码神之路)。

你可能感兴趣的:(前后端分离项目,spring,boot,java,intellij-idea)