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

这里提前讲解一下,我们创建一个项目
1. 如果是平时开发的,首先都要导入如下依赖
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </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的导		
    
	<!--parent的解释https://blog.csdn.net/niceyoo/article/details/91852502-->
	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>

    <!--定义一些属性的问题-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
	b--然后再导入其他的依赖
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!-- 排除 默认使用的logback  -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

		<!--这个就是AOP的问题了撒,不用多说-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
		
		<!--这个是邮箱的处理-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!--这个导入进来就是进程一直在进行-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

		<!--JSON的格式问题了-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
		
		<!--mysql的操作了-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

		<!--这个我暂时不太理解-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
		<!--跟java.lang这个包的作用类似,Commons Lang这一组API也是提供一些基础的、通用的操作和处理,如自动生成toString()的结果、自动实现hashCode()和equals()方法、数组操作、枚举、日期和时间的处理等等。-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

		<!--StringUtils就是这个提供的,用来有时候验证什么是否为空呀-->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <!--像Md5加密呀-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

		<!--mybatis的配置-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--是关于Data注解的-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--时间处理的类-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
    </dependencyManagement>

    <!--打包部署就必须要下面这个插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


  1. 创建子模块
    1)创建blog-api子模块(为什么创建子模块,方便以后可能的分模块开发)
    2)导入刚刚和父依赖一样的依赖
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!-- 排除 默认使用的logback  -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
    </dependencyManagement>

  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. 现在可以毕竟也写了那么多了,启动一下试一试,我反正上面的子模块是有问题的,后来删除了又创建的,后来才好啦
    在这里插入图片描述
  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<xxx类名>。
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依赖注释掉)

在这里插入图片描述

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

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

  1. 问题引入:在之前开发的首页内容显示中,文章下面是没有作者的信息等内容的,要开发下面有内容
    在这里插入图片描述
  2. 对于我自己这个功能暂时不实现

三.首页—最热标签

  1. 问题所在:要实现最热标签的功能
    在这里插入图片描述

  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
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
~~~

二. 登录功能的具体实现

在我跟着他视频学习的同时我自己有一点小小的变动,就是在md5加密的

1.接口介绍
## 1.1 接口说明

接口url:/login

请求方式:POST

请求参数:
参数名称参数类型说明
accountstring账号
passwordstring密码
返回数据:
{
    "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

请求参数:
参数名称参数类型说明
Authorizationstring头部信息(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

	请求参数:
参数名称参数类型说明
Authorizationstring头部信息(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

	请求参数:
参数名称参数类型说明
accountstring账号
passwordstring密码
nicknamestring昵称

返回数据:

{
    "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. 具体的可以康康下面这篇

文章

(三)来看看上面讲的内存泄漏问题在这里插入图片描述

这要知道的是一个线程可以存在多个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,等。

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

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
你可以使用Vue的事件绑定和计算属性来实现拖拽移动一个元素让另一个也跟着的效果。具体实现步骤如下: 1. 绑定mousedown事件,记录鼠标点击位置和拖拽元素的位置; 2. 绑定mousemove事件,计算鼠标移动距离,更新拖拽元素的位置; 3. 绑定mouseup事件,取消mousemove事件绑定; 4. 计算属性computed中计算第二个元素的位置,确保其跟随第一个元素的位置变化。 下面是一个简单的示例代码: ```html <template> <div class="container" @mousedown="handleMouseDown"> <div class="item1" :style="{ left: item1Left + 'px', top: item1Top + 'px' }"></div> <div class="item2" :style="{ left: item2Left + 'px', top: item2Top + 'px' }"></div> </div> </template> <script> export default { data() { return { startX: 0, startY: 0, item1Left: 0, item1Top: 0, item2Left: 0, item2Top: 0 }; }, computed: { item2Style() { return { left: this.item1Left + 'px', top: this.item1Top + 'px' }; } }, methods: { handleMouseDown(e) { this.startX = e.clientX; this.startY = e.clientY; this.item1Left = e.target.offsetLeft; this.item1Top = e.target.offsetTop; document.addEventListener('mousemove', this.handleMouseMove); document.addEventListener('mouseup', this.handleMouseUp); }, handleMouseMove(e) { const diffX = e.clientX - this.startX; const diffY = e.clientY - this.startY; this.item1Left += diffX; this.item1Top += diffY; this.startX = e.clientX; this.startY = e.clientY; }, handleMouseUp() { document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); } } }; </script> <style> .container { position: relative; width: 500px; height: 500px; } .item1 { position: absolute; width: 100px; height: 100px; background-color: red; } .item2 { position: absolute; width: 50px; height: 50px; background-color: blue; } </style> ``` 在这个示例中,我们使用了data属性来存储一些状态,使用computed属性来计算第二个元素的位置。在方法中,我们绑定了mousedown、mousemove、mouseup事件,通过计算鼠标移动距离来更新第一个元素的位置。在mouseup事件中,移除mousemove事件的绑定,结束拖拽。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值