Front End Project Run Command
npm install npm run build npm run dev
Create a project that, if developed normally, first imports the following dependencies
<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>
If the project you created reports the following errors, please lower the SpringBoot version
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. Resource download and project building
-
Download Front End Projects (in this case, go to QQ group) download)
-
Build Project (Create Project with idea)
1) idea create new xxxx in a series of operations
2) Import Dependencya -----: parent and properties Import ```
<!--parent Interpretation 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> <!--Issues defining some attributes--> <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--Import other dependencies
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lum</groupId> <artifactId>blog-parent</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>blog</module> </modules> <packaging>pom</packaging> <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> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!-- https://mvnrepository.com/artifact/joda-time/joda-time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.10</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- Create sub-module blog-api
1) Why create sub-modules: convenient for future sub-module development
2) Import Dependency
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>blog-parent</artifactId> <groupId>com.lum</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>blog</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <!-- Exclude default 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> <!--This is AOP Let's not talk more about the problem.--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--This is the mailbox handling--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <!--This import comes in because the process is ongoing--> <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> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--JSON Formatting problem--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <!--mysql Operation--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--You can see json Medium Information--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!--with java.lang This package does something similar, Commons Lang This group API It also provides some basic, common operations and processing, such as automatic generation toString()Result, Auto-implementation hashCode()and equals()Methods, array operations, enumerations, date and time processing, and so on.--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!--StringUtils This is what's provided to sometimes verify what's empty.--> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <!--image Md5 Encrypt it--> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <!--mybatis Configuration--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!--Is about Data Annotated--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--Classes for time processing--> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.10</version> </dependency> <!--Verification--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>[7.7.0, 7.7.99]</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency> </dependencies> </project>
- Write application.properties file
server.port=8888 #Configuration Project Name spring.application.name=blog #Database Settings spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #Mybaties-plus mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.global-config.db-config.table-prefix=ms_
- Write startup class BlogApplication.java
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); } }
- Then because of the introduction of Mybatis-plus above, all have the following configurations
Create the config folder in your project, then create MybatisplusConfig.java and write the following code:
package com.lum.blog.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.lum.blog.mapper") public class MyBatiesPlusConfig { }
-
Start BlogApplication Test
Success will do, maybe failure, mostly maven dependencies are not right, compare carefully! -
Paging is definitely used in projects, and all Paging Plugins that use Mybatis are included.The following modifications were made to MybatisPlusConfig:
//Mybatis-plus Paging Plugin @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor( new PaginationInnerInterceptor()); return mybatisPlusInterceptor; }
- Create a WebMmvConfig.java file
Configure cross-domain issues here first, since the front and back ends are separated, and front-end front-end port access is cross-domain, all cross-domain issues to be configured.
package com.lum.blog.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { //For cross-domain configuration, why do I need this cross-domain configuration because, for example, my front-end port number is 8080 and my back-end interface is 8888 @Override public void addCorsMappings(CorsRegistry registry) { //addMapping is all the files, allowedOrigins means that you can access them at that address registry.addMapping("/**").allowedOrigins("http://localhost:8080"); } }
ππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
2. Functions
1. First Page Articles List Page - -1
1.1 Interface Description
Interface url:/articles
Request method: post request
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
page | int | Current Page |
pagesize | int | Number of displays per page |
Return data: ~~~json { "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", "summary": "adopt Spring Boot Only one service needs to be implemented Java Class, pack it into jarοΌAnd through`java -jar`The command will run.\r\n\r\n This is all in contrast to tradition Spring It has become very lightweight and simple to use.", "commentCounts": 2, "viewCounts": 54, "weight": 1, "createDate": "2021-06-26 15:58", "author": "lum", "body": null, "tags": [ { "id": 5, "avatar": null, "tagName": "444" }, { "id": 7, "avatar": null, "tagName": "22" }, { "id": 8, "avatar": null, "tagName": "11" } ], "categorys": null }, { "id": 9, "title": "Vue.js What's this", "summary": "Vue (pronunciation /vjuΛ/οΌBe similar to view) Is a progressive framework for building user interfaces.", "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 Relevant", "summary": "This section describes how to use it in your project Element. ", "commentCounts": 0, "viewCounts": 3, "weight": 0, "createDate": "2609-06-27 11:25", "author": "12", "body": null, "tags": [ { "id": 5, "avatar": null, "tagName": "444" }, { "id": 6, "avatar": null, "tagName": "33" }, { "id": 7, "avatar": null, "tagName": "22" }, { "id": 8, "avatar": null, "tagName": "11" } ], "categorys": null } ] }
1.2 Table Structure
Now that we have data to return from the front-end and pages, our users must be related to classes for anything.
Article Data Table Returning Data ~~~sql CREATE TABLE `blog`.`ms_article` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `comment_counts` int(0) NULL DEFAULT NULL COMMENT 'Number of comments', `create_date` bigint(0) NULL DEFAULT NULL COMMENT 'Creation Time', `summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'brief introduction', `title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Title', `view_counts` int(0) NULL DEFAULT NULL COMMENT 'Number of Browses', `weight` int(0) NOT NULL COMMENT 'isTop', `author_id` bigint(0) NULL DEFAULT NULL COMMENT 'author id', `body_id` bigint(0) NULL DEFAULT NULL COMMENT 'content id', `category_id` int(0) NULL DEFAULT NULL COMMENT 'category id', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; ~~~ //Tag table, other items can be viewed by the article ~~~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; ~~~ //User Data Table ~~~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 'Account number', `admin` bit(1) NULL DEFAULT NULL COMMENT 'Is Administrator', `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Head portrait', `create_date` bigint(0) NULL DEFAULT NULL COMMENT 'Registration Time', `deleted` bit(1) NULL DEFAULT NULL COMMENT 'Delete or not', `email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'mailbox', `last_login` bigint(0) NULL DEFAULT NULL COMMENT 'Last Logon Time', `mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Cell-phone number', `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Nickname?', `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Password', `salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Encrypted salt', `status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'state', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
1.3 Dao Development
com.lum.blog.dao.pojo.Article.java
package com.lum.blog.dao.pojo; import lombok.Data; /* Blog Management */ @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; /** * Author id */ private Long authorId; /** * Content id */ private Long bodyId; /** *Category id */ private Long categoryId; /** * Set Top */ private int weight = Article_Common; /** * Creation Time */ private Long createDate; }
com.lum.blog.dao.pojo.SysUser.java
package com.lum.blog.dao.pojo; import lombok.Data; /* user management */ @Data public class SysUser { private Long id; private String account; private Integer admin; private String avatar; private Long createDate; private Integer deleted; private String email; private Long lastLogin; private String mobilePhoneNumber; private String nickname; private String password; private String salt; private String status; }
com.lum.blog.dao.pojo.Tag.java
package com.lum.blog.dao.pojo; import lombok.Data; /* Label Management */ @Data public class Tag { private Long id; private String avatar; private String tagName; }
1.4 Creation of Mapper
package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.Article; public interface ArticleMapper extends BaseMapper<Article> { } ******************************************************************* package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.SysUser; public interface SysUserMapper extends BaseMapper<SysUser> { } ******************************************************************* package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.Tag; public interface TagMapper extends BaseMapper<Tag> { }
1.5 apper created, it's Controller's time
Establish ArticleControllerοΌThis represents the controller of the article class
package com.lum.blog.controller; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.PageParams; import org.springframework.web.bind.annotation.*; //Interacting with json data @RestController @RequestMapping("articles") public class ArticleController { //Why use a post request because the previous interface describes the post request //For the other parameters, set up the PageParams class below vo to specifically represent the parameters /** * First Page Articles List * @param pageParams * return Returns the class responsible for returning the data Result */ @PostMapping public Result listArticle(@RequestBody PageParams pageParams){ //For accepted parameter problems, here is RequestBody receive return articleService.listArticle(pageParams); } }
1) This is supplemented by the parameter we passed in, which is the representative of the PageParms class
Create a directory for vo under com.lum.Blog and then create a class for PageParms
package com.lum.blog.vo.params; import lombok.Data; //Classes that bear the number and number of returned pages @Data public class PageParams { private int page = 1; private int pageSize = 10; }
2) Classes of data Result returned
package com.lum.blog.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; //The class responsible for returning to the list of articles on the first page @Data @AllArgsConstructor @NoArgsConstructor public class Result { //Represents success private boolean success; //Code on our behalf private int code; //Represents a message private String msg; //Represents data private Object data; //Represents Success public static Result success(Object data) { return new Result(true , 200, "success" ,data); } //On behalf of failure public static Result Fail(int code, String msg) { //No data to return, all data is null return new Result(false , code, msg,null); } }
1) First we write the DAO layer and the table names in the database to correspond.
2) Then the XXX-Mapper layer is written to inherit BaseMapper<xxx class name>.
3) Then, instead of typically writing his xxxMapper.xml, we will configure xxxService+xxxServiceImpl if we want to write xxxMapper.xml as well as Mybatis
1.6 service
From the above, we can see that we have almost developed, but want to return the data, but the listArticle method does not have a Service to read the data, so we develop a Service and a method to read the data.
1) Start by writing this service layer, creating a service folder under src/main/java, and following ArticleService files and Impl folders
package com.lum.blog.service; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.PageParams; public interface ArticleService { /** * Paging Query Articles List * @param pageParams * @return */ Result listArticle(PageParams pageParams); }
2) Come back and write his Impl file, ArticleServiceImpl
package com.lum.blog.service.Impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.lum.blog.dao.mapper.ArticleMapper; import com.lum.blog.dao.pojo.Article; import com.lum.blog.service.ArticleService; import com.lum.blog.vo.ArticleVo; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.PageParams; import org.joda.time.DateTime; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class ArticleServiceImpl implements ArticleService { @Autowired private ArticleMapper articleMapper; @Override public Result listArticle(PageParams pageParams) { //1. This is a class of paged queries (representing the split mode), which are passed in the number of pages and the total number of pages Page<Article> page = new Page<Article>(pageParams.getPage(),pageParams.getPageSize()); //2. LambdaQueryWrapper is provided by MybatisPlus, you can import this package as needed LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); //3. Here's sorting by font //queryWrapper.orderByDesc(Article::getWeight); //4. The setting here is to sort by time //queryWrapper.orderByDesc(Article::getCreateDate); //5. This method default Children orderByDesc(boolean condition, R column, R... columns) {is a variable length parameter queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); // Because articleMapper inherits BaseMapper, all parameters set for the query and how the query is sorted Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); //This is the array of data we queried List<Article> records = articlePage.getRecords(); //Because the data displayed on the page is not necessarily the same as the database, we need to do a conversion //Decoupling, vo interacting with page data will be accomplished by copying an array of lookup databases into articleVo 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; } //The main point of this method is BeanUtils, which Spring provides, which is dedicated to copying and returns a copy of the same properties as Article and 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; } }
Where ArticleVo and TagVo need to be created
package com.lum.blog.vo; import lombok.Data; import java.util.List; @Data public class ArticleVo { private Long id; private String title; private String summary; //brief introduction private int commentCounts; private int ViewCounts; private int weight; //Set Top private String createDate; //Creation Time private String author; //Not need it for now // private ArticleBodyVo body; private List<TagVo> tags; //Not need it for now // private List<CategoryVo> categories; }
package com.lum.blog.vo; import lombok.Data; @Data public class TagVo { private Long id; private String tagName; }
Data that vo interacts with pages should not be coupled with database mapping objects and should ideally be separated
Here's a complement to this articleVo, this class, because what we usually develop, it's time
Find the data in the database, and then find out it is not necessarily the same, to copy it the same, different return null
Summary: With the above configuration, the content of the page can be displayed
2. First Page Articles List Page - 2
Problem introduction: In the content display of the first page developed before, there is no label, author's information and so on below the article. To develop the content below
2.1 Implemented in ArticleServiceImpl
Think: Not all interfaces need labels, author information
Add two boolean isTag,isAuthor To make a judgment
Add code to copyList
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor)); } return articleVoList; }
Add code to copy
//The main point of this method is BeanUtils, which Spring provides, which is dedicated to copying and returns a copy of the same properties as Article Vo private ArticleVo copy(Article article,boolean isTag,boolean isAuthor) { ArticleVo articleVo = new ArticleVo(); BeanUtils.copyProperties(article,articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); /* Not all interfaces require labels, author information Add two parameters boolean isTag,boolean isAuthor */ //tagService needs to be developed if (isTag) { Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); } //authorService needs to be developed if (isAuthor) { Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } return articleVo; }
2.2 Tag tag
Write TagService
package com.lum.blog.service; import com.lum.blog.vo.TagVo; import java.util.List; public interface TagService { List<TagVo> findTagsByArticleId(Long articleId); }
Write implementation class TagServiceImpl
package com.lum.blog.service.Impl; import com.lum.blog.dao.mapper.TagMapper; import com.lum.blog.dao.pojo.Tag; import com.lum.blog.service.TagService; import com.lum.blog.vo.TagVo; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class TagServiceImpl implements TagService { @Autowired private TagMapper tagMapper; /*copyList Pass tag*/ public TagVo copy(Tag tag){ TagVo tagVo = new TagVo(); BeanUtils.copyProperties(tag,tagVo); return tagVo; } public List<TagVo> copyList(List<Tag> tagList) { List<TagVo> tagVoList = new ArrayList<>(); for (Tag tag : tagList) { tagVoList.add(copy(tag)); } return tagVoList; } /**********************************/ @Override public List<TagVo> findTagsByArticleId(Long articleId) { /* MyBatisPlus Unable to implement multi-table query */ List<Tag> tags=tagMapper.findTagsByArticleId(articleId); return copyList(tags); } }
Write TagMapper
package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.Tag; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface TagMapper extends BaseMapper<Tag> { /** * Query tag list based on article id * @param articleId * @return */ List<Tag> findTagsByArticleId(Long articleId); }
Create TagMapper.xml under resourse
Path is consistent with interface package (com.lum.blog.dao.mapper)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lum.blog.dao.mapper.TagMapper"> <!-- List<Tag> findTagsByArticleId(Long articleId);--> <select id="findTagsByArticleId" parameterType="long" resultType="com.lum.blog.dao.pojo.Tag"> select id,avatar,tag_name as tagName from ms_tag where id in (select tag_id from ms_article_tag where article_id=#{articleId}) </select> </mapper>
parameterType="long" corresponds to articleId of List findTagsByArticleId(Long articleId)
select id,avatar,tag_name as tagName from ms_tag
where id in
(select tag_id from ms_article_tag where article_id=#{articleId})
Query the id,auatar,tagName of a tag in an associated table
Hump naming can be turned on by mybatis-plus in application.properties
mybatis-plus.configuration.map-underscore-to-camel-case=true
This eliminates the need for an as alias for SQL statements.
Next, inject TagService into the ArticleImpl implementation
if (isTag) { Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); }
2.3 Author author
Set up interface SysUserService
package com.lum.blog.service; import com.lum.blog.dao.pojo.SysUser; public interface SysUserService { SysUser findUserById(Long id); }
Write implementation class SysUserServiceImpl
package com.lum.blog.service.Impl; import com.lum.blog.dao.mapper.SysUserMapper; import com.lum.blog.dao.pojo.SysUser; import com.lum.blog.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper sysUserMapper; @Override public SysUser findUserById(Long id) { /*Prevent IDs from being empty*/ SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null) { sysUser = new SysUser(); sysUser.setNickname("Deer Ming"); } return sysUser; } }
Inject SysUserMapper and write a query
Inject SysUserService into the article implementation class ArticleServiceImpl
if (isAuthor) { Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); }
Write a workaround for empty situations in SysUserImpl
public SysUser findUserById(Long id) { /*Prevent IDs from being empty*/ SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null) { sysUser = new SysUser(); sysUser.setNickname("lum"); } return sysUser; }
Add the return value istag,isauthor to the copyList
List<ArticleVo> articleVoList = copyList(records,true,true);
for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor)); }
Where is it reflected in the preceding code?π
Testing
3. Home page - Hottest label
1.Tags have the most articles 2.Query by tag_id Grouping,technology,Row from large to small,Prefetch limit individual
3.1 Interface Description
Interface url:/tag/hot
Request method: Get
Request parameters: None
Return data:
{ "successs":true "code"200 "msg":"success" "data"[ { "id":1, "tagName":"Hottest" } ] }
3.2 Encoding
Write controller first
Create TagsController
package com.lum.blog.controller; import com.lum.blog.service.TagService; import com.lum.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("tags") public class TagsController { @Autowired private TagService tagService; @GetMapping("hot") public Result hot() { int limit =6; //Hottest 6 return tagService.hots(limit); } }
Implementing the hots method in tagService
package com.lum.blog.service; import com.lum.blog.vo.Result; import com.lum.blog.vo.TagVo; import java.util.List; public interface TagService { List<TagVo> findTagsByArticleId(Long articleId); Result hots(int limit); }
First analyze the SQL statement
select tag_id from ms_article_tag group by tag_id order by count(*) limit 2
TagServiceImpl implements Result hots(int limit)
@Override public Result hots(int limit) { /** * 1.Tags have the most articles * 2.Query based on tag_id grouping, technology, sorting from large to small, top limit */ List<Long> tagIds= tagMapper.findHotsTagId(limit); return null; }
Create findHostTagId(limit) method in TagMapper
/** * Query the top hot label limit bar * @param limit * @return */ List<Long> findHotsTagId(int limit);
Generate findHotsTagId in the resource file TagMapper and add==parameterType="int"==attribute and sql statement
<select id="findHotsTagId" parameterType="int" resultType="java.lang.Long"> select tag_id from ms_article_tag group by tag_id order by count(*) limit 6 </select>
At this point, the hottest tagid is queried, and tagName,Tag need to be queried according to tagid
Continue Result hot
@Override public Result hots(int limit) { /** * 1.Tags have the most articles * 2.Query based on tag_id grouping, technology, sorting from large to small, top limit */ List<Long> tagIds= tagMapper.findHotsTagId(limit); /*Determine if tagIds is empty*/ if(CollectionUtils.isEmpty(tagIds)){ return Result.success(Collections.emptyList()); } // What you need are tagId and tagName tag objects // select * from tag where id in (1,2,3) List<Tag> tagList= tagMapper.findTagsByIds(tagIds); return Result.success(tagList); }
So you need to add a method in TagMapper.java
/** * Query Tag objects based on tagId * @param tagIds * @return */ List<Tag> findTagsByIds(List<Long> tagIds);
Add in resource TagMapper.xml
<!-- List<Tag> findTagsByIds(List<Long> tagIds);--> <select id="findTagsByIds" parameterType="list" resultType="com.lum.blog.dao.pojo.Tag"> select id,tag_name from ms_tag where id in <foreach collection="tagIds" item="tagId" separator="," open="(" close=")"> #{tagId} </foreach> </select>
3.3 Testing
4. Unified exception handling
Create handler (under com.lum.blog.) package and create AllExceptionHandler class
package com.lum.blog.handler; import com.lum.blog.vo.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice //Intercept handling of AOP implementations for methods with @ControllerAdvice added public class AllExceptionHandler { @ExceptionHandler(Exception.class) //Do exception handling to handle Exception.class exceptions @ResponseBody //Return Json data public Result doExceptionHandler(Exception e){ e.printStackTrace(); return Result.fail(-999,"System exception"); } }
Add error code to controller code to test
5. Home - Hottest Articles
5.1 Interface Description
Interface url: /articles/hot
Request method: POST
Request parameters:
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", }, { "id": 2, "title": "springboot Introduction and introductory cases", } ] }
5.2 Controller
Add PostMapper to ArticleController to return to the hottest articles
/** * Hottest articles on the first page * @return */ @PostMapping("hot") public Result hotArticle(){ int limit=5; return articleService.hotArticle(limit); }
5.3Service
Create the corresponding method in articleService. hotArticle(limit);
Result hotArticle(int limit);
Implement Result hotArticle(int limit) in articleServiceImpl for sql query and copyList return
@Override public Result hotArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getViewCounts); queryWrapper.select(Article::getId,Article::getTitle); queryWrapper.last("limit"+limit); //select id,title from article order by view_counts desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles,false,false)); }
5.4 Testing
6. Home page - latest article
6.1 Interface Description
Interface url: /articles/new
Request method: POST
Request parameters:
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", }, { "id": 2, "title": "springboot Introduction and introductory cases", } ] }
6.2 Controller
Add PostMapper to ArticleController to return to the hottest articles
/** * Hottest articles on the first page * @return */ @PostMapping("new") public Result newArticle(){ int limit=5; return articleService.newArticle(limit); }
6.3Service
Create the corresponding method in articleService. hotArticle(limit);
Result newArticle(int limit);
Implement Result hotArticle(int limit) in articleServiceImpl for sql query and copyList return
@Override public Result newArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getCreateDate); queryWrapper.select(Article::getId,Article::getTitle); queryWrapper.last("limit"+limit); //select id,title from article order by CreateDate desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles,false,false)); }
6.4 Testing
7. Home Page - Article Archive
Articles are filed according to the year and month of the date
Because the database create_date needs a time stamp of / 1000 for bigint type, then FROM_UNIXTIME for format conversion
The SQL statement is
select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month;
7.1 Interface Description
Interface url: /articles/listArtchives
Request method: POST
Request parameters:
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "year": "2021", "mouth": "6", "count"2 } ] }
7.2 Controller
ArticleController class
/** * First Page Article Archive * @return */ @PostMapping("listArchives") public Result listArchives(){ return articleService.listArchives(); }
7.3 Service
ArticleService
/** * Article Archives * @return */ Result listArchives();
Implement in ArticleServiceImpl
/*Article Archives*/ @Override public Result listArchives() { List<Archives> archivesList = articleMapper.listArchives(); return Result.success(archivesList); }
7.4 mapper
Because the data returned by the article archive is not the direct data of the database, temporary use, not a pojo object
So create dos packages to hold nonpersistent data
Create Archives Class Archive Information
package com.lum.blog.dao.dos; import lombok.Data; @Data public class Archives { private Integer year; private Integer month; private Long count; }
Create Articlemapper
package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.dos.Archives; import com.lum.blog.dao.pojo.Article; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface ArticleMapper extends BaseMapper<Article> { List<Archives> listArchives(); }
Create ArticleMapper.xml in resource pack mapper to implement listArchives()
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lum.blog.dao.mapper.ArticleMapper"> <select id="listArchives" resultType="com.lum.blog.dao.dos.Archives"> select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month; </select> </mapper>
7.5 Testing
8 Login Function Implementation (JWT)
8.1 Interface Description
Interface url:/login
Request method: POST
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
account | string | Account number |
password | string | Password |
Data returned
{ "success": true, "code": 200, "msg": "success", "data": "token" }
8.2JWT Technology Implementation
JSON Web Token (JWT)οΌIt is currently the most popular cross-domain authentication solution JWT The essence is "de-centralized", where the data is stored on the client side.
jwt can generate an encrypted token to be issued to the client when the user logs on successfully.
When requesting resources and interfaces to log in, take the token with you, and the back end verifies that the token is legal.
jwt has three components:
-
Header, {"type": "JWT", "alg": "HS256"} Fixed
-
playload, which stores customized information such as user id, expiration time, etc., can be decrypted and sensitive information cannot be stored
-
The first two points of the visa, together with key encryption, are considered secure as long as the key is not lost.
(alg:HS256 Is an algorithm)
jwt verification mainly verifies whether the visa part is legal
Import Dependent Packages
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
Create Tool Class JWTUtils
package com.lum.blog.utils; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JWTUtils { private static final String jwtToken = "12345Lum!@#$%'; //secret key public static String createToken(Long userId){ Map<String, Object> claims = new HashMap<>(); claims.put("userId",userId); JwtBuilder jwtBuilder = Jwts.builder() .signWith(SignatureAlgorithm.HS256,jwtToken) //Issuance algorithm with jwtToken key .setClaims(claims)//body data, unique, self-set .setIssuedAt(new Date()) //Set issuance time .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 *1000)); //Time available every day return jwtBuilder.compact(); } public static Map<String,Object> checkToken(String token){ try { Jwt parser = Jwts.parser().setSigningKey(jwtToken).parse(token); //Resolve jwtToken return (Map<String, Object>) parser.getBody(); }catch ( Exception e) { e.printStackTrace(); } return null; } /*Test jwt*/ public static void main(String[] args) { String token = JWTUtils.createToken(100L); System.out.println(token); Map<String, Object> map = JWTUtils.checkToken(token); System.out.println(map.get("userId")); } }
8.3Controller
Create LoginController at the controller layer for login control
package com.lum.blog.controller; import com.lum.blog.service.LoginService; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/login") public class LoginController { // @Autowired // private SysUserService sysUserService; // Not recommended, each Service has a separate business @Autowired private LoginService loginService; @PostMapping public Result login(@RequestBody LoginParam loginParam){ //Logon Authentication User Access User Table return loginService.login(loginParam); } }
Business needs to be written at the Service tier
8.4Service
Create LoginService to write business
package com.lum.blog.service; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.LoginParam; public interface LoginService { /** * Logon function * @param loginParam * @return */ Result login(LoginParam loginParam); }
Write login parameters in the param parameter package of the vo package Logon parameters
Implementing the login method in LoginServiceImpl
package com.lum.blog.service.Impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.lum.blog.dao.pojo.SysUser; import com.lum.blog.service.LoginService; import com.lum.blog.service.SysUserService; import com.lum.blog.utils.JWTUtils; import com.lum.blog.vo.ErrorCode; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.LoginParam; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class LoginServiceImpl implements LoginService { @Autowired private SysUserService sysUserService;//User table required @Autowired private RedisTemplate<String,String> redisTemplate; private static final String salt="lum!@#"; @Override public Result login(LoginParam loginParam) { /** * 1.Check parameter validity * 2.Queries exist based on the username and password zone user tables * 3.If there are no login failures * 4.If present, use jwt to generate token to return to front end * 5.token Put it in redis, redis maps token and user information, sets expiration time, verifies token is legal, and then verifies whether it exists in redis */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); if (StringUtils.isBlank(account)||StringUtils.isBlank(password)) { return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } password = DigestUtils.md5Hex(password + salt); //Password Salting SysUser sysUser = sysUserService.findUser(account,password); if (sysUser == null) { return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg()); } String token = JWTUtils.createToken(sysUser.getId()); redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); //Expiration Time return Result.success(token); } }
Here, first determine if the username or password is empty, and then return if it is empty Uniform error code
if (StringUtils.isBlank(account)||StringUtils.isBlank(password)) { return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); }
Salt the password again
private static final String salt="lum!@#"; password = DigestUtils.md5Hex(password + salt); //Password Salting
redis are required to mediate caching and databases
Conduct application.properties redis configuration
Set token and expiration time
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
8.5, login parameters, redis configuration, unified error code
LoginParam login parameters
package com.lum.blog.vo.params; import lombok.Data; @Data public class LoginParam { private String account; private String password; }
redis configuration
#redis configuration spring.redis.host=localhost spring.redis.port=6379
Uniform error code
Create ErrorCode under vo package
package com.lum.blog.vo; public enum ErrorCode { PARAMS_ERROR(10001,"Error in parameters"), ACCOUNT_PWD_NOT_EXIST(10002,"User password does not exist!"), TOKEN_ERROR(10003,"Token Wrongful"), ACCOUNT_EXIST(10004,"Account already exists"), NO_PERMISSION(70001,"No access rights"), SESSION_TIME_OUT(90001,"session time out"), NO_LOGIN(90002,"Not logged in"),; private int code; private String msg; ErrorCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
8.6 Testing
Use postman for testing, because after login, you need to jump pages, token authentication, there is an interface not completed, change ends will have problems,
When the token front end is acquired, it stores b5 in the storage, which is stored locally
token can also be queried using key * in redis-ci
9. Get user information after login
9.1 Interface Description
Interface url:/users/currentUser
Request method: Get
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
AuthorZation | string | Header Information (Token) |
9.2 Controller
Create UserController for user information return
package com.lum.blog.controller; import com.lum.blog.service.SysUserService; import com.lum.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("users") public class UserController { @Autowired private SysUserService sysUserService; //users/currentUser @GetMapping("currentUser") public Result currentUser(@RequestHeader("Authorization") String token) { //Request Header return sysUserService.findUserByToken(token); } }
9.3 Service
Add the findUserByToken(String token) method to the SysUserService interface
/*Query user information based on token*/ Result findUserByToken(String token);
Writing findUserByToken(String token) in the implementation class requires creating LoginUserVo in the vo package
@Override public Result findUserByToken(String token) { /** * 1.According to token validity * Is it empty, resolution successful, redis present * 2.If the check fails, an error is returned * 3.If successful, return the corresponding result LoginUserVo * */ SysUser sysUser = loginService.checkToken(token); if (sysUser == null) { return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg()); } //In theory, you should write a TokenService process, which is simple here LoginUserVo loginUserVo = new LoginUserVo(); loginUserVo.setId(sysUser.getId()); loginUserVo.setNickname(sysUser.getNickname()); loginUserVo.setAccount(sysUser.getAccount()); loginUserVo.setAvatar(sysUser.getAvatar()); return Result.success(loginUserVo); }
LoginUserVo
package com.lum.blog.vo; import lombok.Data; @Data public class LoginUserVo { private Long id; private String account; private String nickname; private String avatar; //Head portrait }
Write a LoginService interface to add a checkToken to check if the Token is empty, do not empty Continue to determine if the userJson in redis is empty, and do not return a user object for empty
SysUser checkToken(String token);
Implement method checkToken in LoginServiceImpl
@Override public SysUser checkToken(String token) { if (StringUtils.isBlank(token)){ return null; } Map<String, Object> checkToken = JWTUtils.checkToken(token); if (checkToken == null) { return null; } String userJson = redisTemplate.opsForValue().get("TOKEN_" + token); if(StringUtils.isBlank(userJson)){ return null; } SysUser sysUser = JSON.parseObject(userJson, SysUser.class); return sysUser; }
9.4 Testing
10 Log out
Front-end clears token, back-end clears data in redis
10.1 Interface Description
Interface url:/logout
Request method: Get
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
AuthorZation | string | Header Information (Token) |
10.2 Controller
Create LogoutController
package com.lum.blog.controller; import com.lum.blog.service.LoginService; import com.lum.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/logout") public class LogoutController { @Autowired private LoginService loginService; @GetMapping public Result logout(@RequestHeader("Authorization") String token){ return loginService.logout(token); } }
10.3Service
Create logout method in LoginService interface
/** * Log out * @param token * @return */ Result logout(String token);
Complete method in implementation class
Remove token from redis
@Override public Result logout(String token) { redisTemplate.delete("TOKEN_" + token); return Result.success(null); }
11. Registration
sso(single sign on) Single sign-on,Later put forward login registration,Individual services,Interfaces can be provided independently
11.1 Interface Description
Interface url:/register
Request method: POST
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
account | string | Account number |
password | string | Password |
nickname | string | Nickname? |
Data returned
{ "success": true, "code": 200, "msg": "success", "data": "token" }
11.2 Controller
Create RegisterController
package com.lum.blog.controller; import com.lum.blog.service.LoginService; import com.lum.blog.vo.Result; import com.lum.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("register") public class RegisterController { @Autowired private LoginService loginService; @PostMapping public Result register(@RequestBody LoginParam loginParam) { return loginService.register(loginParam); } }
11.3 Service
LoginParam adds attribute nickname
Add Method to LoginService Interface
private String nickname; /** * register * @param loginParam * @return */ Result register(LoginParam loginParam)
Implement register method in LoginServiceImpl
@Override public Result register(LoginParam loginParam) { /** * 1.Judging whether a parameter is legal * 2.Determine whether an account exists * 3.No registered user exists for the account * 4.Generate token * 5.Save redis and return * 6.Note that with transactions, registered users need to roll back whenever there is any problem in the middle */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); String nickname = loginParam.getNickname(); if (StringUtils.isBlank(account) ||StringUtils.isBlank(nickname) ||StringUtils.isBlank(password) ){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } SysUser sysUser = sysUserService.findUserByAccount(account); if (sysUser != null) { return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(), "Account already used in Europe"); } sysUser=new SysUser(); sysUser.setAccount(account); //Account Name sysUser.setNickname(nickname); //Nickname? sysUser.setPassword(DigestUtils.md5Hex(password+salt)); //Password salt md5 sysUser.setCreateDate(System.currentTimeMillis()); //Creation Time sysUser.setLastLogin(System.currentTimeMillis()); //Last Logon Time sysUser.setAvatar("/static/img/logo.b3a48c0.png"); //Head portrait sysUser.setAdmin(1); //Administrator privileges sysUser.setDeleted(0); //False Delete sysUser.setSalt(""); //salt sysUser.setStatus(""); //state sysUser.setEmail(""); //mailbox this.sysUserService.save(sysUser); //Pass token String token = JWTUtils.createToken(sysUser.getId()); redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); return Result.success(token); }
Because you need to find and write accounts to the database, you need to add a new method to SysUserService
/** * Query users based on account name * @param account * @return */ SysUser findUserByAccount(String account); /** * Save User * @param sysUser */ void save(SysUser sysUser);
Implement findUserByAccount(String account)
@Override public SysUser findUserByAccount(String account) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getAccount,account); queryWrapper.last("limit 1"); return this.sysUserMapper.selectOne(queryWrapper); }
Implement save(SysUser sysUser)
public void save(SysUser sysUser) { //Saving the user id is automatically generated //Generate distributed IDS by default, using the snowflake algorithm //mybatis-plus this.sysUserMapper.insert(sysUser); }
Open database transaction
Open @Transactional in SysUserServiceImpl
11.4 Testing
12 Logon Interceptor
Every time you access a resource that needs to be logged in, you need to make a judgment in the code. Once the login logic changes, the code changes, which is not appropriate
Can login judgment be unified then?
Yes, use an interceptor to intercept logins. If an interface is accessible only when login requirements are encountered, the interceptor returns directly and jumps to the login page.
1. Interceptors can be implemented in a variety of ways ( SpringSecurityοΌinherit HandlerInterceptorοΌAlso shiro Can be achieved), here we choose inheritance HandlerInterceptor 2. There are two steps to this, first writing inheritance classes, and second writing inheritance classes in webMVC Inside Configuration
12.1 Interceptor Implementation
package com.lum.blog.handler; import com.alibaba.fastjson.JSON; import com.lum.blog.dao.pojo.SysUser; import com.lum.blog.service.LoginService; import com.lum.blog.vo.ErrorCode; import com.lum.blog.vo.Result; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Autowired private LoginService loginService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //Execute before executing the Controller method (Handle) // What does prerepresent before /** * 1.You need to decide if the requested interface path is a HandleMethod(controller method) * 2.Determine if token is empty and not logged in * 3.Not empty, login validation loginService checkToken * 4.If the certification is released successfully */ if (!(handler instanceof HandlerMethod)) { //Simply put, Handler is one of the methods in the controller //handle may be a RequestResourceHandle springboot program accessing static resources Default de-classpath static directory query return true; } Got to get it TokenοΌWhy, because when the front end comes in, we use@RequestHeader("Authorization") Incoming String token = request.getHeader("Authorization"); //Log problem, need to import @slf4 under lombok log.info("=============request start================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}",token); log.info("=============request end==================="); /*token Empty, intercept*/ if(StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg()); response.setContentType("application/json;charset=utf8"); response.getWriter().print(JSON.toJSONString(result)); //Return json information (fastjson transforms) return false; } /*User empty, intercept*/ SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ //Here is the information returned by the error Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg()); //To tell the browser that we want to return this type response.setContentType("application/json;charset=utf8"); //What's returned is a result type, only converting to JSON type response.getWriter().print(JSON.toJSONString(result)); return false; } //Logon validation successful, release return true; } }
12.2 Configure Interception Path
Configure Intercept Path in WebMvcConfig
//Interceptor Injection @Autowired private LoginInterceptor loginInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { //Configure Intercept Interface // All interfaces except login registration // registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/register"); registry.addInterceptor(loginInterceptor) .addPathPatterns("/test"); }
13. ThreadLocal saves user information
Want to controller How to get user information directly from?
What is ThreadLocal
I. ThreadLocal What's this From the name we can see ThreadLocal Called thread variable, which means ThreadLocal The variable populated in belongs to the current thread and is isolated from other threads. ThreadLocal A copy of the variable is created in each thread, so each thread can access its own internal copy variable. It's easy to understand literally, but it's not so easy from a practical point of view. As a point of common interview questions, there are plenty of scenarios to use: 1,When passing objects across layers, use ThreadLocal Multiple passes can be avoided, breaking the constraints between levels. 2,Data isolation between threads 3,Perform transactional operations to store thread transaction information. 4,Database connection, Session Session management.
13.1 Use ThreadLocal
Now create the UserThreadLocal class under the utils package
package com.lum.blog.utils; import com.lum.blog.dao.pojo.SysUser; public class UserThreadLocal { //This sentence means declare private private UserThreadLocal (){ } //Instantiate a ThreadLocal class, that is, enable 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(); } }
Now that you are saving user information
Modifications to LoginIntercept, which are added here since they are validated here
package com.lum.blog.handler; import com.alibaba.fastjson.JSON; import com.lum.blog.dao.pojo.SysUser; import com.lum.blog.service.LoginService; import com.lum.blog.utils.UserThreadLocal; import com.lum.blog.vo.ErrorCode; import com.lum.blog.vo.Result; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Autowired private LoginService loginService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //Execute before executing the Controller method (Handle) // What does prerepresent before /** * 1.You need to decide if the requested interface path is a HandleMethod(controller method) * 2.Determine if token is empty and not logged in * 3.Not empty, login validation loginService checkToken * 4.If the certification is released successfully */ if (!(handler instanceof HandlerMethod)) { //Simply put, Handler is one of the methods in the controller //handle may be a RequestResourceHandle springboot program accessing static resources Default de-classpath static directory query return true; } Got to get it TokenοΌWhy, because when the front end comes in, we use@RequestHeader("Authorization") Incoming String token = request.getHeader("Authorization"); log.info("=============request start================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}",token); log.info("=============request end==================="); /*token Empty, intercept*/ if(StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg()); response.setContentType("application/json;charset=utf8"); response.getWriter().print(JSON.toJSONString(result)); //Return json information (fastjson transforms) return false; } /*User empty, intercept*/ SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ //Here is the information returned by the error Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg()); //To tell the browser that we want to return this type response.setContentType("application/json;charset=utf8"); //What's returned is a result type, only converting to JSON type response.getWriter().print(JSON.toJSONString(result)); return false; } //Logon validation successful, release //Get user information directly in the controller UserThreadLocal.put(sysUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //If you don't delete information from ThreadLocal, you risk internal disclosure UserThreadLocal.remove(); } }
Remember to clear the information at the end to avoid memory leaks
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //If you don't delete information from ThreadLocal, you risk internal disclosure UserThreadLocal.remove(); }
13.2 Testing
Testing in TsetCroller
package com.lum.blog.controller; import com.lum.blog.dao.pojo.SysUser; import com.lum.blog.utils.UserThreadLocal; import com.lum.blog.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { /** * Used to test interceptors */ @RequestMapping public Result test(){ SysUser sysUser = UserThreadLocal.get(); System.out.println(sysUser); return Result.success(null); } }
What exactly does 13.3ThreadLocal (local thread) do?
- Let's say that, like one of our requests, when you start a process and you let it bind to your process, it will be deeply tied together (for the purpose of binding user information).
- Why delete it after that because once a memory leak is serious
What you need to know is that a thread can have multiple threads ThreadLocal Every last Thread Maintain one ThreadLocalMap, key For use**Weak reference**Of ThreadLocal Example, value Is a copy of the thread variable. **Strong Reference**οΌWith the most common reference, an object has a strong reference and will not be recycled by the garbage collector.When memory Not enough space, Java Virtual machines prefer to throw OutOfMemoryError Error, causes the program to terminate abnormally, and does not recycle this Species object. **If you want to disassociate a strong reference from an object, you can explicitly assign a reference to nullοΌThis allows JVM The object is recycled at the appropriate time.** **Weak reference**οΌJVM When garbage collection occurs, objects associated with weak references are recycled regardless of whether there is enough memory. stay java In the java.lang.ref.WeakReference Class to represent. The one above key For use**Weak reference**Of ThreadLocal Instance, when the one in our thread ThreadLocal Was this a weak reference after being killed by the garbage collection mechanism? Key No more, but this is Map Collection, Value Will always exist, all to be deleted manually
14. Article Details
14.1 Interface Description
Interface url:/article/view/(id)
Request method: POST
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
id | long | Article ID (path parameter) |
Data returned
{ "success": true, "code": 200, "msg": "success", "data": "token" }
14.2 Tables and corresponding POJOs
CREATE TABLE `blog`.`ms_article_body` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, #This is the content of the article `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, #Article Content Page `content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `article_id` bigint(0) NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `article_id`(`article_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
ArticleBody
package com.lum.blog.dao.pojo; import lombok.Data; @Data public class ArticleBody { private Long id; private String content; private String contentHtml; private Long articleId; }
Category table
//Category table CREATE TABLE `blog`.`ms_category` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, #Classified icons `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, #Name of category Icon `category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
Category
package com.lum.blog.dao.pojo; import lombok.Data; @Data public class Category { private Long id; private String avatar; private String categoryName; private String description; }
14.3Controller
Add a method to find articles in ArticleConller
/** * View article details * @param articleId * @return */ @PostMapping("view/{id}") public Result findArticleById(@PathVariable("id") Long articleId){ return articleService.findArticleById(articleId); }
14.3 Service
What you need to add ArticleBodyVo and CategoryCo to articleVo to return the article
package com.lum.blog.vo; import lombok.Data; import java.util.List; @Data public class ArticleVo { private Long id; private String title; private String summary; //brief introduction private int commentCounts; private int ViewCounts; private int weight; //Set Top private String createDate; //Creation Time private String author; private ArticleBodyVo body; private List<TagVo> tags; private CategoryVo category; }
Add Find Article Method Interface to articleService
/** * Query article details * @param articleId * @return */ Result findArticleById(Long articleId);
The following are all in ArticleServiceImpl Written in
Implement method in articleServiceImpl
/*Article Details*/ @Override public Result findArticleById(Long articleId) { /** * 1.Query article information based on id * 2.Make Association queries based on bodyId and categoryId */ Article article = this.articleMapper.selectById(articleId); ArticleVo articleVo = copy(article, true, true,true,true); return Result.success(articleVo); }
/*Article Body Display*/ private ArticleBodyVo findArticleBodyById(Long bodyId) { ArticleBody articleBody = articleBodyMapper.selectById(bodyId); ArticleBodyVo articleBodyVo = new ArticleBodyVo(); articleBodyVo.setContent(articleBody.getContent()); return articleBodyVo; }
Find details based on the article if you want to show part of the article
ArticleVo is required to display content
Overloaded cpoyList can display different article content based on parameters
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; }
Article articleVo shows copyList overloaded copy method
private ArticleVo copy(Article article,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory) { ArticleVo articleVo = new ArticleVo(); BeanUtils.copyProperties(article,articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); if (isTag) { Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); } if (isAuthor) { Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } if(isBody){ Long bodyId = article.getBodyId(); articleVo.setBody(findArticleBodyById(bodyId)); } if(isCategory){ Long categoryId = article.getCategoryId(); articleVo.setCategory(categoryService.findCategoryById(categoryId)); } return articleVo; }
Don't forget to inject relationships
@Autowired private ArticleMapper articleMapper; @Autowired private TagService tagService; @Autowired private SysUserService sysUserService; @Autowired private CategoryService categoryService; @Autowired private ArticleBodyMapper articleBodyMapper;
You need to add articleBodyMapper to get the body
package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.ArticleBody; public interface ArticleBodyMapper extends BaseMapper<ArticleBody> { }
14.4 Testing
15. Update of reading numbers
Use of thread pools
Question introduction
@Override public ArticleVo findArticleById(Long id) { Article article = articleMapper.selectById(id); //Having checked the articles and added reading numbers, is there any problem? //The answer is yes, it should have returned the data directly. At this time, an update operation was made, adding write lock to update the time, blocking other reading operations, and the new energy will be lower. //And the update increases the time-consuming of this interface, once the update has a problem, it can not affect our other things such as: look at the article or what //So how to optimize it?,-->All think of thread pools //You can throw the update operation into the thread pool without affecting it or having nothing to do with the main thread return copy(article,true,true,true,true); }
Configuration of Thread Pool
Create a new ThreadPoolConfig under com.lum.blog.config to open the thread pool and configure it accordingly
package com.lum.blog.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; /** * @author lum * @date 2021/9/3 */ @Configuration @EnableAsync //Open multithreading public class ThreadPoolConfig { @Bean("taskExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // Set Number of Thread Cores executor.setCorePoolSize(5); // Set maximum number of threads executor.setMaxPoolSize(20); // Configure Queue Size executor.setQueueCapacity(Integer.MAX_VALUE); // Set thread active time executor.setKeepAliveSeconds(60); // Set Thread Name executor.setThreadNamePrefix("Lum Blog"); //Wait for all tasks and close threads after completion executor.setWaitForTasksToCompleteOnShutdown(true); //Perform Initialization executor.initialize(); return executor; } }
15.1 Controller
Article business, no controller
15.2 Service
Create a new ThreadService under com.lum.blog.service.Impl to complete the use of the thread pool
package com.lum.blog.service.Impl; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.lum.blog.dao.mapper.ArticleMapper; import com.lum.blog.dao.pojo.Article; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; /** * @author lum * @date 2021/9/3 */ @Component public class ThreadService { //Expect this operation to be performed in the thread pool without affecting the main thread @Async("taskExecutor") public void updateArticleViewCount(ArticleMapper articleMapper, Article article) { int viewCounts = article.getViewCounts(); Article articleUpdate = new Article(); articleUpdate.setViewCounts(viewCounts+1); LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Article::getId,article.getId()); // Set a thread security for multithreaded conditions updateWrapper.eq(Article::getViewCounts,viewCounts); // update article set view_count = 100 where view_count =99 and id = 1 articleMapper.update(articleUpdate, updateWrapper); try { Thread.sleep(2000); System.out.println("Update complete!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
This is a bug that will occur
Update the number of views by updating the number of comments to 0
This is because in Article's pojo relational mapping to the database, the default value of the int base type is 0, which affects the sql statement
So don't use basic types in pojo
package com.lum.blog.dao.pojo; import lombok.Data; /* Blog Management */ @Data public class Article { public static final Integer Article_TOP = 1; public static final Integer Article_Common = 0; private Long id; private String title; private String summary; private Integer commentCounts; private Integer viewCounts; /** * Author id */ private Long authorId; /** * Content id */ private Long bodyId; /** *Category id */ private Long categoryId; /** * Set Top */ private Integer weight; /** * Creation Time */ private Long createDate; }
16 Comments List
16.1 Interface Description
Interface url:/comments/article/(id)
Request method: GET
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
id | long | Article ID (path parameter) |
Data returned
{ "success": true, "code": 200, "msg": "success", "data": [ { "id":12, "author":{ "nickname":"Zhao Yun", "avatar":"", "id":1 }, "content":"111" "childrens":[], "createDate":"2021-9-1 08:35", "level":2, "toUser":{ "id":12, "nickname":"Zhao Yun", "id":1 } } ], "createDate":"2021-9-1 08:35", "level":1, "toUser":null } ] }
New Comment mapping relationship under pojo package
package com.lum.blog.dao.pojo; import lombok.Data; /** * @author lum * @date 2021/9/3 */ @Data public class Comment { private Long id; private String content; private Long createDate; private Long articleId; private Long authorId; private Long parentId; private Long toUid; private Integer level; }
Create two Vo s to display
CommentVo
package com.lum.blog.vo; import lombok.Data; import java.util.List; /** * @author lum * @date 2021/9/3 */ @Data public class CommentVo { private Long id; private UserVo author; private String content; private List<CommentVo> childrens; private String createDate; private Integer level; private UserVo toUser; }
UserVo
package com.lum.blog.vo; import lombok.Data; /** * @author lum * @date 2021/9/3 */ @Data public class UserVo { private String nickname; private String avatar; private Long id; }
16.2 Controller
Create CommentController
package com.lum.blog.controller; import com.lum.blog.service.CommentsService; import com.lum.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @author lum */ @RestController @RequestMapping("comments") public class CommentController { @Autowired private CommentsService commentsService; @GetMapping("article/{id}") public Result findArticleById(@PathVariable("id") Long articleId){ return commentsService.commentsByArticleId(articleId); } }
16.3 Service
Create Interface CommentService
package com.lum.blog.service; import com.lum.blog.vo.Result; /** * @author lum * @date 2021/9/3 */ public interface CommentsService { /** * Find comments based on article id * @param articleId * @return */ Result commentsByArticleId(Long articleId); }
CommentServiceImpl Implementation Method
package com.lum.blog.service.Impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.lum.blog.dao.mapper.CommentMapper; import com.lum.blog.dao.pojo.Comment; import com.lum.blog.service.CommentsService; import com.lum.blog.service.SysUserService; import com.lum.blog.vo.CommentVo; import com.lum.blog.vo.Result; import com.lum.blog.vo.UserVo; import org.joda.time.DateTime; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * @author lum * @date 2021/9/3 */ @Service public class CommentsServiceImpl implements CommentsService { @Autowired private CommentMapper commentMapper; @Autowired private SysUserService sysUserService; @Override public Result commentsByArticleId(Long articleId) { /* 1.Query the comment list based on article id and from comment 2.Query author information based on author id 3.If level=1, query for subcomments,\ 4.If there is a query based on the comment id */ LambdaQueryWrapper<Comment> queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(Comment::getArticleId,articleId); queryWrapper.eq(Comment::getLevel,1); List<Comment> comments = commentMapper.selectList(queryWrapper); List<CommentVo> commentVoList= copyList(comments); return Result.success(commentVoList); } private List<CommentVo> copyList(List<Comment> comments) { List<CommentVo> commentVoList = new ArrayList<>(); for (Comment comment : comments) { commentVoList.add(copy(comment)); } return commentVoList; } private CommentVo copy(Comment comment) { CommentVo commentVo = new CommentVo(); //copy the same type into commentVo BeanUtils.copyProperties(comment, commentVo); //Time Formatting commentVo.setCreateDate(new DateTime(comment.getCreateDate()).toString("yyyy-MM-dd HH:mm")); //Author Information Long authorId = comment.getAuthorId(); UserVo userVo = this.sysUserService.findUserVoById(authorId); commentVo.setAuthor(userVo); //Sub-comments Integer level = comment.getLevel(); if (level == 1){ Long id = comment.getId(); List<CommentVo> commentVoList = findCommentByParentId(id); commentVo.setChildrens(commentVoList); } //To whom does toUser comment if (level > 1) { Long toUid = comment.getToUid(); UserVo toUserVo = this.sysUserService.findUserVoById(toUid); commentVo.setToUser(toUserVo); } return commentVo; } //Subcomment Query private List<CommentVo> findCommentByParentId(Long id) { LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Comment::getParentId,id); queryWrapper.eq(Comment::getLevel,2); return copyList(commentMapper.selectList(queryWrapper)); } }
The findUserVoById(Long id) method needs to be added to SysUserServiceImpl
@Override public UserVo findUserVoById(Long id) { SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null) { sysUser = new SysUser(); sysUser.setId(1L); sysUser.setAvatar("/static/img/logo.b3a48c0.png"); sysUser.setNickname("empty"); } UserVo userVo = new UserVo(); BeanUtils.copyProperties(sysUser,userVo); return userVo; }
Testing
17. Comment function
17.1 Interface Description
Interface url:/comments/create/change
Request method: POST
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
articleid | long | Article id |
content | lstring | Comments |
parent | long | Parent comment id |
toUserid | long | Commented user id |
Create CommentParam
package com.lum.blog.vo.params; /** * @author lum * @date 2021/9/3 */ @Data public class CommentParam { private Long articleId; private String content; private Long parentId; private Long toUserId; }
17.2 Need to join the login interceptor
Sign in to comment
WebMvcConfig
@Override public void addInterceptors(InterceptorRegistry registry) { //Configure Intercept Interface // All interfaces except login registration // registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/register"); registry.addInterceptor(loginInterceptor) .addPathPatterns("/comments/create/change") .addPathPatterns("/test"); }
Json serialization is required because Long front-end resolution of Distributed IDS is too long and will change the value
CommentVo
@JsonSerialize(using = ToStringSerializer.class) private Long id;
17.3 Controller
Add interface in commentController
@PostMapping("create/change") public Result comment(@RequestBody CommentParam commentParam){ return commentsService.comment(commentParam); }
14.4 Service
CommentService
/** * comment * @param commentParam * @return */ Result comment(CommentParam commentParam);
Implementation Method CommentServiceImpl
//Comment @Override public Result comment(CommentParam commentParam) { SysUser sysUser = UserThreadLocal.get(); Comment comment = new Comment(); comment.setArticleId(commentParam.getArticleId()); comment.setAuthorId(sysUser.getId()); comment.setContent(commentParam.getContent()); comment.setCreateDate(System.currentTimeMillis()); Long parent = commentParam.getParentId(); //If parent id is empty, parent comment, otherwise child comment if (parent == null || parent == 0) { comment.setLevel(1); } else { comment.setLevel(2); } comment.setParentId(parent == null ? 0 : parent); Long toUserId = commentParam.getToUserId(); comment.setToUid(toUserId == null ? 0 : toUserId); return Result.success(null); }
18. Writing articles
Three interfaces are required:
- Get all article categories
- Get all tags
- Publish articles
18.1 All Article Categories
Interface Description
Interface url:/categorys
Request method: GET
Request parameters:
|Parameter Name|Parameter Type|Description|
Controller
CategoryController
//categroys @GetMapping public Result categories(){ return categoryService.findAll(); }
Service
CategroyService
//Find Categories Result findAll();
CategoryServiceImpl
@Override public Result findAll() { LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); //queryWrapper.select(Category::getId,Category::getCategoryName); List<Category> categories = categoryMapper.selectList(queryWrapper); //Objects for page interaction return Result.success(copyList(categories)); } public CategoryVo copy(Category category){ CategoryVo categoryVo = new CategoryVo(); BeanUtils.copyProperties(category,categoryVo); //categoryVo.setId(String.valueOf(category.getId())); return categoryVo; } public List<CategoryVo> copyList(List<Category> categoryList){ List<CategoryVo> categoryVoList = new ArrayList<>(); for (Category category : categoryList) { categoryVoList.add(copy(category)); } return categoryVoList; }
18.2 All article labels
Interface Description
Interface url:/tags
Request method: GET
Request parameters:
|Parameter Name|Parameter Type|Description|
Controller
TagsController
/** * All article labels * @return */ @GetMapping public Result findAll() { return tagService.findAll(); }
TagService
/** * Query all article labels * @return */ Result findAll();
TagsServiceImpl
@Override public Result findAll() { List<Tag> tags = this.tagMapper.selectList(new LambdaQueryWrapper<>()); return Result.success(copyList(tags)); }
19. Publish articles
Interface Description
Interface url:/articles/publish
Request method: POST
Request parameters:
Parameter Name | Parameter type | Explain |
---|---|---|
title | string | Article Title |
id | long | Article ID (edited with value) |
body | object | Article Content |
category | json | Article Categories |
summary | string | Article Overview |
tags | json | Article Label |
Create ArticleParam
@Data public class ArticleParam { private Long id; private ArticleBodyParam body; private CategoryVo category; private String summary; private List<TagVo> tags; private String title; }
Create ArticBodyParam
@Data public class ArticleBodyParam { private String content; private String contentHtml; }
Create article and tag associated table objects under pojo
@Data public class ArticleTag { private Long id; private Long articleId; private Long tagId;
New ArticleTagMapper Interface
package com.lum.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lum.blog.dao.pojo.ArticleTag; /** * @author lum * @date 2021/9/4 */ public interface ArticleTagMapper extends BaseMapper<ArticleTag> { }
Serialize the id in ArticleVo id to prevent loss of previous segment resolution
@JsonSerialize(using = ToStringSerializer.class) private Long id;
ArticleController
@PostMapping("publish") public Result publish(@RequestBody ArticleParam articleParam){ return articleService.publish(articleParam); }
ArticleService
/** * Article Publishing * @param articleParam * @return */ Result publish(ArticleParam articleParam);
ArticleServiceImpl
@Autowired private ArticleTagMapper articleTagMapper; /** * 1.Publishing Articles for Article Objects * 2. Author id current logon user * 3. Tags Add tags to the associated table * 4. body Content Store article bodyId * @param articleParam * @return * This interface will be added to login interception */ @Override public Result publish(ArticleParam articleParam) { SysUser sysUser = UserThreadLocal.get(); Article article = new Article(); article.setAuthorId(sysUser.getId()); article.setWeight(Article.Article_Common); article.setViewCounts(0); article.setTitle(articleParam.getTitle()); article.setSummary(articleParam.getSummary()); article.setCommentCounts(0); article.setCreateDate(System.currentTimeMillis()); article.setCategoryId((articleParam.getCategory().getId())); //An article id is generated after insertion this.articleMapper.insert(article); //tag List<TagVo> tags = articleParam.getTags(); if (tags != null){ for (TagVo tag : tags) { Long articleId = article.getId(); ArticleTag articleTag = new ArticleTag(); articleTag.setTagId((tag.getId())); articleTag.setArticleId(articleId); articleTagMapper.insert(articleTag); } } //body ArticleBody articleBody = new ArticleBody(); articleBody.setArticleId(article.getId()); articleBody.setContent(articleParam.getBody().getContent()); articleBody.setContentHtml(articleParam.getBody().getContentHtml()); articleBodyMapper.insert(articleBody); article.setBodyId(articleBody.getId()); articleMapper.updateById(article); //Convert id to string and put in map Map<String,String> map = new HashMap<>(); map.put("id",article.getId().toString()); return Result.success(map); }