Article catalogue
- Description of spring boot training project
- Basic knowledge
- Interview preparation
- 1. Project construction
- 2. Home page - article list
- 3. Home page - Hottest label
- 4.1. Unified exception handling
- 4.2. Home page - Hottest article
- 4.3. Home page - latest articles
- 4.4. Home page - article archiving
- 5.1. Sign in
- 5.2. Get user information
- 5.3. Log out
- 6.1. register
- 6.2. Login interceptor
- 6.3. ThreadLocal saves user information
- 7.1. ThreadLocal memory leak
- 7.2. Article details
- 7.3. Number of reads updated using thread pool
- Bug correction
- 8.1. Comment list
- 8.2. comment
- write an article
- 9.1. All article categories
- 9.2. All article Tags
- 9.3. Publish articles
- 9.4. AOP log
- bug fix
- 10.1. Article image upload
- 10.2. Navigation - article classification
- 10.3. Classified article list
- 10.4. Tag article list
- 11.1. List of archived articles
- 11.2. Unified cache processing (optimized)
- 11.3. Think about other optimizations
- Management background
- 12.1. Construction project
- 12.2. Authority management
- 12.3. Security integration
- 12.4. task
- 13. Summarize technical highlights
- 14. Front end
Description of spring boot training project
The blog used by code God Road website has simple projects, clear needs and easy to use. It is very suitable for hand training projects.
Final product
Project explanation:
- Provide front-end engineering, and only need to implement the back-end interface
- The project starts with a single architecture, develops rapidly first, does not consider project optimization, and reduces the development burden
- After the development, start to optimize the project and improve the programming thinking ability
- Such as page static, cache, cloud storage, log, etc
- docker deployment Online
- ECS purchase, domain name purchase, domain name filing, etc
Technology used in the project:
springboot + mybatisplus+redis+mysql
Basic knowledge
Introduction to mybatisDao layer Mapper layer controller layer service layer model layer entity layer
Mall shopping mall learning document
mybatisplus learning documentation
mybatisplus supporting code
easycode with mybatisplus
It is recommended to install plug-ins
Use @ Data
View article code structure
Code written by automatic prompt
Quickly generate xml files
Distinguishing brackets
vscode plugin
Interview preparation
springboot personal blog project interview preparation
1. Project construction
Front end Engineering:
npm install npm run build npm run dev
1.1 new maven project
<?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.mszlu</groupId> <artifactId>blog-parent</artifactId> <version>1.0-SNAPSHOT</version> <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</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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Delete src file
1.1.2 bug s encountered
How maven child projects reference parent projects
1.2 configuration
#server server.port= 8888 spring.application.name=mszlu_blog # datasource 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 #mybatis-plus mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.global-config.db-config.table-prefix=ms_ package com.mszlu.blog.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration //Scan the package, generate the proxy implementation class from the interface under this package, and register it in the spring container @MapperScan("com.mszlu.blog.dao") public class MybatisPlusConfig { //Paging plug-in @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } } package com.mszlu.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 WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { //Cross domain configuration, which cannot be set to *, is unsafe, the front and rear end are separated, and the domain name may be inconsistent //Inconsistent local test ports are also considered cross domain registry.addMapping("/**").allowedOrigins("http://localhost:8080"); } }
1.3 startup
package com.mszlu.blog; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BlogApp { public static void main(String[] args) { SpringApplication.run(BlogApp.class,args); } }
2. Home page - article list
2.1 interface description
Interface url: / articles
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
page
int
Current number of pages
pageSize
int
Quantity displayed per page
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", "summary": "adopt Spring Boot The realized service only needs to rely on one Java Class, package it into jar,And pass`java -jar`The command can be run. Compared with tradition Spring In terms of application, it has become very light and simple.", "commentCounts": 2, "viewCounts": 54, "weight": 1, "createDate": "2609-06-26 15:58", "author": "12", "body": null, "tags": [ { "id": 5, "avatar": null, "tagName": "444" }, { "id": 7, "avatar": null, "tagName": "22" }, { "id": 8, "avatar": null, "tagName": "11" } ], "categorys": null }, { "id": 9, "title": "Vue.js What is it?", "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 in a 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 } ] }
2.2 coding
2.2.0 Spring - annotation based development
Spring annotation based development
Role of each annotation
2.2.1 table structure
Article table
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 views', `weight` int(0) NOT NULL COMMENT 'Top or not', `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;
Label table
id, article id, tag id. the tag id can be found indirectly through the article id
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 table
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 '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', `email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'mailbox', `last_login` bigint(0) NULL DEFAULT NULL COMMENT 'Last login 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 'Encryption 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;
entity layer
Layer: model, alias: domain
Purpose: the entity layer is used to store our entity classes, which are basically consistent with the attribute values in the database, and realize the methods of set and get.
Example: the user entity in the user table
article
package com.mszlu.blog.dao.pojo; import lombok.Data; @Data public class Article { public static final int Article_TOP = 1; public static final int Article_Common = 0; private Long id; private String title; private String summary; private int commentCounts; private int viewCounts; /** * Author id */ private Long authorId; /** * Content id */ private Long bodyId; /** *Category id */ private Long categoryId; /** * Topping */ private int weight = Article_Common; /** * Creation time */ private Long createDate; }
user
package com.mszlu.blog.dao.pojo; import lombok.Data; @Data public class SysUser { private Long id; private String account; private Integer admin; private String avatar; private Long createDate; private Integer deleted; private String email; private Long lastLogin; private String mobilePhoneNumber; private String nickname; private String password; private String salt; private String status; }
label
package com.mszlu.blog.dao.pojo; import lombok.Data; @Data public class Tag { private Long id; private String avatar; private String tagName; }
2.2.2 Controller
Controller layer. The controller is imported into the service layer because the methods in the service are used by us. The controller performs business operations by receiving the parameters transmitted from the front end and returns a specified path or data table
package com.mszlu.blog.api; import com.mszlu.blog.dao.pojo.Article; import com.mszlu.blog.service.ArticleService; import com.mszlu.blog.vo.Archive; import com.mszlu.blog.vo.ArticleVo; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.PageParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("articles") public class ArticleController { @Autowired private ArticleService articleService; //Result is the unified result return @PostMapping public Result articles(@RequestBody PageParams pageParams) { //ArticleVo page received data List<ArticleVo> articles = articleService.listArticlesPage(pageParams); return Result.success(articles); } }
Unify the final results
package com.mszlu.blog.vo; import com.mszlu.blog.dao.pojo.Article; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @AllArgsConstructor @NoArgsConstructor public class Result { private boolean success; private Integer code; private String msg; private Object data; public static Result success(Object data) { return new Result(true,200,"success",data); } public static Result fail(Integer code, String msg) { return new Result(false,code,msg,null); } }
Create a Vo file that interacts with the front end
package com.mszlu.blog.vo; import com.mszlu.blog.dao.pojo.ArticleBody; import com.mszlu.blog.dao.pojo.Category; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.dao.pojo.Tag; import lombok.Data; import java.util.List; @Data public class ArticleVo { private Long id; private String title; private String summary; private int commentCounts; private int viewCounts; private int weight; /** * Creation time */ private String createDate; private String author; private ArticleBodyVo body; private List<TagVo> tags; private List<CategoryVo> categorys; }
2.2.3 Service
The service layer mainly writes business logic methods. The service layer often calls the methods of dao layer (also known as mapper layer) to add, delete, modify and query data*
2.2.3.0 solving mapper red explosion
Establish service interface
package com.mszlu.blog.service; import com.mszlu.blog.vo.Archive; import com.mszlu.blog.vo.ArticleVo; import com.mszlu.blog.vo.params.PageParams; import java.util.List; public interface ArticleService { /** * Paging query article list * @param pageParams * @return */ List<ArticleVo> listArticlesPage(PageParams pageParams); }
Establish the implementation class of service interface
package com.mszlu.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.mszlu.blog.dao.ArticleMapper; import com.mszlu.blog.dao.SysUserMapper; import com.mszlu.blog.dao.pojo.Article; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.dao.pojo.Tag; import com.mszlu.blog.service.ArticleService; import com.mszlu.blog.service.SysUserService; import com.mszlu.blog.service.TagsService; import com.mszlu.blog.vo.ArticleBodyVo; import com.mszlu.blog.vo.ArticleVo; import com.mszlu.blog.vo.TagVo; import com.mszlu.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; @Autowired private TagService tagService; @Autowired private SysUserService sysUserService; @Override public Result listArticle(PageParams pageParams) { /** * 1,Paging query of article database table */ Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize()); LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); //Whether to sort by placing it at the top or not, / / arrange it in reverse chronological order, which is equivalent to order by create_data desc queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); //Paging query usage https://blog.csdn.net/weixin_41010294/article/details/105726879 List<Article> records = articlePage.getRecords(); // To return the vo data defined by us is the corresponding front-end data. We should not only return the current data, but need further processing List<ArticleVo> articleVoList =copyList(records,true,true); return Result.success(articleVoList); } private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor)); } return articleVoList; } //"The function of eop is to correspond to the copyList. The copy between sets is decomposed into the copy between set elements private ArticleVo copy(Article article,boolean isTag,boolean isAuthor){ ArticleVo articleVo = new ArticleVo(); //BeanUtils.copyProperties usage https://blog.csdn.net/Mr_linjw/article/details/50236279 BeanUtils.copyProperties(article, articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); //Not all interfaces require tags and author information if(isTag){ Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); } if (isAuthor) { //Get the author id Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } return articleVo; } }
Establish user's service interface
package com.mszlu.blog.service; import com.mszlu.blog.dao.pojo.SysUser; public interface UserService { SysUser findUserById(Long userId); }
Establish user's service interface implementation class
package com.mszlu.blog.service.impl; import com.mszlu.blog.dao.SysUserMapper; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.service.UserService; 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) { //Query by id //To prevent sysUser from being empty, a judgment is added SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null){ sysUser = new SysUser(); sysUser.setNickname("Code God's Road"); } return sysUser; } }
2.2.3.1mybatisplus how to deal with multi table query
This problem is encountered in the establishment of TagMapper. The method is to create an xml file for reading and writing after the establishment of TagMapper
Put the xml file in the resource folder
Folder name and xml file name must be the same as tagmapper Java folder consistency
2.2.3.2 pits encountered when creating folders
When using IntelliJ IDEA to create a multi-level folder, the folder name is com immer. monitor. Both persistence and com/immer/monitor/persistence will be displayed as shown in the figure below
But the actual structure is quite different immer. monitor. Persistence is a single folder and com/immer/monitor/persistence is a nested folder
It will cause the problem that the resource file is not found, and it is difficult to find it
The problem that the folder path cannot be found will not be displayed, because we need to ensure that mapper XML should be consistent with the folder and path of mapper
How to create multi-level folders
Establish the service interface of the tag
package com.mszlu.blog.service; import com.mszlu.blog.dao.pojo.Tag; import com.mszlu.blog.vo.TagVo; import java.util.List; public interface TagsService { List<TagVo> findTagsByArticleId(Long id); }
Establish the implementation class of tag's service interface
package com.mszlu.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.dao.TagMapper; import com.mszlu.blog.dao.pojo.Tag; import com.mszlu.blog.service.TagsService; import com.mszlu.blog.vo.TagVo; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @Service public class TagsServiceImpl implements TagsService { @Autowired private TagMapper tagMapper; /** * ms_article_tag Is the association table of articles and labels * ms_tag Table for simple labels * @param articleId * @return */ @Override public List<TagVo> findTagsByArticleId(Long articleId) { //mybatisplus cannot perform multi table query List<Tag> tags = tagMapper.findTagsByArticleId(articleId); return copyList(tags); } private List<TagVo> copyList(List<Tag> tagList) { List<TagVo> tagVoList = new ArrayList<>(); for (Tag tag : tagList) { tagVoList.add(copy(tag)); } return tagVoList; } private TagVo copy(Tag tag) { TagVo tagVo = new TagVo(); BeanUtils.copyProperties(tag, tagVo); return tagVo; } }
2.2.4 Dao layer
Mapper layer = dao layer. Now the mapper layer generated by reverse engineering with mybatis is actually dao layer.
dao layer carries out data persistence operation on the database. Its method statement is directly aimed at the database operation, while the service layer is aimed at our controller, that is, our users. The impl of service is a file that integrates mapper and service.
Relationship between dao layer and service layer: the service layer often calls dao layer methods to add, delete, modify and query data. In real development, business operations will involve data operations, while data operations often use databases, so the service layer will often call dao layer methods.
dao layer of the article
Since we directly inherit the BaseMapper of mybatisplus, we don't need to write basic additions, deletions, modifications and queries.
package com.mszlu.blog.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.Article; import com.mszlu.blog.vo.ArticleVo; import java.util.List; public interface ArticleMapper extends BaseMapper<Article> { }
dao layer of label
package com.mszlu.blog.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.Tag; import java.util.List; public interface TagMapper extends BaseMapper<Tag> { List<Tag> findTagsByArticleId(Long articleId); }
Author's dao layer
package com.mszlu.blog.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.SysUser; public interface SysUserMapper extends BaseMapper<SysUser> { }
This is what to do when mybatisplus encounters multi table query mentioned above. We need to establish our own xml file for joint query operation
In the mapper file
Using mapperX plug-in, we can create
Command with method name select
<select id="findTagsByArticleId" resultType="com.mszlu.blog.dao.pojo.Tag"></select>
The total xml file is as follows
<?xml version="1.0" encoding="UTF-8" ?> <!--MyBatis configuration file--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mszlu.blog.dao.mapper.TagMapper"> <sql id="all"> id,avatar,tag_name as tagName </sql> <!-- List<Tag> findTagsByArticleId(Long articleId); In this file, id Represents the method name, parameterType Indicates the name of the input variable, resultType Represents the type of the generic--> <select id="findTagsByArticleId" parameterType="long" resultType="com.mszlu.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>
2.2.5 testing
3. Home page - Hottest label
3.1 interface description
Interface url: / tags/hot
Request method: GET
Request parameters: None
id
Label name
We expect all lists of articles to be displayed by clicking on the tab
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id":1, "tagName":"4444" } ] }
3.2 coding
3.2.1 Controller
package com.mszlu.blog.api; import com.mszlu.blog.service.ArticleService; import com.mszlu.blog.service.TagsService; import com.mszlu.blog.vo.Archive; import com.mszlu.blog.vo.ArticleVo; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.TagVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; //@RestController returns json data on behalf of us, and @ RequestMapping("tags") represents path mapping @RestController @RequestMapping("tags") public class TagsController { @Autowired private TagService tagService; // Path tags/hot @GetMapping("hot") public Result hot(){ int limit =6; return tagService.hots(limit); } }
vo represents the data that the back end interacts with the front end
package com.mszlu.blog.vo; import lombok.Data; @Data public class TagVo { private Long id; private String tagName; }
3.2.2 Service
Establish service interface
package com.mszlu.blog.service; import com.mszlu.blog.vo.TagVo; import java.util.List; public interface TagsService { Result hots(int limit); }
Create serviceimpl implementation class
package com.mszlu.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.dao.TagMapper; import com.mszlu.blog.dao.pojo.Tag; import com.mszlu.blog.service.TagsService; import com.mszlu.blog.vo.TagVo; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @Service public class TagsServiceImpl implements TagsService { @Autowired private TagMapper tagMapper; @Override public Result hots(int limit) { /** * The hottest label is the label ms_article_tag in tag_id is our hottest tag with the largest number of sorting * 1,Tags have the largest number of articles, which is the hottest tag * 2,Query by tag_id group count, from large to small, take the first limit */ List<Long> tagIds = tagMapper.findHotsTagIds(limit); //We can't judge if ID (1,3) is empty // CollectionUtils.isEmpty effect https://blog.csdn.net/qq_42848910/article/details/105717235 if(CollectionUtils.isEmpty(tagIds)){ return Result.success(Collections.emptyList()); } //tagId and tagName Tag objects are required //Our sql statement is similar to select * from tag where id in (1,2,3) List<Tag> tagList = tagMapper.findTagsByTagIds(tagIds); return Result.success(tagList); } }
3.2.3 Dao
TagMapper.java
package com.mszlu.blog.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.Tag; import java.util.List; public interface TagMapper extends BaseMapper<Tag> { /** * Query the top n hottest labels * @param limit * @return */ List<Long> findHotsTagIds(int limit); List<Tag> findTagsByTagIds(List<Long> tagIds); }
TagMapper.xml file
Be sure to understand the business logic of all tables and know what value you want to return before operating
We use the method of findHotsTagIds in ms_article_tag found in tag table_ id
Then multi table query, tag_id is Ms_ id in the tag table. We found the first two IDS we want in the findHotsTagIds method, and then used the dynamic mysql method to select the id and tagName options. Writing and reading more can basically become a qualified crud engineer
<?xml version="1.0" encoding="UTF-8" ?> <!--MyBatis configuration file--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mszlu.blog.dao.TagMapper"> <!-- List<Long> findHotsTagIds(int limit);--> <!-- parameterType="int"It was added by ourselves, because the label we entered will not be automatically generated,#{limit} parameters passed for ourselves -- > <!-- GROUP by usage https://www.runoob.com/sql/sql-groupby.html--> <!-- sql The meaning of the sentence is in ms_article_tag Find in table tag_id,according to tag_id Aggregate them together, and then according to count(*)The number of is sorted in descending order, and finally two pieces of data are limited to output--> <select id="findHotsTagIds" parameterType="int" resultType="java.lang.Long"> select tag_id from ms_article_tag GROUP BY tag_id ORDER BY count(*) DESC LIMIT #{limit} </select> <!-- List<Tag> findTagsByTagIds(List<Long> tagIds);Because the type of input is list therefore parameterType The value of is list--> <!-- foreach usage https://www.cnblogs.com/fnlingnzb-learner/p/10566452.html amount to for Loop through the incoming one id Set, each id adopt sql Statement to find the corresponding tag object--> <select id="findTagsByTagIds" parameterType="list" resultType="com.mszlu.blog.dao.pojo.Tag"> select id,tag_name as tagName from ms_tag where id in <foreach collection="tagIds" item="tagId" separator="," open="(" close=")"> #{tagId} </foreach> </select> </mapper>
3.2.4 testing
The hottest label is displayed
4.1. Unified exception handling
Whether it is the controller layer or the service or dao layer, it is possible to report exceptions. If it is an expected exception, it can be directly captured and handled. If it is an unexpected exception, it needs to be handled and recorded uniformly, and give users relatively friendly information.
package com.mszlu.blog.handler; import com.mszlu.blog.vo.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; //Implementation of Aop for intercepting the method annotated with @ controller @ControllerAdvice public class AllExceptionHandler { //Handle exceptions and exceptions Class exception @ExceptionHandler(Exception.class) @ResponseBody //Return json data. If it is not added, it will return to the page public Result doException(Exception ex) { //e.printStackTrace(); Is the stack information of the print exception, indicating the cause of the error, // In fact, when an exception occurs, you usually have to deal with the exception, which is a good habit of programming, so e.printStackTrace() can facilitate you to debug the program! ex.printStackTrace(); return Result.fail(-999,"System abnormality"); } }
4.2. Home page - Hottest article
In MS_ View in article table_ Counts indicates the number of views, and the more, the more popular
4.2.1 interface description
Interface url: / articles/hot
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", }, { "id": 9, "title": "Vue.js What is it?", }, { "id": 10, "title": "Element relevant", } ] }
4.2.2 Controller
ArticleController.java
/** * The hottest article on the home page * @return */ @PostMapping("hot") public Result hotArticle(){ int limit = 5; return articleService.hotArticle(limit); }
4.2.3 Service
src/main/java/com/mszlu/blog/service/ArticleService.java
package com.mszlu.blog.service; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.PageParams; /** * @Author ljm * @Date 2021/10/11 10:30 * @Version 1.0 */ public interface ArticleService { /** * Paging query article list * @param pageParams * @return */ Result listArticle(PageParams pageParams); /** * Hottest article * @param limit * @return */ Result hotArticle(int limit); }
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result hotArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getViewCounts); queryWrapper.select(Article::getId,Article::getTitle); //The word "limit" should be followed by a space. Don't forget to add a space, otherwise the data will be spelled together queryWrapper.last("limit "+limit); //select id,title from article order by view_counts desc limt 5 List<Article> articles = articleMapper.selectList(queryWrapper); //Return vo object return Result.success(copyList(articles,false,false)); }
4.2.4 testing
4.3. Home page - latest articles
It is very similar to the hottest articles. One is selected according to the number of views and the other is selected according to the latest creation time
4.3.1 interface description
Interface url: / articles/new
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "title": "springboot Introduction and introductory cases", }, { "id": 9, "title": "Vue.js What is it?", }, { "id": 10, "title": "Element relevant", } ] }
4.3.1 Controller
/** * Latest articles on home page * @return */ @PostMapping("new") public Result newArticles(){ int limit = 5; return articleService.newArticles(limit); }
4.3.2 Service
src/main/java/com/mszlu/blog/service/ArticleService.java
/** * Latest article * @param limit * @return */ Result newArticles(int limit);
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result newArticles(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 create_date desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles,false,false)); }
4.4. Home page - article archiving
How many articles are published in each article according to the creation time, a year and a month
4.4.1 interface description
Interface url: / articles/listArchives
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "year": "2021", "month": "6", "count": 2 } ] } ? select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article group by year,month
however
p9 up create in the sql given by the master_ Date is bigint 13 bits, but direct year() cannot. You need to change the date type first and then year().
select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month;
So we can find out the results
4.4.1 Controller
src/main/java/com/mszlu/blog/controller/ArticleController.java
/** * Home page article archiving * @return */ @PostMapping("listArchives") public Result listArchives(){ return articleService.listArchives(); }
The following is in Src / main / Java / COM / mszlu / blog / service / impl / articleserviceimpl Return value used in Java
package com.mszlu.blog.dao.dos; import lombok.Data; /** * @Author ljm * @Date 2021/10/12 17:19 * @Version 1.0 * do The object found in the object database does not need to be persisted. Because do is the keyword, an s is added to become dos */ @Data public class Archives { private Integer year; private Integer month; private Long count; }
4.4.2 Service
src/main/java/com/mszlu/blog/service/ArticleService.java
/** * Article archiving * @return */ Result listArchives();
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result listArchives() { /* Article archiving */ List<Archives> archivesList = articleMapper.listArchives(); return Result.success(archivesList); }
4.4.3 Dao
src/main/java/com/mszlu/blog/dao/mapper/ArticleMapper.java
package com.mszlu.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.Article; import java.util.List; import java.util.Map; public interface ArticleMapper extends BaseMapper<Article> { List<Archives> listArchives(); }
src/main/resources/com/mszlu/blog/dao/mapper/ArticleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!--MyBatis configuration file--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--found ArticleMapper.xml Reuse after file mybatisX one touch select sentence--> <mapper namespace="com.mszlu.blog.dao.mapper.ArticleMapper"> <select id="listArchives" resultType="com.mszlu.blog.dao.dos.Archives"> select year(FROM_UNIXTIME(create_date/1000)) as year,month(FROM_UNIXTIME(create_date/1000)) as month, count(*) as count from ms_article group by year,month </select> </mapper>
4.4.4 testing
Note: the front-end project needs to use the app under the data of the day
5.1. Sign in
5.1.1 interface description
Interface url: / login
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
account
string
account number
password
string
password
Return data:
{ "success": true, "code": 200, "msg": "success", "data": "token" }
5.1.2 JWT
Login using JWT technology.
jwt can generate an encrypted token as a user login token, which will be issued to the client after the user logs in successfully.
When requesting a resource or interface that needs to be logged in, the token is carried, and the backend verifies whether the token is legal.
jwt consists of three parts: A.B.C
A: Header, {"type": "JWT", "alg": "HS256"} fixed
B: playload stores information, such as user id, expiration time, etc., which can be decrypted and cannot store sensitive information
C: Visa, A and B are encrypted with the secret key. As long as the secret key is not lost, it can be considered safe.
jwt verification is mainly to verify whether Part C is legal.
Import dependent packages
Dependent packages:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
src/main/java/com/mszlu/blog/utils/JWTUtils.java
Tools:
package com.mszlu.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 = "123456Mszlu!@#$$"; public static String createToken(Long userId){ Map<String,Object> claims = new HashMap<>(); claims.put("userId",userId); JwtBuilder jwtBuilder = Jwts.builder() .signWith(SignatureAlgorithm.HS256, jwtToken) // Issuing algorithm, the secret key is jwtToken .setClaims(claims) // body data should be unique and set by yourself .setIssuedAt(new Date()) // Set issuing time .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// Effective time of day String token = jwtBuilder.compact(); return token; } public static Map<String, Object> checkToken(String token){ try { Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token); return (Map<String, Object>) parse.getBody(); }catch (Exception e){ e.printStackTrace(); } return null; } }
5.1.3 Controller
src/main/java/com/mszlu/blog/controller/LoginController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("login") public class LoginController { @Autowired private LoginService loginService; //@Usage and understanding of requestbody and @ ResponseBody https://blog.csdn.net/zhanglf02/article/details/78470219 //On the usage and difference between @ RequestMapping @ResponseBody and @ RequestBody annotation //https://blog.csdn.net/ff906317011/article/details/78552426?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link //@The RequestBody is mainly used to receive the data in the json string passed from the front end to the back end (the data in the request body); The most common way to use the request body to transfer parameters is undoubtedly the POST request, so when using @ RequestBody to receive data, it is generally submitted in the form of POST. @PostMapping public Result login(@RequestBody LoginParam loginParam){ //Login authentication user access user table return loginService.login(loginParam); } }
Construct LoginParam, that is, our request data
src/main/java/com/mszlu/blog/vo/params/LoginParam.java
package com.mszlu.blog.vo.params; import lombok.Data; /** * @Author ljm * @Date 2021/10/12 20:06 * @Version 1.0 */ @Data public class LoginParam { private String account; private String password; }
5.1.4 Service
src/main/java/com/mszlu/blog/service/LoginService.java
package com.mszlu.blog.service; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; public interface LoginService { /** * Sign in * @param loginParam * @return */ Result login(LoginParam loginParam); }
Import dependent packages
md5 encrypted dependent packages:
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency>
src/main/java/com/mszlu/blog/service/impl/LoginServiceImpl.java
package com.mszlu.blog.service.impl; import com.alibaba.fastjson.JSON; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.service.SysUserService; import com.mszlu.blog.utils.JWTUtils; import com.mszlu.blog.vo.ErrorCode; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; //Spring auto scan component// https://blog.csdn.net/u010002184/article/details/72870065 // @Component – indicates that components are automatically scanned. //@Repository – represents DAO components in the persistence layer. //@Service – represents a service component in the business layer. //@Controller – represents the controller component at the presentation layer. @Service public class LoginServiceImpl implements LoginService { //Encryption salt is used for encryption private static final String slat = "mszlu!@#"; @Autowired private SysUserService sysUserService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public Result login(LoginParam loginParam) { /** * 1. Check whether the parameters are legal * 2. According to the user name and password, go to the user table to query whether it exists * 3. If there is no login failure * 4. If it exists, use jwt to generate a token and return it to the front end * 5. token Put it into redis. Redis token: user information sets the expiration time * (When logging in for authentication, first verify whether the token string is legal and whether redis authentication exists) */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } String pwd = DigestUtils.md5Hex(password + slat); SysUser sysUser = sysUserService.findUser(account,pwd); if (sysUser == null){ return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg()); } //Log in successfully, use JWT to generate a token and return it to token and redis String token = JWTUtils.createToken(sysUser.getId()); // JSON.toJSONString usage https://blog.csdn.net/antony9118/article/details/71023009 //The expiration time is 100 days //redisTemplate usage https://blog.csdn.net/lydms/article/details/105224210 redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),100, TimeUnit.DAYS); return Result.success(token); } //Generate the password we want and put it in the database for login public static void main(String[] args) { System.out.println(DigestUtils.md5Hex("admin"+slat)); } }
src/main/java/com/mszlu/blog/service/impl/SysUserServiceImpl.java
@Override public SysUser findUser(String account, String password) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getAccount,account); queryWrapper.eq(SysUser::getPassword,password); //account id avatar name queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAdmin,SysUser::getNickname); //Increase query efficiency and query only one item queryWrapper.last("limit 1"); return sysUserMapper.selectOne(queryWrapper); }
src/main/java/com/mszlu/blog/service/SysUserService.java
SysUser findUser(String account, String pwd);
5.1.5 login parameters, redis configuration, unified error code
src/main/resources/application.properties
spring.redis.host=localhost spring.redis.port=6379
src/main/java/com/mszlu/blog/vo/ErrorCode.java
package com.mszlu.blog.vo; public enum ErrorCode { PARAMS_ERROR(10001,"Parameter error"), ACCOUNT_PWD_NOT_EXIST(10002,"User name or password does not exist"), NO_PERMISSION(70001,"No access"), 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; } }
5.1.6 testing
Use the postman test, because after logging in, you need to jump to the page for token authentication. If the interface is not written, there will be problems in the front end.
After the token is obtained by the front end, it will be stored in the storage h5, which is stored locally
postman
redis view
5.2. Get user information
Why can I log in after obtaining user information?
After the token is obtained by the front-end, it will be stored in the storage h5 and stored locally. After storage, it will get the token in the storage to obtain the user information. If this interface is not implemented, it will always request to fall into an endless loop
5.2.1 interface description
You have to get this parameter from the head of http. In this way, it is relatively safe to transfer parameters,
The returned data is the data related to our users, including id, account number, nickname and avatar
Interface url: / users/currentUser
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Authorization
string
Header information (TOKEN)
Return data:
{ "success": true, "code": 200, "msg": "success", "data": { "id":1, "account":"1", "nickaname":"1", "avatar":"ss" } }
5.2.2 Controller
src/main/java/com/mszlu/blog/controller/UsersController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.SysUserService; import com.mszlu.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; //On the usage and difference between @ RequestMapping @ResponseBody and @ RequestBody annotation //https://blog.csdn.net/ff906317011/article/details/78552426?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link //@The RequestBody is mainly used to receive the data in the json string passed from the front end to the back end (the data in the request body); The most common way to use the request body to transfer parameters is undoubtedly the POST request, so when using @ RequestBody to receive data, it is generally submitted in the form of POST. @RestController @RequestMapping("users") public class UserController { @Autowired private SysUserService sysUserService; @GetMapping("currentUser") public Result currentUser(@RequestHeader("Authorization") String token){ return sysUserService.findUserByToken(token); } }
5.2.3 Service
src/main/java/com/mszlu/blog/service/SysUserService.java
/** * Query user information according to token * @param token * @return */ Result findUserByToken(String token);
src/main/java/com/mszlu/blog/service/impl/SysUserServiceImpl.java
//This pop-up problem can be solved by adding @ Repository to the corresponding mapper so that spring can recognize it @Autowired private SysUserMapper sysUserMapper; @Autowired private LoginService loginService; @Override public Result findUserByToken(String token) { /** * 1,token Validity verification * Whether it is empty, whether the parsing is successful, and whether redis exists * 2,If the verification fails, an error is returned *3,If successful, the corresponding result LoginUserVo is returned */ //Check the token in loginservice SysUser sysUser = loginService.checkToken(token); if(sysUser == null){ return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg()); } LoginUserVo loginUserVo = new LoginUserVo(); loginUserVo.setId(sysUser.getId()); loginUserVo.setNickname(sysUser.getNickname()); loginUserVo.setAvatar(sysUser.getAvatar()); loginUserVo.setAccount(sysUser.getAccount()); return Result.success(loginUserVo); }
src/main/java/com/mszlu/blog/service/LoginService.java
package com.mszlu.blog.service; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; /** * @Author ljm * @Date 2021/10/12 20:04 * @Version 1.0 */ public interface LoginService { /** * Login function * @param loginParam * @return */ Result login(LoginParam loginParam); SysUser checkToken(String token); }
src/main/java/com/mszlu/blog/service/impl/LoginServiceImpl.java
@Override public SysUser checkToken(String token) { //Null returned if token is null if(StringUtils.isBlank(token)){ return null; } Map<String, Object> stringObjectMap = JWTUtils.checkToken(token); //Parsing failed if(stringObjectMap ==null){ return null; } //If successful String userJson = redisTemplate.opsForValue().get("TOKEN_"+token); if (StringUtils.isBlank(userJson)) { return null; } //Resolve back to sysUser object SysUser sysUser = JSON.parseObject(userJson, SysUser.class); return sysUser; }
5.2.4 LoginUserVo
src/main/java/com/mszlu/blog/vo/LoginUserVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class LoginUserVo { //Interact with pages private Long id; private String account; private String nickname; private String avatar; }
5.2.5 testing
5.3. Log out
One is to log in and authenticate the token. The other is to register in redis. The token string cannot be changed. It can only be cleared by the front end. What the back end can do is to clear redis
5.3.1 interface description
Interface url: / logout
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Authorization
string
Header information (TOKEN)
Return data:
{ "success": true, "code": 200, "msg": "success", "data": null }
5.3.2 Controller
src/main/java/com/mszlu/blog/controller/LogoutController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("logout") public class LogoutController { @Autowired private LoginService loginService; //Get a parameter such as header information @GetMapping public Result logout(@RequestHeader("Authorization") String token){ return loginService.logout(token); } }
5.3.3 Service
src/main/java/com/mszlu/blog/service/LoginService.java
/** * Exit login * @param token * @return */ Result logout(String token);
src/main/java/com/mszlu/blog/service/impl/LoginServiceImpl.java
@Override public Result logout(String token) { //The backend directly deletes the token in redis redisTemplate.delete("TOKEN_"+token); return Result.success(null); }
5.3.4 testing
6.1. register
6.1.1 interface description
Interface url: / register
Request method: POST
post parameter passing means that the request parameters are passed in json mode
See this article for details
post and @ Requestbody
Request parameters:
Parameter name
Parameter type
explain
account
string
account number
password
string
password
nickname
string
nickname
Return data:
{ "success": true, "code": 200, "msg": "success", "data": "token" }
6.1.2 Controller
src/main/java/com/mszlu/blog/controller/RegisterController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("register") public class RegisterController { @Autowired private LoginService loginService; //The back end passes multiple parameters, and the front end only selects the parameters it needs @PostMapping public Result register(@RequestBody LoginParam loginParam){ //sso single sign on, if the login registration function is proposed later (separate service, interface service can be provided independently) return loginService.register(loginParam); } }
Add a new parameter nickname to the parameter LoginParam.
src/main/java/com/mszlu/blog/vo/params/LoginParam.java
package com.mszlu.blog.vo.params; import lombok.Data; @Data public class LoginParam { private String account; private String password; private String nickname; }
6.1.3 Service
src/main/java/com/mszlu/blog/service/impl/LoginServiceImpl.java
@Override public Result register(LoginParam loginParam) { /** * 1. Judge whether the parameter is legal * 2. Judge whether the account exists and return that the account has been registered * 3. Does not exist, registered user * 4. Generate token * 5. Save to redis and return * 6. Note that with the addition of transactions, once any process in the middle has problems, the registered user needs to be rolled back */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); String nickname = loginParam.getNickname(); if (StringUtils.isBlank(account) || StringUtils.isBlank(password) || StringUtils.isBlank(nickname) ){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } SysUser sysUser = this.sysUserService.findUserByAccount(account); if (sysUser != null){ return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg()); } sysUser = new SysUser(); sysUser.setNickname(nickname); sysUser.setAccount(account); sysUser.setPassword(DigestUtils.md5Hex(password+slat)); sysUser.setCreateDate(System.currentTimeMillis()); sysUser.setLastLogin(System.currentTimeMillis()); sysUser.setAvatar("/static/img/logo.b3a48c0.png"); sysUser.setAdmin(1); //1 is true sysUser.setDeleted(0); // 0 is false sysUser.setSalt(""); sysUser.setStatus(""); sysUser.setEmail(""); this.sysUserService.save(sysUser); //token String token = JWTUtils.createToken(sysUser.getId()); redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); return Result.success(token); }
In errorcode Add one in Java
src/main/java/com/mszlu/blog/vo/ErrorCode.java
ACCOUNT_EXIST(10004,"Account already exists"),
The save and findUserByAccount methods in sysUserService do not need to construct interfaces and implementation classes
src/main/java/com/mszlu/blog/service/SysUserService.java
/** * Find users by account * @param account * @return */ SysUser findUserByAccount(String account); /** * Save user * @param sysUser */ void save(SysUser sysUser);
src/main/java/com/mszlu/blog/service/impl/SysUserServiceImpl.java
@Override public SysUser findUserByAccount(String account) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getAccount,account); //Ensure that only one item can be queried queryWrapper.last("limit 1"); return sysUserMapper.selectOne(queryWrapper); } @Override public void save(SysUser sysUser) { //Save the user id, which will be generated automatically //The default id generated in this place is the distributed id snowflake algorithm //mybatis-plus this.sysUserMapper.insert(sysUser); }
6.1.4 additional services
Roll back when an error occurs to prevent adding exceptions
Add @ Transactional annotation
src/main/java/com/mszlu/blog/service/impl/LoginServiceImpl.java
@Service @Transactional public class LoginServiceImpl implements LoginService {}
Of course, it is generally recommended to add the transaction annotation @ Transactional to the interface, which is more general.
src/main/java/com/mszlu/blog/service/LoginService.java
package com.mszlu.blog.service; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.vo.Result; import com.mszlu.blog.vo.params.LoginParam; import org.springframework.transaction.annotation.Transactional; /** * @Author ljm * @Date 2021/10/12 20:04 * @Version 1.0 */ @Transactional public interface LoginService { /** * Login function * @param loginParam * @return */ Result login(LoginParam loginParam); SysUser checkToken(String token); /** * Exit login * @param token * @return */ Result logout(String token); /** * register * @param loginParam * @return */ Result register(LoginParam loginParam); }
During the test, you can stop redis. After the redis connection is abnormal, the newly added user should perform the rollback operation.
6.1.5 testing
6.2. Login interceptor
Every time you access a resource that needs to be logged in, you need to judge in the code. Once the login logic changes, the code has to be changed, which is very inappropriate.
So can we make a unified login judgment?
Interceptor in spring MVC
Yes, use the interceptor to intercept the login. If you encounter an interface that needs to be accessed by login, if you don't log in, the interceptor will directly return and jump to the login page.
Three Javas devices: filter listener interceptor
6.2.1 interceptor implementation
src/main/java/com/mszlu/blog/handler/LoginInterceptor.java
package com.mszlu.blog.handler; import com.alibaba.fastjson.JSON; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.vo.ErrorCode; import com.mszlu.blog.vo.Result; import lombok.extern.slf4j.Slf4j; 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 (Handler) /** * 1. You need to judge whether the requested interface path is a HandlerMethod (controller method) * 2. Judge whether the token is empty. If it is empty, you are not logged in * 3. If the token is not empty, log in and verify loginService checkToken * 4. If the certification is successful, it can be released */ //If it's not our way to release if (!(handler instanceof HandlerMethod)){ //The handler may be requestresourcehandler. The springboot program accesses the static resources and queries the static directory under the classpath by default return true; } String token = request.getHeader("Authorization"); log.info("=================request start==========================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}", token); log.info("=================request end==========================="); if(StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "Not logged in"); //Set browser recognition to return json response.setContentType("application/json;charset=utf-8"); //https://www.cnblogs.com/qlqwjy/p/7455706.html response.getWriter().print() //SON.toJSONString converts an object into a Json string response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "Not logged in"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } //Yes, login status, release //Login verification successful, release //I want to get the user's information directly in the controller. How can I get it? return true; } }
6.2.2 make interceptor effective
src/main/java/com/mszlu/blog/config/WebMVCConfig.java
package com.mszlu.blog.config; import com.mszlu.blog.handler.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMVCConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addCorsMappings(CorsRegistry registry) { //Cross domain configuration registry.addMapping("/**").allowedOrigins("http://localhost:8080"); } @Override public void addInterceptors(InterceptorRegistry registry) { //Assuming that the test interface is intercepted, the real intercepting interface will be configured when the intercepting interface is actually encountered later registry.addInterceptor(loginInterceptor) .addPathPatterns("/test"); } }
6.2.3 testing
src/main/java/com/mszlu/blog/controller/TestController.java
package com.mszlu.blog.controller; import com.mszlu.blog.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { @RequestMapping public Result test(){ return Result.success(null); } }
src/main/java/com/mszlu/blog/handler/LoginInterceptor.java returns true for release, and the test interface can be accessed normally
6.3. ThreadLocal saves user information
Only token is put in redis. We want to get user information directly
Benefits and how to use them
Use ThreadLocal to save user login information
Use ThreadLocal instead of Session to complete the function of saving user login information
Benefits of using ThreadLocal instead of Session:
User information can be easily obtained in the same thread without frequent transmission session Object.
Specific implementation process:
In the login business code, when the user logs in successfully, a login credential is generated and stored in the redis In, Save the string in the voucher in cookie Return to the client in. Use an interceptor to intercept requests from cookie Get voucher string and redis Match the vouchers in to obtain user information, Store user information in ThreadLocal If you hold the user information in this request, you can use the user information in subsequent operations.
Related issues
Session principle
What's the difference between COOKIE and SESSION?
src/main/java/com/mszlu/blog/utils/UserThreadLocal.java
package com.mszlu.blog.utils; import com.mszlu.blog.dao.pojo.SysUser; public class UserThreadLocal { private UserThreadLocal(){} //Thread variable isolation 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(); } }
src/main/java/com/mszlu/blog/handler/LoginInterceptor.java
package com.mszlu.blog.handler; import com.alibaba.fastjson.JSON; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.service.LoginService; import com.mszlu.blog.utils.UserThreadLocal; import com.mszlu.blog.vo.ErrorCode; import com.mszlu.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 (Handler) /** * 1. You need to judge whether the requested interface path is a HandlerMethod (controller method) * 2. Judge whether the token is empty. If it is empty, you are not logged in * 3. If the token is not empty, log in and verify loginService checkToken * 4. If the certification is successful, it can be released */ if (!(handler instanceof HandlerMethod)){ //The handler may be requestresourcehandler. The springboot program accesses the static resources and queries the static directory under the classpath by default return true; } String token = request.getHeader("Authorization"); log.info("=================request start==========================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}", token); log.info("=================request end==========================="); if (StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "Not logged in"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "Not logged in"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } //Login verification successful, release //I want to get the user's information directly in the controller. How can I get it? UserThreadLocal.put(sysUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //If you do not delete the information used up in ThreadLocal, there is a risk of memory leakage UserThreadLocal.remove(); } }
src/main/java/com/mszlu/blog/controller/TestController.java
package com.mszlu.blog.controller; import com.mszlu.blog.dao.pojo.SysUser; import com.mszlu.blog.utils.UserThreadLocal; import com.mszlu.blog.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { @RequestMapping public Result test(){ // SysUser SysUser sysUser = UserThreadLocal.get(); System.out.println(sysUser); return Result.success(null); } }
7.1. ThreadLocal memory leak
ThreadLocal principle and memory leakage prevention
Solid lines represent strong references and dotted lines represent weak references
Each Thread maintains a threadlocalmap. Key is the ThreadLocal instance using weak reference, and value is the copy of Thread variable.
Strong reference, the most commonly used reference. An object has strong reference and will not be recycled by the garbage collector. When the memory space is insufficient, the Java virtual machine would rather throw OutOfMemoryError error to make the program terminate abnormally than recycle this object.
If you want to disassociate a strong reference from an object, you can explicitly assign the reference to null, so that the JVM can reclaim the object at the right time.
Weak reference: when the JVM performs garbage collection, the objects associated with weak references will be recycled regardless of whether the memory is sufficient or not. In java, use java Lang.ref.weakreference class.
7.2. Article details
7.2.1 interface description
Interface url: / articles/view/{id}
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
id
long
Article id (path parameter)
Return data:
{success: true, code: 200, msg: "success",...} code: 200 data: {id: "1405916999732707330", title: "SpringBoot Introductory case", summary: "springboot Introductory case", commentCounts: 0,...} msg: "success" success: true
7.2.2 tables involved
table of content
content stores information in makedown format
content_html stores information in html format
CREATE TABLE `blog`.`ms_article_body` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `article_id` bigint(0) NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `article_id`(`article_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
src/main/java/com/mszlu/blog/dao/pojo/ArticleBody.java
package com.mszlu.blog.dao.pojo; import lombok.Data; //table of content @Data public class ArticleBody { private Long id; private String content; private String contentHtml; private Long articleId; }
Category table
avata path classification Icon
category_name of the icon for the category
description Description of classification
CREATE TABLE `blog`.`ms_category` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
src/main/java/com/mszlu/blog/dao/pojo/Category.java
package com.mszlu.blog.dao.pojo; import lombok.Data; //Category table @Data public class Category { private Long id; private String avatar; private String categoryName; private String description; }
7.2.3 Controller
src/main/java/com/mszlu/blog/controller/ArticleController.java
@PostMapping("view/{id}") public Result findArticleById(@PathVariable("id") Long articleId){ return articleService.findArticleById(articleId); }
7.2.4 Service
src/main/java/com/mszlu/blog/service/ArticleService.java
/** * View Article Details * @param articleId * @return */ Result findArticleById(Long articleId);
There are only tilt and some introductions in the article table
ms_ Body in Article_ id corresponds to the second table ms_article_ id on body
ms_category will be mapped to ms_category in Article_ id
You need to do some relative association queries
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result findArticleById(Long articleId) { /** * 1. Query article information according to id * 2. Do association query according to bodyId and categoryid */ Article article = this.articleMapper.selectById(articleId); ArticleVo articleVo = copy(article, true, true,true,true); //After viewing the article, the number of new readings. Is there a problem? //After viewing the article, the data should be returned directly. At this time, an update operation is performed. When updating, write lock is added to block other read operations, and the performance will be relatively low // The update increases the time-consuming of the interface. If there is a problem with the update, the operation of viewing the article cannot be affected //The thread pool can throw the update operation into the thread pool for execution, which is not related to the main thread //threadService.updateArticleViewCount(articleMapper,article); return Result.success(articleVo); }
src/main/java/com/mszlu/blog/vo/ArticleVo.java
package com.mszlu.blog.vo; import lombok.Data; import java.util.List; @Data public class ArticleVo { private Long id; private String title; private String summary; private int commentCounts; private int viewCounts; private int weight; /** * Creation time */ private String createDate; private String author; private ArticleBodyVo body; private List<TagVo> tags; private CategoryVo category; }
src/main/java/com/mszlu/blog/vo/ArticleBodyVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class ArticleBodyVo { //content private String content; }
src/main/java/com/mszlu/blog/vo/CategoryVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class CategoryVo { //id, icon path, icon name private Long id; private String avatar; private String categoryName; }
Attribute filling in ArticleVo:
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
//Method overload. The method name is the same, and the number of parameters is different 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; } @Autowired private CategoryService categoryService; //With body information and category information private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory){ ArticleVo articleVo = new ArticleVo(); articleVo.setId(String.valueOf(article.getId())); BeanUtils.copyProperties(article,articleVo); //Time can't copy because it's a long type articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); //Not all interfaces need labels and author information 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; } @Autowired private CategoryService categoryService; private CategoryVo findCategory(Long categoryId) { return categoryService.findCategoryById(categoryId); } //Building ArticleBodyMapper @Autowired private ArticleBodyMapper articleBodyMapper; private ArticleBodyVo findArticleBodyById(Long bodyId) { ArticleBody articleBody = articleBodyMapper.selectById(bodyId); ArticleBodyVo articleBodyVo = new ArticleBodyVo(); articleBodyVo.setContent(articleBody.getContent()); return articleBodyVo; }
src/main/java/com/mszlu/blog/dao/mapper/ArticleBodyMapper.java
package com.mszlu.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.ArticleBody; public interface ArticleBodyMapper extends BaseMapper<ArticleBody> { }
src/main/java/com/mszlu/blog/dao/mapper/CategoryMapper.java
package com.mszlu.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.Category; public interface CategoryMapper extends BaseMapper<Category> { }
src/main/java/com/mszlu/blog/service/CategoryService.java
package com.mszlu.blog.service; import com.mszlu.blog.vo.CategoryVo; public interface CategoryService { CategoryVo findCategoryById(Long id); }
src/main/java/com/mszlu/blog/service/impl/CategoryServiceImpl.java
package com.mszlu.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.dao.mapper.CategoryMapper; import com.mszlu.blog.dao.pojo.Category; import com.mszlu.blog.service.CategoryService; import com.mszlu.blog.vo.CategoryVo; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; //Inject spring @Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Override public CategoryVo findCategoryById(Long id){ Category category = categoryMapper.selectById(id); CategoryVo categoryVo = new CategoryVo(); //Because the attributes of category and categoryvo are the same, you can use BeanUtils copyProperties BeanUtils.copyProperties(category,categoryVo); return categoryVo; } }
7.2.5 testing
7.3. Number of reads updated using thread pool
//After viewing the article, the number of new readings. Is there a problem?
//After viewing the article, the data should be returned directly. At this time, an update operation is performed. When updating, the write lock is added to block other read operations, and the performance will be relatively low (there is no way to solve it, and the lock must be added to increase the number of reads)
//The update increases the time-consuming of the interface (consider reducing the time-consuming). If there is a problem with the update, the viewing operation cannot be affected
Thought of a technology thread pool
You can throw the update operation into the thread pool for execution, which is not related to the main thread
What is optimistic lock and what is pessimistic lock
CAS principle analysis
7.3.1 configuration of route pool
Make a thread pool configuration to open the thread pool
src/main/java/com/mszlu/blog/config/ThreadPoolConfig.java
package com.mszlu.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; //https://www.jianshu.com/p/0b8443b1adc9 Usage and understanding of @ Configuration and @ Bean @Configuration @EnableAsync //Turn on Multithreading public class ThreadPoolConfig { @Bean("taskExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // Set the number of core threads executor.setCorePoolSize(5); // Set the maximum number of threads executor.setMaxPoolSize(20); //Configure queue size executor.setQueueCapacity(Integer.MAX_VALUE); // Set thread active time (seconds) executor.setKeepAliveSeconds(60); // Set default thread name executor.setThreadNamePrefix("Code God Road blog project"); // Wait for all tasks to finish before closing the thread pool executor.setWaitForTasksToCompleteOnShutdown(true); //Perform initialization executor.initialize(); return executor; } }
7.3.1 use
src/main/java/com/mszlu/blog/service/ThreadService.java
package com.mszlu.blog.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.dao.mapper.ArticleMapper; import com.mszlu.blog.dao.pojo.Article; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class ThreadService { //It is expected that the execution of this operation in the thread pool will not affect the original main thread //If you don't know about thread pool here, you can see JUC concurrent programming @Async("taskExcutor") public void updateArticleViewCount(ArticleMapper articleMapper, Article article) { Integer viewCounts = article.getViewCounts(); Article articleupdate = new Article(); articleupdate.setViewCounts(viewCounts+1); LambdaQueryWrapper<Article> updatewrapper = new LambdaQueryWrapper<>(); //Update by id updatewrapper.eq(Article::getId ,article.getId()); //Set one for thread safety in a multithreaded environment //Before changing, confirm whether this value has been modified by other threads first. It is similar to cas operation, cas plus spin, and adding a loop is cas updatewrapper.eq(Article ::getViewCounts,viewCounts ); // update article set view_count=100 where view_count=99 and id =111 //Entity class plus update condition articleMapper.update(articleupdate,updatewrapper); try { Thread.sleep(5000); System.out.println("The update is complete"); } catch (InterruptedException e) { e.printStackTrace(); } } }
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Autowired private ThreadService threadService; @Override public ArticleVo findArticleById(Long id) { Article article = articleMapper.selectById(id); //Thread pool threadService.updateViewCount(articleMapper,article); return copy(article,true,true,true,true); }
7.3.3 testing
Sleep the method in ThredService for 5 seconds, which will not affect the use of the main thread, that is, the article details will be displayed quickly and will not be affected
Bug correction
When the commentCounts, viewCounts and weight fields in the previous Article are int, which will cause the reading times to be updated, set the other two fields to the initial value of 0
mybatisplus only set up articleupdate.com when updating the reading times of articles setviewsCounts(viewCounts+1),
But the default basic data type of int is 0,
If mybatisplus is not null, it will be generated into sql statements for updating. Will appear
Ideally, there should be only views_counts changes, but this happens because of the mybatisplus rule
Therefore, changing int to Integer will not cause this problem.
src/main/java/com/mszlu/blog/dao/pojo/Article.java
package com.mszlu.blog.dao.pojo; import lombok.Data; @Data public class Article { public static final int Article_TOP = 1; public static final int Article_Common = 0; private Long id; private String title; private String summary; private Integer commentCounts; private Integer viewCounts; /** * Author id */ private Long authorId; /** * Content id */ private Long bodyId; /** *Category id */ private Long categoryId; /** * Topping */ private Integer weight; /** * Creation time */ private Long createDate; }
8.1. Comment list
Comment form
id comment id
Content comment content
create_date comment time
article_id comment article
author_ Who commented
parent_id building function to reply to comments
to_uid to whom
What level is the comment level (Level 1 indicates the top level comment, and level 2 indicates the comment on the comment)
CREATE TABLE `blog`.`ms_comment` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `create_date` bigint(0) NOT NULL, `article_id` int(0) NOT NULL, `author_id` bigint(0) NOT NULL, `parent_id` bigint(0) NOT NULL, `to_uid` bigint(0) NOT NULL, `level` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `article_id`(`article_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
src/main/java/com/mszlu/blog/dao/pojo/Comment.java
package com.mszlu.blog.dao.pojo; import lombok.Data; @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; }
8.1.1 interface description
Interface url: / comments/article/{id}
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
id
long
Article id (path parameter)
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 53, "author": { "nickname": "Li Si", "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png", "id": 1 }, "content": "Well written", "childrens": [ { "id": 54, "author": { "nickname": "Li Si", "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png", "id": 1 }, "content": "111", "childrens": [], "createDate": "1973-11-26 08:52", "level": 2, "toUser": { "nickname": "Li Si", "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png", "id": 1 } } ], "createDate": "1973-11-27 09:53", "level": 1, "toUser": null } ] }
Code structure
8.1.2 Controller
src/main/java/com/mszlu/blog/controller/CommentsController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.CommentsService; import com.mszlu.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("comments") public class CommentsController { @Autowired private CommentsService commentsService; @GetMapping("article/{id}") public Result comments(@PathVariable("id") Long articleId){ return commentsService.commentsByArticleId(articleId); } }
8.1.3 Service
src/main/java/com/mszlu/blog/service/CommentsService.java
package com.mszlu.blog.service; import com.mszlu.blog.vo.Result; public interface CommentsService { /** * Query all comment lists according to the article id * @param id * @return */ Result commentsByArticleId(Long id); }
src/main/java/com/mszlu/blog/service/impl/CommentsServiceImpl.java
package com.mszlu.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.dao.mapper.CommentMapper; import com.mszlu.blog.dao.pojo.Comment; import com.mszlu.blog.service.CommentsService; import com.mszlu.blog.service.SysUserService; import com.mszlu.blog.vo.CommentVo; import com.mszlu.blog.vo.Result; import com.mszlu.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; @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 according to the article id and query from the comment table * 2. Query the author's information according to the author's id * 3. Judge whether there are sub comments if level = 1 * 4. If yes, query according to the comment ID (parent_id) */ LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>(); //Query according to Article id queryWrapper.eq(Comment::getArticleId,id ); //Query according to hierarchical relationship queryWrapper.eq(Comment::getLevel,1 ); List<Comment> comments = commentMapper.selectList(queryWrapper); List<CommentVo> commentVoList = copyList(comments); return Result.success(commentVoList); } //Judge the comment in the list table public List<CommentVo> copyList(List<Comment> commentList){ List<CommentVo> commentVoList = new ArrayList<>(); for (Comment comment : commentList) { commentVoList.add(copy(comment)); } return commentVoList; } private CommentVo copy(Comment comment) { CommentVo commentVo = new CommentVo(); // Same attribute copy BeanUtils.copyProperties(comment,commentVo); commentVo.setId(String.valueOf(comment.getId())); //Author information Long authorId = comment.getAuthorId(); UserVo userVo = this.sysUserService.findUserVoById(authorId); commentVo.setAuthor(userVo); //Sub comment Integer level = comment.getLevel(); if (1 == level){ Long id = comment.getId(); List<CommentVo> commentVoList = findCommentsByParentId(id); commentVo.setChildrens(commentVoList); } //to User to whom if (level > 1){ Long toUid = comment.getToUid(); UserVo toUserVo = this.sysUserService.findUserVoById(toUid); commentVo.setToUser(toUserVo); } return commentVo; } private List<CommentVo> findCommentsByParentId(Long id) { LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Comment::getParentId,id); queryWrapper.eq(Comment::getLevel,2); List<Comment> comments = this.commentMapper.selectList(queryWrapper); return copyList(comments); } }
Returned data:
src/main/java/com/mszlu/blog/vo/CommentVo.java
package com.mszlu.blog.vo; import com.mszlu.blog.dao.pojo.SysUser; import lombok.Data; import java.util.List; @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; }
src/main/java/com/mszlu/blog/vo/UserVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class UserVo { private String nickname; private String avatar; private Long id; }
The SysUserService provides a service for querying user information:
src/main/java/com/mszlu/blog/service/SysUserService.java
UserVo findUserVoById(Long id);
src/main/java/com/mszlu/blog/service/impl/SysUserServiceImpl.java
@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("Code God's Road"); } UserVo userVo = new UserVo(); userVo.setAvatar(sysUser.getAvatar()); userVo.setNickname(sysUser.getNickname()); userVo.setId(sysUser.getId()); return userVo; }
8.2. comment
8.2.1 interface description
Interface url: / comments/create/change
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
articleId
long
Article id
content
string
Comment content
parent
long
Parent comment id
toUserId
long
Commented user id
Return data:
{ "success": true, "code": 200, "msg": "success", "data": null }
8.2.2 add to login interceptor
src/main/java/com/mszlu/blog/config/WebMVCConfig.java
@Override public void addInterceptors(InterceptorRegistry registry) { //Intercept the test interface. When the interface to be intercepted is actually encountered later, it will be configured as a real intercepting interface registry.addInterceptor(loginInterceptor) .addPathPatterns("/test").addPathPatterns("/comments/create/change"); }
8.2.3 Controller
Code structure
Build comment parameter object:
src/main/java/com/mszlu/blog/vo/params/CommentParam.java
package com.mszlu.blog.vo.params; import lombok.Data; @Data public class CommentParam { private Long articleId; private String content; private Long parent; private Long toUserId; }
src/main/java/com/mszlu/blog/controller/CommentsController.java
@PostMapping("create/change") public Result comment(@RequestBody CommentParam commentParam){ return commentsService.comment(commentParam); }
8.2.4 Service
src/main/java/com/mszlu/blog/service/CommentsService.java
Result comment(CommentParam commentParam);
src/main/java/com/mszlu/blog/service/impl/CommentsServiceImpl.java
@Override public Result comment(CommentParam commentParam) { //Get the current user 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.getParent(); if (parent == null || parent == 0) { comment.setLevel(1); }else{ comment.setLevel(2); } //If it is empty, the parent is 0 comment.setParentId(parent == null ? 0 : parent); Long toUserId = commentParam.getToUserId(); comment.setToUid(toUserId == null ? 0 : toUserId); this.commentMapper.insert(comment); return Result.success(null); } //Prevent the loss of front-end precision and convert id to string // The distributed id is relatively long, and there will be precision loss when it is transmitted to the front end. It must be converted to string type for transmission, so there will be no problem @JsonSerialize(using = ToStringSerializer.class) private Long id;
write an article
Writing articles requires three interfaces:
-
Get all article categories
-
Get all tags
-
Publish articles
9.1. All article categories
9.1.1 interface description
Interface url: / categories
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success":true, "code":200, "msg":"success", "data": [ {"id":1,"avatar":"/category/front.png","categoryName":"front end"}, {"id":2,"avatar":"/category/back.png","categoryName":"back-end"}, {"id":3,"avatar":"/category/lift.jpg","categoryName":"life"}, {"id":4,"avatar":"/category/database.png","categoryName":"database"}, {"id":5,"avatar":"/category/language.png","categoryName":"programing language"} ] }
9.1.2 Controller
src/main/java/com/mszlu/blog/controller/CategoryController.java
package com.mszlu.blog.controller; import com.mszlu.blog.service.CategoryService; import com.mszlu.blog.vo.CategoryVo; import com.mszlu.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("categorys") public class CategoryController { @Autowired private CategoryService categoryService; @GetMapping public Result listCategory() { return categoryService.findAll(); } }
9.1.3 Service
src/main/java/com/mszlu/blog/service/impl/CategoryServiceImpl.java
//id is inconsistent. You need to reset it public CategoryVo copy(Category category){ CategoryVo categoryVo = new CategoryVo(); BeanUtils.copyProperties(category,categoryVo); //id is inconsistent. You need to reset it 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; } @Override public Result findAll() { // Without any parameters, all you need is an empty LambdaQueryWrapper List<Category> categories = this.categoryMapper.selectList(new LambdaQueryWrapper<>()); //Object of page interaction return Result.success(copyList(categories)); }
9.2. All article Tags
9.2.1 interface description
Interface url: / tags
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 5, "tagName": "springboot" }, { "id": 6, "tagName": "spring" }, { "id": 7, "tagName": "springmvc" }, { "id": 8, "tagName": "11" } ] }
9.2.2 Controller
src/main/java/com/mszlu/blog/controller/TagsController.java
@Autowired private TagService tagService; @GetMapping public Result findAll(){ /** * Query all article Tags * @return */ return tagService.findAll(); }
9.2.3 Service
src/main/java/com/mszlu/blog/service/TagService.java
/** * Query all article Tags * @return */ Result findAll();
src/main/java/com/mszlu/blog/service/impl/TagServiceImpl.java
@Override public Result findAll() { List<Tag> tags = this.tagMapper.selectList(new LambdaQueryWrapper<>()); return Result.success(copyList(tags)); }
9.3. Publish articles
9.3.1 interface description
The content of the request is object({content: "ww", contentHtml: "ww?"})Because they are makedown Editor for id Refers to the article id
Interface url: / articles/publish
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
title
string
Article title
id
long
Article id (edit value)
body
object({content: "ww", contentHtml: "
ww
"})
Article content
category
{id: 2, avatar: "/ category/back.png", catalyname: "back end"}
Article category
summary
string
Article overview
tags
[{id: 5}, {id: 6}]
Article label
Return data:
{ "success": true, "code": 200, "msg": "success", "data": {"id":12232323} }
Code structure
9.3.2 Controller
src/main/java/com/mszlu/blog/controller/ArticleController.java
// @The RequestBody is mainly used to receive the data in the json string passed from the front end to the back end (the data in the request body); // The most common way to use the request body to transfer parameters is undoubtedly the POST request, so when using @ RequestBody to receive data, it is generally submitted in the form of POST. @PostMapping("publish") public Result publish(@RequestBody ArticleParam articleParam){ return articleService.publish(articleParam); }
We need to establish a parameter object to receive the data from the front end
src/main/java/com/mszlu/blog/vo/params/ArticleParam.java
package com.mszlu.blog.vo.params; import com.mszlu.blog.vo.CategoryVo; import com.mszlu.blog.vo.TagVo; import lombok.Data; import java.util.List; @Data public class ArticleParam { private Long id; private ArticleBodyParam body; private CategoryVo category; private String summary; private List<TagVo> tags; private String title; }
src/main/java/com/mszlu/blog/vo/params/ArticleBodyParam.java
package com.mszlu.blog.vo.params; import lombok.Data; @Data public class ArticleBodyParam { private String content; private String contentHtml; }
9.3.3 Service
src/main/java/com/mszlu/blog/service/ArticleService.java
/** * Article publishing service * @param articleParam * @return */ Result publish(ArticleParam articleParam);
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override @Transactional public Result publish(ArticleParam articleParam) { //Note that if you want to get the data, you must add the interface to the interceptor SysUser sysUser = UserThreadLocal.get(); /** * 1. The purpose of publishing articles is to build Article objects * 2. Author id current login user * 3. Tag to add the tag to the association list * 4. body Content store article bodyId */ Article article = new Article(); article.setAuthorId(sysUser.getId()); article.setCategoryId(articleParam.getCategory().getId()); article.setCreateDate(System.currentTimeMillis()); article.setCommentCounts(0); article.setSummary(articleParam.getSummary()); article.setTitle(articleParam.getTitle()); article.setViewCounts(0); article.setWeight(Article.Article_Common); article.setBodyId(-1L); //After insertion, an article id will be generated (because the new article does not have an article id, so you need to insert it //The official website explained: "after insart, the primary key will be automatically 'set to the ID field of the entity. So you only need" getid(). " // Using the self increment of the primary key, the id value of mp will return to the parameter object after the insert operation //https://blog.csdn.net/HSJ0170/article/details/107982866 this.articleMapper.insert(article); //tags List<TagVo> tags = articleParam.getTags(); if (tags != null) { for (TagVo tag : tags) { ArticleTag articleTag = new ArticleTag(); articleTag.setArticleId(article.getId()); articleTag.setTagId(tag.getId()); this.articleTagMapper.insert(articleTag); } } //body ArticleBody articleBody = new ArticleBody(); articleBody.setContent(articleParam.getBody().getContent()); articleBody.setContentHtml(articleParam.getBody().getContentHtml()); articleBody.setArticleId(article.getId()); articleBodyMapper.insert(articleBody); //Give an id after inserting article.setBodyId(articleBody.getId()); //When does the save method in MybatisPlus execute insert and update // https://www.cxyzjd.com/article/Horse7/103868144 //Insert or update only when changing the database. General query is OK articleMapper.updateById(article); ArticleVo articleVo = new ArticleVo(); articleVo.setId(article.getId()); return Result.success(articleVo); }
src/main/java/com/mszlu/blog/config/WebMVCConfig.java
Of course, to log in to the interceptor, you need to add the configuration of publishing articles:
@Override public void addInterceptors(InterceptorRegistry registry) { //Intercept the test interface. When the interface to be intercepted is actually encountered later, it will be configured as a real intercepting interface registry.addInterceptor(loginInterceptor) .addPathPatterns("/test") .addPathPatterns("/comments/create/change") .addPathPatterns("/articles/publish"); }
src/main/java/com/mszlu/blog/dao/mapper/ArticleTagMapper.java
package com.mszlu.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.ArticleTag; public interface ArticleTagMapper extends BaseMapper<ArticleTag> { }
src/main/java/com/mszlu/blog/dao/mapper/ArticleBodyMapper.java
package com.mszlu.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.dao.pojo.ArticleBody; public interface ArticleBodyMapper extends BaseMapper<ArticleBody> { }
src/main/java/com/mszlu/blog/vo/ArticleVo.java
package com.mszlu.blog.vo; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.Data; import java.util.List; @Data public class ArticleVo { //Be sure to add, or there will be a loss of accuracy @JsonSerialize(using = ToStringSerializer.class) private Long id; private String title; private String summary; private Integer commentCounts; private Integer viewCounts; private Integer weight; /** * Creation time */ private String createDate; private String author; private ArticleBodyVo body; private List<TagVo> tags; private CategoryVo category; }
src/main/java/com/mszlu/blog/dao/pojo/ArticleTag.java
package com.mszlu.blog.dao.pojo; import lombok.Data; @Data public class ArticleTag { private Long id; private Long articleId; private Long tagId; }
9.3.4 testing
9.4. AOP log
IOC is one of the two core concepts of spring. IOC provides us with an IOCbean container, which will help us create objects automatically without manual creation. IOC is created through DI (Dependency Injection). We can write Java annotation code or XML configuration, Automatically inject some other beans that we want to inject into the object. It helps us inject through byName or byType. It is precisely because of Dependency Injection that IOC has the very powerful advantage of decoupling.
For example, if we want to inject beans like JdbcTemplate or SqlSessionFactory into the container, they need to rely on a data source. If we strongly couple the data sources of JdbcTemplate or Druid, it will lead to a problem. When we want to use Jdbc template, we must use Druid data source, Dependency injection can help us to rely on a DataSource interface instead of a specific implementation when injecting into the Jdbc. The advantage is that when we inject a druid data source into the container in the future, it will be automatically injected into the JdbcTemplate. If we inject another one, it is the same. For example, c3p0 is the same. In this way, the JdbcTemplate is completely decoupled from the data source and does not rely strongly on any data source. When spring starts, all beans will be created. In this way, the program does not need to create beans when running, and the running speed will be faster. In addition, when IOC manages beans, it is a singleton by default, It can save time and improve performance,
Understanding of spring IOC, AOP and MVC
Explanation of Springboot AOP log
The original method is not enhanced on the basis of the original method
src/main/java/com/mszlu/blog/common/aop/LogAnnotation.java
package com.mszlu.blog.common.aop; import java.lang.annotation.*; /** * Log annotation */ //ElementType. The type representative can be placed on the class, and the method representative can be placed on the method @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { String module() default ""; String operation() default ""; }
Adding this annotation means that we need log output for this interface
src/main/java/com/mszlu/blog/controller/ArticleController.java
@PostMapping //Adding this annotation means that the interface should be logged @LogAnnotation(module = "article",operation = "Get article list") public Result listArticle(@RequestBody PageParams pageParams){ return articleService.listArticle(pageParams); }
src/main/java/com/mszlu/blog/common/aop/LogAspect.java
package com.mszlu.blog.common.aop; import com.alibaba.fastjson.JSON; import com.mszlu.blog.utils.HttpContextUtils; import com.mszlu.blog.utils.IpUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; /** * @Author ljm * @Date 2021/10/18 21:01 * @Version 1.0 */ @Component @Aspect //Aspect defines the relationship between notification and pointcut @Slf4j public class LogAspect { @Pointcut("@annotation(com.mszlu.blog.common.aop.LogAnnotation)") public void pt(){ } //Around Advice @Around("pt()") public Object log(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); //Execution method Object result = point.proceed(); //Execution time (MS) long time = System.currentTimeMillis() - beginTime; //Save log recordLog(point, time); return result; } private void recordLog(ProceedingJoinPoint joinPoint, long time) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class); log.info("=====================log start================================"); log.info("module:{}",logAnnotation.module()); log.info("operation:{}",logAnnotation.operation()); //Requested method name String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); log.info("request method:{}",className + "." + methodName + "()"); // //Requested parameters Object[] args = joinPoint.getArgs(); String params = JSON.toJSONString(args[0]); log.info("params:{}",params); //Get request set IP address HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); log.info("ip:{}", IpUtils.getIpAddr(request)); log.info("excute time : {} ms",time); log.info("=====================log end================================"); } }
Method class used
src/main/java/com/mszlu/blog/utils/HttpContextUtils.java
package com.mszlu.blog.utils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * HttpServletRequest * */ public class HttpContextUtils { public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } }
src/main/java/com/mszlu/blog/utils/IpUtils.java
package com.mszlu.blog.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; /** * Get Ip * */ @Slf4j public class IpUtils { /** * Get IP address * <p> * If you use reverse proxy software such as Nginx, you cannot pass request Getremoteaddr() get IP address * If multi-level reverse proxy is used, the value of x-forward-for is not only one, but a string of IP addresses. The first valid IP string in x-forward-for that is not unknown is the real IP address */ public static String getIpAddr(HttpServletRequest request) { String ip = null, unknown = "unknown", seperator = ","; int maxLength = 15; try { ip = request.getHeader("x-forwarded-for"); if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } catch (Exception e) { log.error("IpUtils ERROR ", e); } // If you use a proxy, get the first IP address if (StringUtils.isEmpty(ip) && ip.length() > maxLength) { int idx = ip.indexOf(seperator); if (idx > 0) { ip = ip.substring(0, idx); } } return ip; } /** * Get ip address * * @return */ public static String getIpAddr() { HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); return getIpAddr(request); } }
The result is
ip address query is
bug fix
Prevent the obtained value from being null, because the obtained value is a millisecond value, which needs to be converted. Y represents the year and m represents the month. Rewrite the time.
Article archiving:
Description of correlation function
select FROM_UNIXTIME(create_date/1000,'%Y') as year, FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
10.1. Article image upload
10.1.1 interface description
Interface url: / upload
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
image
file
Name of uploaded file
Return data:
{ "success":true, "code":200, "msg":"success", "data":"https://static.mszlu.com/aa.png" }
Modify the pom file and introduce the sdk of qiniu cloud
pom.xml
<dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>[7.7.0, 7.7.99]</version> </dependency>
10.1.2 Controller
src/main/java/com/mszlu/blog/controller/UploadController.java
package com.mszlu.blog.controller; import com.mszlu.blog.utils.QiniuUtils; import com.mszlu.blog.vo.Result; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.util.UUID; @RestController @RequestMapping("upload") public class UploadController { @Autowired private QiniuUtils qiniuUtils; //https://blog.csdn.net/justry_deng/article/details/80855235 MultipartFile introduction @PostMapping public Result upload(@RequestParam("image")MultipartFile file){ //Original file name, such as AA png String originalFilename = file.getOriginalFilename(); //Unique file name String fileName = UUID.randomUUID().toString()+"."+StringUtils.substringAfterLast(originalFilename, "."); //Where to upload the file? Qiniu cloud server //Reduce the bandwidth consumption of our own application servers boolean upload = qiniuUtils.upload(file, fileName); if (upload) { return Result.success(QiniuUtils.url+fileName); } return Result.fail(20001,"Upload failed"); }
10.1.3 use qiniu cloud
Note the domain name of qiniu cloud test https://static.mszlu.com/ It's recycled once a month. Remember to modify it.
springboot only uploads 1M images by default, so modify the file configuration
src/main/resources/application.properties
# Maximum total uploaded files spring.servlet.multipart.max-request-size=20MB # Maximum value of a single file spring.servlet.multipart.max-file-size=2MB
Seven cattle cloud storage space tutorial
src/main/java/com/mszlu/blog/utils/QiniuUtils.java
package com.mszlu.blog.utils; import com.alibaba.fastjson.JSON; import com.qiniu.http.Response; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @Component public class QiniuUtils { public static final String url = "https://static.mszlu.com/"; //Modify the following two values, put them into Proprietaries, and obtain them in key management @Value("${qiniu.accessKey}") private String accessKey; @Value("${qiniu.accessSecretKey}") private String accessSecretKey; public boolean upload(MultipartFile file,String fileName){ //Construct a configuration class with a specified Region object Configuration cfg = new Configuration(Region.huabei()); //... Other parameters refer to class notes UploadManager uploadManager = new UploadManager(cfg); //... Generate the upload voucher, then prepare to upload, modify the upload name and create the space name for yourself (it's your own) String bucket = "mszlu"; //If the key is not specified by default, the hash value of the file content is used as the file name try { byte[] uploadBytes = file.getBytes(); Auth auth = Auth.create(accessKey, accessSecretKey); String upToken = auth.uploadToken(bucket); Response response = uploadManager.put(uploadBytes, fileName, upToken); //Analyze the result of successful upload DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class); return true; } catch (Exception ex) { ex.printStackTrace(); } return false; } }
10.1.4 testing
10.2. Navigation - article classification
10.2.1 query all article classifications
10.2.1.1 interface description
Interface url: / categories / detail
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 1, "avatar": "/static/category/front.png", "categoryName": "front end", "description": "What's the front end, big front end" }, { "id": 2, "avatar": "/static/category/back.png", "categoryName": "back-end", "description": "The best fork at the back" }, { "id": 3, "avatar": "/static/category/lift.jpg", "categoryName": "life", "description": "Interesting things in life" }, { "id": 4, "avatar": "/static/category/database.png", "categoryName": "database", "description": "The database doesn't work" }, { "id": 5, "avatar": "/static/category/language.png", "categoryName": "programing language", "description": "Many languages, which should I learn?" } ] }
src/main/java/com/mszlu/blog/vo/CategoryVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class CategoryVo { private Long id; private String avatar; private String categoryName; private String description; }
10.2.1.2 Controller
src/main/java/com/mszlu/blog/controller/CategoryController.java
@GetMapping("detail") public Result categoriesDetail(){ return categoryService.findAllDetail(); }
10.2.1.3 Service
src/main/java/com/mszlu/blog/service/CategoryService.java
Result findAllDetail();
src/main/java/com/mszlu/blog/service/impl/CategoryServiceImpl.java
@Override public Result findAllDetail() { List<Category> categories = categoryMapper.selectList(new LambdaQueryWrapper<>()); //Object of page interaction return Result.success(copyList(categories)); }
Article classification display
10.2.2 query all labels
10.2.2.1 interface description
Interface url: / tags/detail
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [ { "id": 5, "tagName": "springboot", "avatar": "/static/tag/java.png" }, { "id": 6, "tagName": "spring", "avatar": "/static/tag/java.png" }, { "id": 7, "tagName": "springmvc", "avatar": "/static/tag/java.png" }, { "id": 8, "tagName": "11", "avatar": "/static/tag/css.png" } ] }
10.2.2.3 Controller
src/main/java/com/mszlu/blog/vo/TagVo.java
package com.mszlu.blog.vo; import lombok.Data; @Data public class TagVo { private Long id; private String tagName; private String avatar; }
src/main/java/com/mszlu/blog/controller/TagsController.java
@GetMapping("detail") public Result findAllDetail(){ return tagService.findAllDetail(); }
10.2.2.4 Service
src/main/java/com/mszlu/blog/service/TagService.java
Result findAllDetail();
src/main/java/com/mszlu/blog/service/impl/TagServiceImpl.java
@Override public Result findAllDetail() { LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>(); List<Tag> tags = this.tagMapper.selectList(queryWrapper); return Result.success(copyList(tags)); }
Label display
10.3. Classified article list
10.3.1 interface description
Interface url: / category/detail/{id}
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
id
Classification id
Path parameters
Return data:
{ "success": true, "code": 200, "msg": "success", "data": { "id": 1, "avatar": "/static/category/front.png", "categoryName": "front end", "description": "What's the front end, big front end" } }
10.3.2 Controller
src/main/java/com/mszlu/blog/controller/CategoryController.java
@GetMapping("detail/{id}") public Result categoriesDetailById(@PathVariable("id") Long id){ return categoryService.categoriesDetailById(id); }
10.3.3 Service
src/main/java/com/mszlu/blog/service/CategoryService.java
Result categoryDetailById(Long id);
src/main/java/com/mszlu/blog/service/impl/CategoryServiceImpl.java
@Override public Result categoriesDetailById(Long id) { Category category = categoryMapper.selectById(id); //Convert to CategoryVo CategoryVo categoryVo = copy(category); return Result.success(categoryVo); }
After completing the above, it can only be said that the icon of article classification can be displayed
However, if you want to display all the attribution content of the back end, you have to search the queryWrapper in the article query list. When the article classification label is not null, add the query element of article classification label to modify the classification.
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result listArticle(PageParams pageParams) { /** * 1,Paging query of article database table */ Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize()); LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); //Add the classification id to the parameter of the query article. If it is not empty, add the classification condition if (pageParams.getCategoryId()!=null) { //and category_id=#{categoryId} queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId()); } //Whether to sort by placing it at the top or not, / / arrange it in reverse chronological order, which is equivalent to order by create_data desc queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); //Paging query usage https://blog.csdn.net/weixin_41010294/article/details/105726879 List<Article> records = articlePage.getRecords(); // To return the vo data defined by us is the corresponding front-end data. We should not only return the current data, but need further processing List<ArticleVo> articleVoList =copyList(records,true,true); return Result.success(articleVoList); }
src/main/java/com/mszlu/blog/vo/params/PageParams.java
package com.mszlu.blog.vo.params; import lombok.Data; @Data public class PageParams { private int page = 1; private int pageSize = 10; private Long categoryId; private Long tagId; }
Finally, you can display the content under each label of all article categories
10.4. Tag article list
10.4.1 interface description
Interface url: / tags/detail/{id}
Request method: GET
Request parameters:
Parameter name
Parameter type
explain
id
Tag id
Path parameters
Return data:
{ "success": true, "code": 200, "msg": "success", "data": { "id": 5, "tagName": "springboot", "avatar": "/static/tag/java.png" } }
10.4.2 Controller
src/main/java/com/mszlu/blog/controller/TagsController.java
@GetMapping("detail/{id}") public Result findADetailById(@PathVariable("id") Long id){ /** * Query all articles under all articles tab * @return */ return tagService.findADetailById(id); }
10.4.3 Service
src/main/java/com/mszlu/blog/service/TagService.java
Result findADetailById(Long id);
src/main/java/com/mszlu/blog/service/impl/TagServiceImpl.java
@Override public Result findDetailById(Long id) { Tag tag = tagMapper.selectById(id); TagVo copy = copy(tag); return Result.success(copy); }
Completing the above ensures that the article tag is displayed. We need to rewrite the article query interface to ensure that when we encounter label query, we can correctly query the content corresponding to the article tag, otherwise the content found by each tag is the same.
10.4.4 modify the original query article interface
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
//Add tag condition query //There is no tag field in article table. An article has multiple tags //articie_tog article_id 1: n tag_id //We need to use a new queryWrapper that belongs to the article tag to convert the article of this article_ Find tag and save it in a list. // Then select the tag we need according to the in method of queryWrapper. @Override public Result listArticle(PageParams pageParams) { /** * 1,Paging query of article database table */ Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize()); LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); if (pageParams.getCategoryId()!=null) { //and category_id=#{categoryId} queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId()); } List<Long> articleIdList = new ArrayList<>(); if(pageParams.getTagId()!=null){ //Add tag condition query //There is no tag field in article table. An article has multiple tags //articie_tog article_id 1: n tag_id //We need to use a new queryWrapper that belongs to the article tag to convert the article of this article_ Find tag and save it in a list. // Then select the tag we need according to the in method of queryWrapper. LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>(); articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId()); List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper); for (ArticleTag articleTag : articleTags) { articleIdList.add(articleTag.getArticleId()); } if (articleTags.size() > 0) { // and id in(1,2,3) queryWrapper.in(Article::getId,articleIdList); } } //Whether to sort by placing it at the top or not, / / arrange it in reverse chronological order, which is equivalent to order by create_data desc queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); //Paging query usage https://blog.csdn.net/weixin_41010294/article/details/105726879 List<Article> records = articlePage.getRecords(); // To return the vo data defined by us is the corresponding front-end data. We should not only return the current data, but need further processing List<ArticleVo> articleVoList =copyList(records,true,true); return Result.success(articleVoList); }
10.4.5 testing
The final result is as follows. Each label corresponds to the article corresponding to the label
11.1. List of archived articles
11.1.1 interface description
Interface url: / articles
Request method: POST
Request parameters:
Parameter name
Parameter type
explain
year
string
year
month
string
month
Return data:
{ "success": true, "code": 200, "msg": "success", "data": [Article list. The data is the same as the previous article list interface] }
mybatisplus hump naming and mapper XML usage
11.1.2 article list parameters
src/main/java/com/mszlu/blog/vo/params/PageParams.java
package com.mszlu.blog.vo.params; import lombok.Data; @Data public class PageParams { private int page = 1; private int pageSize = 10; private Long categoryId; private Long tagId; private String year; private String month; //Pass 6 and it becomes 06 public String getMonth(){ if (this.month != null && this.month.length() == 1){ return "0"+this.month; } return this.month; } }
11.1.3 using custom sql to implement Article List
src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java
@Override public Result listArticle(PageParams pageParams) { Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize()); IPage<Article> articleIPage = this.articleMapper.listArticle(page,pageParams.getCategoryId(),pageParams.getTagId(),pageParams.getYear(),pageParams.getMonth()); return Result.success(copyList(articleIPage.getRecords(),true,true)); } <resultMap id="articleMap" type="com.mszlu.blog.dao.pojo.Article"> <id column="id" property="id" /> <result column="author_id" property="authorId"/> <result column="comment_counts" property="commentCounts"/> <result column="create_date" property="createDate"/> <result column="summary" property="summary"/> <result column="title" property="title"/> <result column="view_counts" property="viewCounts"/> <result column="weight" property="weight"/> <result column="body_id" property="bodyId"/> <result column="category_id" property="categoryId"/> </resultMap> <!-- resultMap and resultType difference https://blog.csdn.net/xushiyu1996818/article/details/89075069?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-4.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-4.no_search_link--> <!--Hump nomenclature https://blog.csdn.net/A_Java_Dog/article/details/107006391?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.no_search_link--> <!-- Long categoryId,--> <!-- Long tagId,--> <!-- String year,--> <!-- String month--> <!--mybatis in xml File usage https://blog.csdn.net/weixin_43882997/article/details/85625805--> <!--dynamic sql https://www.jianshu.com/p/e309ae5e4a77--> <!--Hump naming https://zoutao.blog.csdn.net/article/details/82685918?spm=1001.2101.3001.6650.18&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-18.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-18.no_search_link--> <select id="listArticle" resultMap="articleMap"> select * from ms_article <where> 1 = 1 <if test="categoryId != null"> and category_id=#{categoryId} </if> <if test="tagId != null"> and id in (select article_id from ms_article_tag where tag_id=#{tagId}) </if> <if test="year != null and year.length>0 and month != null and month.length>0"> and (FROM_UNIXTIME(create_date/1000,'%Y') =#{year} and FROM_UNIXTIME(create_date/1000,'%m')=#{month}) </if> </where> order by weight,create_date desc </select>
11.1.4 testing
give the result as follows
11.2. Unified cache processing (optimized)
The access speed of memory is much higher than that of disk (from 1000 times)
Introduction to Spring Cache
src/main/java/com/mszlu/blog/common/cache/Cache.java
package com.mszlu.blog.common.cache; import java.lang.annotation.*; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Cache { long expire() default 1 * 60 * 1000; String name() default ""; }
src/main/java/com/mszlu/blog/common/cache/CacheAspect.java
package com.mszlu.blog.common.cache; import com.alibaba.fastjson.JSON; import com.mszlu.blog.vo.Result; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AliasFor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.time.Duration; @Aspect @Component @Slf4j public class CacheAspect { @Autowired private RedisTemplate<String, String> redisTemplate; @Pointcut("@annotation(com.mszlu.blog.common.cache.Cache)") public void pt(){} @Around("pt()") public Object around(ProceedingJoinPoint pjp){ try { Signature signature = pjp.getSignature(); //Class name String className = pjp.getTarget().getClass().getSimpleName(); //Method name called String methodName = signature.getName(); Class[] parameterTypes = new Class[pjp.getArgs().length]; Object[] args = pjp.getArgs(); //parameter String params = ""; for(int i=0; i<args.length; i++) { if(args[i] != null) { params += JSON.toJSONString(args[i]); parameterTypes[i] = args[i].getClass(); }else { parameterTypes[i] = null; } } if (StringUtils.isNotEmpty(params)) { //Encrypt to prevent the key from being too long and the character escape cannot be obtained params = DigestUtils.md5Hex(params); } Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes); //Get Cache annotation Cache annotation = method.getAnnotation(Cache.class); //Cache expiration time long expire = annotation.expire(); //Cache name String name = annotation.name(); //Get it from redis first String redisKey = name + "::" + className+"::"+methodName+"::"+params; String redisValue = redisTemplate.opsForValue().get(redisKey); if (StringUtils.isNotEmpty(redisValue)){ log.info("Go cache~~~,{},{}",className,methodName); return JSON.parseObject(redisValue, Result.class); } Object proceed = pjp.proceed(); redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire)); log.info("Store in cache~~~ {},{}",className,methodName); return proceed; } catch (Throwable throwable) { throwable.printStackTrace(); } return Result.fail(-999,"System error"); } }
use:
@PostMapping("hot") @Cache(expire = 5 * 60 * 1000,name = "hot_article") public Result hotArticle(){ int limit = 5; return articleService.hotArticle(limit); }
11.3. Think about other optimizations
- Articles can be put into es, which is convenient for subsequent Chinese word segmentation search. Spring boot tutorial has integration with ES
- The comment data can be put into mongodb and the e-commerce system. The comment data can be put into mongo
- For the number of readings and comments, consider adding the number of readings and comments to the redis incr self increment when the number of readings and comments increases, and use the scheduled task to regularly fix the data to the database
- In order to speed up the access speed, when deploying, you can put pictures, js, css, etc. into qiniu cloud storage to speed up the website access speed
Making a background and using spring security to make a permission system is of great help to the work
Register, record and deploy domain names
Management background
12.1. Construction project
12.1.1 new maven project blog admin
<?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-parent2</artifactId> <groupId>com.mszlu</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>blog-admin</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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> </project>
12.1.2 configuration
application.properties:
server.port=8889 spring.application.name=mszlu_admin_blog #Configuration of database # datasource 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 #mybatis-plus mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.global-config.db-config.table-prefix=ms_ # Maximum total uploaded files spring.servlet.multipart.max-request-size=20MB # Maximum value of a single file spring.servlet.multipart.max-file-size=2MB
Mybatis plus configuration:
package com.mszlu.blog.admin.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.mszlu.blog.admin.mapper") public class MybatisPlusConfig { //Paging plug-in @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }
12.1.3 startup
package com.mszlu.blog.admin; import com.alibaba.fastjson.annotation.JSONField; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AdminApp { public static void main(String[] args) { SpringApplication.run(AdminApp.class,args); } }
12.1.4 import front-end engineering
Put it into the static directory under resources, and the front-end project is listed in the data
12.1.5 new table
Background management user table
CREATE TABLE `blog`.`ms_admin` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
Permission table
CREATE TABLE `blog`.`ms_permission` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
Association table of users and permissions
CREATE TABLE `blog`.`ms_admin_permission` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `admin_id` bigint(0) NOT NULL, `permission_id` bigint(0) NOT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
12.2. Authority management
12.2.1 Controller
package com.mszlu.blog.admin.controller; import com.mszlu.blog.admin.model.params.PageParam; import com.mszlu.blog.admin.pojo.Permission; import com.mszlu.blog.admin.service.PermissionService; import com.mszlu.blog.admin.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("admin") public class AdminController { @Autowired private PermissionService permissionService; @PostMapping("permission/permissionList") public Result permissionList(@RequestBody PageParam pageParam){ return permissionService.listPermission(pageParam); } @PostMapping("permission/add") public Result add(@RequestBody Permission permission){ return permissionService.add(permission); } @PostMapping("permission/update") public Result update(@RequestBody Permission permission){ return permissionService.update(permission); } @GetMapping("permission/delete/{id}") public Result delete(@PathVariable("id") Long id){ return permissionService.delete(id); } } package com.mszlu.blog.admin.model.params; import lombok.Data; @Data public class PageParam { private Integer currentPage; private Integer pageSize; private String queryString; } package com.mszlu.blog.admin.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; @Data public class Permission { @TableId(type = IdType.AUTO) private Long id; private String name; private String path; private String description; }
12.2.2 Service
package com.mszlu.blog.admin.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.mszlu.blog.admin.mapper.PermissionMapper; import com.mszlu.blog.admin.model.params.PageParam; import com.mszlu.blog.admin.pojo.Permission; import com.mszlu.blog.admin.vo.PageResult; import com.mszlu.blog.admin.vo.Result; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class PermissionService { @Autowired private PermissionMapper permissionMapper; public Result listPermission(PageParam pageParam){ Page<Permission> page = new Page<>(pageParam.getCurrentPage(),pageParam.getPageSize()); LambdaQueryWrapper<Permission> queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(pageParam.getQueryString())) { queryWrapper.eq(Permission::getName,pageParam.getQueryString()); } Page<Permission> permissionPage = this.permissionMapper.selectPage(page, queryWrapper); PageResult<Permission> pageResult = new PageResult<>(); pageResult.setList(permissionPage.getRecords()); pageResult.setTotal(permissionPage.getTotal()); return Result.success(pageResult); } public Result add(Permission permission) { this.permissionMapper.insert(permission); return Result.success(null); } public Result update(Permission permission) { this.permissionMapper.updateById(permission); return Result.success(null); } public Result delete(Long id) { this.permissionMapper.deleteById(id); return Result.success(null); } } package com.mszlu.blog.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.admin.pojo.Permission; import java.util.List; public interface PermissionMapper extends BaseMapper<Permission> { } package com.mszlu.blog.admin.vo; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Result { private boolean success; private int code; private String msg; private Object data; public static Result success(Object data){ return new Result(true,200,"success",data); } public static Result fail(int code, String msg){ return new Result(false,code,msg,null); } } package com.mszlu.blog.admin.vo; import lombok.Data; import java.util.List; @Data public class PageResult<T> { private List<T> list; private Long total; }
12.2.3 testing
12.3. Security integration
12.3.1 add dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
12.3.2 configuration
package com.mszlu.blog.admin.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } public static void main(String[] args) { //Encryption policy MD5 unsafe rainbow table MD5 salt String mszlu = new BCryptPasswordEncoder().encode("mszlu"); System.out.println(mszlu); } @Override public void configure(WebSecurity web) throws Exception { super.configure(web); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //Turn on login authentication // .antMatchers("/user/findAll").hasRole("admin") / / the provider needs the role of admin .antMatchers("/css/**").permitAll() .antMatchers("/img/**").permitAll() .antMatchers("/js/**").permitAll() .antMatchers("/plugins/**").permitAll() .antMatchers("/admin/**").access("@authService.auth(request,authentication)") //Customize the service to realize real-time authority authentication .antMatchers("/pages/**").authenticated() .and().formLogin() .loginPage("/login.html") //Custom login page .loginProcessingUrl("/login") //Login processing interface .usernameParameter("username") //The key that defines the user name during login is username by default .passwordParameter("password") //Define the password key when logging in. The default is password .defaultSuccessUrl("/pages/main.html") .failureUrl("/login.html") .permitAll() //By not intercepting, it is determined by the previously configured path, which means that the interfaces related to the login form pass through .and().logout() //Exit login configuration .logoutUrl("/logout") //Exit login interface .logoutSuccessUrl("/login.html") .permitAll() //Exit login interface release .and() .httpBasic() .and() .csrf().disable() //csrf off if custom login needs to be closed .headers().frameOptions().sameOrigin(); } }
12.3.3 login authentication
package com.mszlu.blog.admin.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; @Data public class Admin { @TableId(type = IdType.AUTO) private Long id; private String username; private String password; } package com.mszlu.blog.admin.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.admin.mapper.AdminMapper; import com.mszlu.blog.admin.pojo.Admin; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import java.util.ArrayList; @Component @Slf4j public class SecurityUserService implements UserDetailsService { @Autowired private AdminService adminService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("username:{}",username); //When the user logs in, spring security forwards the request to this //Find the User according to the User name. There is no exception thrown. Assemble the User name, password and authorization list into the User object of springSecurity and return it Admin adminUser = adminService.findAdminByUserName(username); if (adminUser == null){ throw new UsernameNotFoundException("user name does not exist"); } ArrayList<GrantedAuthority> authorities = new ArrayList<>(); UserDetails userDetails = new User(username,adminUser.getPassword(), authorities); //The rest of the authentication is done by the framework return userDetails; } public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode("123456")); } } package com.mszlu.blog.admin.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blog.admin.mapper.AdminMapper; import com.mszlu.blog.admin.mapper.PermissionMapper; import com.mszlu.blog.admin.pojo.Admin; import com.mszlu.blog.admin.pojo.Permission; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class AdminService { @Autowired private AdminMapper adminMapper; @Autowired private PermissionMapper permissionMapper; public Admin findAdminByUserName(String username){ LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Admin::getUsername,username).last("limit 1"); Admin adminUser = adminMapper.selectOne(queryWrapper); return adminUser; } public List<Permission> findPermissionsByAdminId(Long adminId){ return permissionMapper.findPermissionsByAdminId(adminId); } } package com.mszlu.blog.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.admin.pojo.Admin; public interface AdminMapper extends BaseMapper<Admin> { } package com.mszlu.blog.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blog.admin.pojo.Permission; import java.util.List; public interface PermissionMapper extends BaseMapper<Permission> { List<Permission> findPermissionsByAdminId(Long adminId); }
12.3.4 authority authentication
package com.mszlu.blog.admin.service; import com.mszlu.blog.admin.mapper.AdminMapper; import com.mszlu.blog.admin.pojo.Admin; import com.mszlu.blog.admin.pojo.Permission; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.util.Collection; import java.util.List; @Service @Slf4j public class AuthService { @Autowired private AdminService adminService; public boolean auth(HttpServletRequest request, Authentication authentication){ //Authority authentication, request path String requestURI = request.getRequestURI(); log.info("request url:{}", requestURI); //true for release and false for interception Object principal = authentication.getPrincipal(); if (principal == null || "anonymousUser".equals(principal)){ //Not logged in return false; } UserDetails userDetails = (UserDetails) principal; String username = userDetails.getUsername(); Admin admin = adminService.findAdminByUserName(username); if (admin == null){ return false; } if (admin.getId() == 1){ //Think it's a super administrator return true; } List<Permission> permissions = adminService.findPermissionsByAdminId(admin.getId()); requestURI = StringUtils.split(requestURI,'?')[0]; for (Permission permission : permissions) { if (requestURI.equals(permission.getPath())){ log.info("Permission passed"); return true; } } return false; } } package com.mszlu.blog.admin.service; import org.springframework.security.core.GrantedAuthority; public class MySimpleGrantedAuthority implements GrantedAuthority { private String authority; private String path; public MySimpleGrantedAuthority(){} public MySimpleGrantedAuthority(String authority){ this.authority = authority; } public MySimpleGrantedAuthority(String authority,String path){ this.authority = authority; this.path = path; } @Override public String getAuthority() { return authority; } public String getPath() { return path; } } <?xml version="1.0" encoding="UTF-8" ?> <!--MyBatis configuration file--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mszlu.blog.admin.mapper.PermissionMapper"> <select id="findPermissionsByAdminId" parameterType="long" resultType="com.mszlu.blog.admin.pojo.Permission"> select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId}) </select> </mapper>
12.4. task
Add roles, users have multiple roles, and one role has multiple permissions
13. Summarize technical highlights
1,jwt + redis
Token token login mode, fast access authentication, session sharing and security
redis manages tokens and user information accordingly,
1. Further increase the security
2. The logged in user has made a cache
3. Flexibly control the expiration of users (renewal, kicking off the line, etc.)
2. threadLocal is used to save user information. Within the requested thread, the logged in user can be obtained at any time, and thread isolation is done
3. After using ThreadLocal, the value is deleted to prevent memory leakage (strong reference and weak reference are said in the interview. It's not obvious to let the interviewers share the JVM)
4. Thread safety - update table set value = newValue where id=1 and value=oldValue
5. Thread pool is widely used, with 7 core parameters (operations that have no impact on the current main business process are put into the thread pool for execution)
1. Log in and record the log
6. Key contents of authority system
7. Unified logging and cache processing
14. Front end
Find home first Vue, usually put the home page here
General folder of views
The components folder generally stores vue customized components
Generally, views uses various components
The router folder stores routes and jumps to different pages through different paths
store is generally used for storage
The utils folder is generally a tool class
Request is usually a request
api is the definition of some interfaces with the backend
Static pages generated after packaging dist folder
First look
Dev.env.config in config directory JS configuration backend access path
Deploy production environment
Look at the static directory. category is the image path
This is configured in the database
Look at the src directory again
api represents the definition of back-end interface access, including all back-end access interfaces
In the api folder JS as an example
import request from '@/request' export function getArticles(query, page) { return request({ url: '/articles',//Access path method: 'post',//Access method post //Transfer parameters data: { page: page.pageNumber, pageSize: page.pageSize, name: page.name, sort: page.sort, year: query.year, month: query.month, tagId: query.tagId, categoryId: query.categoryId } }) } export function getHotArtices() { return request({ url: '/articles/hot',//The name of the interface path can also be changed at will method: 'post'//If you want to change the access method to get, you can modify it directly }) } export function getNewArtices() { return request({ url: '/articles/new', method: 'post' }) } export function viewArticle(id) { return request({ url: `/articles/view/${id}`, method: 'post' }) } export function getArticlesByCategory(id) { return request({ url: `/articles/category/${id}`, method: 'post' }) } export function getArticlesByTag(id) { return request({ url: `/articles/tag/${id}`, method: 'post' }) } export function publishArticle(article,token) { return request({ headers: {'Authorization': token}, url: '/articles/publish', method: 'post', data: article }) } export function listArchives() { return request({ url: '/articles/listArchives', method: 'post' }) } export function getArticleById(id) { return request({ url: `/articles/${id}`, method: 'post' }) }
In login JS file
import request from '@/request' export function login(account, password) { const data = { account, password } return request({ url: '/login', method: 'post', data }) } export function logout(token) { return request({ headers: {'Authorization': token},//Get the token through headers on the back end url: '/logout', method: 'get' }) } export function getUserInfo(token) { return request({ headers: {'Authorization': token}, url: '/users/currentUser', method: 'get' }) } export function register(account, nickname, password) { const data = { account, nickname, password } return request({ url: '/register', method: 'post', data }) }
At home Vue folder
<template> <div id="home"> <el-container> <base-header :activeIndex="activeIndex"></base-header>//head <router-view class="me-container"/>//container <base-footer v-show="footerShow"></base-footer>//tail </el-container> </div> </template> <script> //Components corresponds to the components directory, and views corresponds to the views directory import BaseFooter from '@/components/BaseFooter' import BaseHeader from '@/views/BaseHeader' export default { name: 'Home', data (){ return { activeIndex: '/', footerShow:true } }, components:{ 'base-header':BaseHeader, 'base-footer':BaseFooter }, beforeRouteEnter (to, from, next){ next(vm => { vm.activeIndex = to.path }) }, beforeRouteUpdate (to, from, next) { if(to.path == '/'){ this.footerShow = true }else{ this.footerShow = false } this.activeIndex = to.path next() } } </script> <style> .me-container{ margin: 100px auto 140px; } </style>
Srccomponentsbasefooter. In the components folder Vue folder
<template> <el-footer class="me-area"> <div class="me-footer"> <p>Designed by <strong> <router-link to="/" class="me-login-design-color">Code God's Road</router-link> </strong> </p> </div> </el-footer> </template> <script> export default { name: 'BaseFooter', data() { return {} }, methods: {}, mounted() { } } </script> <style> .el-footer { min-width: 100%; box-shadow: 0 -2px 3px hsla(0, 0%, 7%, .1), 0 0 0 1px hsla(0, 0%, 7%, .1); position: absolute; bottom: 0; left: 0; z-index: 1024; } .me-footer { text-align: center; line-height: 60px; font-family: 'Open Sans', sans-serif; font-size: 18px; } .me-login-design-color { color: #5FB878 !important; } </style>
Corresponding to the bottom of the picture
srciewsBaseHeader.vue file header
<template> <el-header class="me-area"> <el-row class="me-header"> <el-col :span="4" class="me-header-left"> <router-link to="/" class="me-title"> <img src="../assets/img/logo.png" /> </router-link> </el-col> <el-col v-if="!simple" :span="16"> <el-menu :router=true menu-trigger="click" active-text-color="#5FB878" :default-active="activeIndex" mode="horizontal"> <el-menu-item index="/">home page</el-menu-item> <el-menu-item index="/category/all">Article classification</el-menu-item> <el-menu-item index="/tag/all">label</el-menu-item> <el-menu-item index="/archives">Article archiving</el-menu-item> <el-col :span="4" :offset="4"> <el-menu-item index="/write"><i class="el-icon-edit"></i>write an article</el-menu-item> </el-col> </el-menu> </el-col> <template v-else> <slot></slot> </template> <el-col :span="4"> <el-menu :router=true menu-trigger="click" mode="horizontal" active-text-color="#5FB878"> <template v-if="!user.login"> <el-menu-item index="/login"> <el-button type="text">Sign in</el-button> </el-menu-item> <el-menu-item index="/register"> <el-button type="text">register</el-button> </el-menu-item> </template> <template v-else> <el-submenu index> <template slot="title"> <img class="me-header-picture" :src="user.avatar"/>//Avatar acquisition </template> <el-menu-item index @click="logout"><i class="el-icon-back"></i>sign out</el-menu-item> </el-submenu> </template> </el-menu> </el-col> </el-row> </el-header> </template> <script> export default { name: 'BaseHeader', props: { activeIndex: String, simple: { type: Boolean, default: false } }, data() { return {} }, computed: { user() { let login = this.$store.state.account.length != 0 let avatar = this.$store.state.avatar return { login, avatar } } }, methods: { logout() { let that = this this.$store.dispatch('logout').then(() => { this.$router.push({path: '/'}) }).catch((error) => { if (error !== 'error') { that.$message({message: error, type: 'error', showClose: true}); } }) } } } </script> <style> .el-header { position: fixed; z-index: 1024; min-width: 100%; box-shadow: 0 2px 3px hsla(0, 0%, 7%, .1), 0 0 0 1px hsla(0, 0%, 7%, .1); } .me-title { margin-top: 10px; font-size: 24px; } .me-header-left { margin-top: 10px; } .me-title img { max-height: 2.4rem; max-width: 100%; } .me-header-picture { width: 36px; height: 36px; border: 1px solid #ddd; border-radius: 50%; vertical-align: middle; background-color: #5fb878; } </style>
Corresponding to the top of the picture
BaseHeader. logout in Vue essentially calls index. In the store folder JS file
There are a lot of materials at the front end of online learning, but if the knowledge learned is not systematic, only a superficial taste when encountering problems and no further research, it is difficult to achieve real technological improvement. hope This systematic technical system We have a direction reference.