This is the 24th day of my participation in the Novembermore Challenge.The final text challenge in 2021

Hello, Hello, I am gray little ape, a super bug writing program ape!

During the National Day, I made a personal blog website based on springboot+ VUE. Today, I will share the development process with you and teach you how to build a personal blog.

The full source code is now available on Gitee.

Remember ⭐star⭐!

Friends a key three ➕ concern! The grey Ape takes you to the highway 🎉🎉🎉**! **

⚡ Project directory ⚡

First, the overall idea of personal blog website project

Second, Java back-end interface development

(1) Database design

(2) Integrate MybatisPlus

(3) Unified result encapsulation

(4) Integrate ShirO + JWT to achieve security verification

(5) Global exception processing

(6) Entity verification

(7) Cross-domain problems

(8) Login interface development

(9) Blog interface development

First, the overall idea of personal blog website project

The design of the whole project is separated from the front and back ends. The back end uses SpringBoot+MybatisPlus design, and the front end uses Vue+ElementUI to build the page. Security verification and other operations are completed by Shiro security framework. Routing transmission is adopted in data interaction between front and back ends, and cross-domain problems are solved at the front and back ends. Blog to achieve login function, in the case of not logged in can only access the blog home page, in the logged state can achieve blog publishing and editing functions.

The entire blog home page of the blog uses a timeline layout, the first published articles will be displayed at the front; Blog editing also supports Markdown editor editing. Specific functions to achieve partners continue to look!

Second, Java back-end interface development

(1) Database design

In the database design is mainly two tables, a user information table and a blog information table,

The data ids in the blog information table correspond to the user ids. The detailed table structure is as follows:

(2) Integrate MybatisPlus

We usually use MyBatis for database operation. MybatisPlus is developed on the basis of MyBatis. My personal understanding is that it can directly read our database by combining MyBatis with reverse engineering. And automatically generate * mapper. XML, Dao, Service code, improve our development efficiency.

The steps to integrate MybatisPlus are as follows:

The first step is to import the required JAR packages

Here we need to import the jar packages MybatisPlus relies on, and since MybatisPlus involves automatic code generation, we also need to import Freemarker’s page template engine.

<! --mp--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> The < version > 3.2.0 < / version > < / dependency > <! -- Freemarker template engine dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector- Java </artifactId> <version>5.1.37</version> <scope> Runtime </scope> </dependency> <! <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> The < version > 3.2.0 < / version > < / dependency >Copy the code

Second, write the configuration file

Because we need to connect to the database, so of course we need to use the database connection driver, but also need to configure in the configuration file, specify our database driver, user name, password, database name these.

You also need to specify the XML file that MybatisPlus scans,

# configuration database information spring: a datasource: driver - class - name: com. Mysql.. JDBC driver url: JDBC: mysql: / / localhost: 3306 / vueblog? useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: Mybatis -plus: mapper-locations: classpath*:/mapper/** mapper.xmlCopy the code

Step 3: Enable mapper interface scanning and add paging plug-in

Here, we need to implement a PaginationInterceptor. The purpose of using this pagination plug-in is very simple, so that we can display the results in the form of pagination. The plugin is written under the MybatisPlusConfig class,

** It is also important to note that the @mapperscan (“”) annotation needs to be added to the class, passing in the name of the package to which we want to write the interface. The purpose of this interface is to execute the package that we want to turn into the interface that implements the class. Such as @ MapperScan (” com. Gyg. Mapper “)

/ * * * * / @ mybatisPlus Configuration Configuration @ EnableTransactionManagement @ MapperScan (com. Gyg. "mapper") / / specified into the interface implementation class in public Class MybatisPlusConfig {/** * public PaginationInterceptor * @return */ @bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); return paginationInterceptor; }}Copy the code

Step 4: Generate relevant code

To generate code through MybatisPlus, we have been given a utility class. Through this utility class, we can write our own parameters, and then we can automatically generate the relevant code.

The utility class is called CodeGenerator, and to use it we need to place it in the same directory as the SpringBoot boot class. Once running, enter the name of the table we want to generate the corresponding code for.

The code for the utility class is quite long, so I put it in Gitee.

Run this code generator we can automatically generate mapper, DAO, service and other content related to the data table!

Now that the database code is almost complete,

(3) Unified result encapsulation

Since all of our data needs to be returned to our front-end page in the form of JSON strings, we need a unified encapsulation of the returned results. Here we can define a custom wrapper class Result, so that we can return data in a uniform format.

The wrapper class typically returns three pieces of information:

  • Status code (for example, 200 indicates correct operation and 400 indicates abnormal operation)
  • Result message MSG
  • Result data

Global methods are also defined in the wrapper class to return different data in different states. The code for the wrapper class is as follows:

import lombok.Data; import java.io.Serializable; */ @data public class Result implements Serializable {private int code; Private String MSG; Private Object data; Public static Result success(Object data) {return success(200," operation succeeded ",data); } @param code @param data @return */ public static Result success(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; } public static Result fail(String msg) { return fail(400,msg,null); } public static Result fail(String msg, Object data) { return fail(400,msg,data); } public static Result fail(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; }}Copy the code

(4) Integrate ShirO + JWT to achieve security verification

When conducting security verification, I adopted the combination of Shiro + JWT, and the general verification idea is as follows:

After sending the login information, the front end authenticates it securely through Shiro’s Realm. If the authentication fails, an error message is returned to the front end. If the login information is verified, the user information is stored on the server, and then a token is generated according to the user ID through the jwtUtils utility class, and the token is put into the request header that returns the request, and carried to the browser. When the browser receives the request returned by the server, it will parse and obtain the token. The token is stored locally.

In this way, each time the browser sends a request to the server, the server also verifies each request to verify that the token returned by the browser is the same as the token saved on the server. If the same release for processing; If not, the error message is returned to the browser.

Attached is a diagram of the request process:

The classes used for security verification are:

  1. ShiroConfig: used to configure shiro authentication information
  2. AccountRealm: Verifies the login information returned by the browser
  3. JwtToken: Encapsulates and obtains the data in the token
  4. AccountProfile: a carrier for user information returned after login
  5. JwtFilter: JWT filter, used to filter browser requests

One of the code is more, I put it on Gitee, friends can get [source link]

(5) Global exception processing

No matter what kind of project development we are doing, global exception handling is a very good habit. For global exception handling, it can express our error messages in the simplest way, and there will not be a lot of error messages. For our convenience, HERE I declare a few error messages that are commonly encountered in projects.

@slf4j @RestControllerAdvice Public Class GlobalExceptionHandler {/** * runtime exception * @param e * @return */ @responseStatus (httpstatus.bad_request) @ExceptionHandler(value = runtimeException.class) public Result Handler (RuntimeException e) {log. The error (a runtime exception "-- -- -- -- -- -- -- -- -- -- > > >" + e); return Result.fail(e.getMessage()); } /** * Shiro is running abnormally * @param e * @return */ @responseStatus (httpstatus.unauthorized @ExceptionHandler(value = ShiroException.class) public Result handler(ShiroException e){ Log. error("shiro exception ---------->>>" + e); return Result.fail(401,e.getMessage(),null); } /** * Entity check exception * @param e * @return */ @responseStatus (httpstatus.bad_request @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result handler(MethodArgumentNotValidException E){log.error(" entity check exception ---------->>>" + e); BindingResult bindingResult = e.getBindingResult(); ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get(); return Result.fail(objectError.getDefaultMessage()); } @param e * @return */ @responseStatus (httpstatus.bad_request) @ExceptionHandler(value = IllegalArgumentException.class) public Result handler(IllegalArgumentException e){ Log. error(" assertion exception exception ---------->>>" + e); return Result.fail(e.getMessage()); }}Copy the code

(6) Entity verification

When submitting form data, we usually verify that the data cannot be empty or shorter than the specified value. This can be done in the front end by using JS plug-ins, but in the back end we can verify the data by using Hibernate Validatior.

Hibernate Validatior validation is already automatically integrated in SpringBoot, so we just need to use it directly in our code.

So we just need to add validation rules to the attributes of the entity, such as in the User instance class:

/** ** @author @data @equalSandHashCode (callSuper = false) @accessors (chain = true) @tablename ("m_user") public class User implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; @notblank (message = "username cannot be blank ") private String username; private String avatar; @notblank (message = "mailbox cannot be empty ") @email (message =" mailbox format is incorrect ") private String Email; private String password; private Integer status; private LocalDateTime created; private LocalDateTime lastLogin; }Copy the code

(7) Cross-domain problems

Because what we do is the separation of front and back end project, so will appear the same origin policy on request related issues, this needs us to solve problems of cross-domain, about cross domain in the front and rear end interaction to solve the problem, I wrote a blog, other children can go to see the a “SpringBoot and solve the problem of cross-domain Vue interaction”

The strategy to resolve cross-domain problems on the back end of SpringBoot is simple. Simply add a class CorsConfig and make it implement the WebMvcConfigurer interface, with the following code, which is usually copied directly during development.

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 CorsConfig implements WebMvcConfigurer {@override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); }}Copy the code

(8) Login interface development

The development idea of the login interface is very simple, is to receive the login information sent from the front end and verify whether it passes. At the same time, there is an interface for logging out. The user’s information is passed in to determine whether the login state can be realized when logging out.

The code is as follows;

@RestController public class AccountController { @Autowired UserService userService; @Autowired JwtUtils jwtUtils; @PostMapping("/login") public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse Response) {system.out.println (" Username and password :" + logindto.getUsername () + "" + logindto.getPassword ()); Subject Subject = securityutils.getSubject (); // Encapsulate the username and password UsernamePasswordToken Token = new UsernamePasswordToken(logindto.getUsername (), logindto.getPassword ()); System.out.println(" Encapsulate username and password successfully!! ") ); Try {// Use shiro to authenticate the user subject. Login (token); User User = userservice.getone (new QueryWrapper<User>().eq("username", logindto.getUsername ())); Assert.notNull(user, "user does not exist!" ); if (! User.getpassword ().equals(logindto.getPassword ())) {return result.fail (" Password error!" ); } // Generate a JWT String based on the user ID JWT = jwTutils.generateToken (user.getid ()); // write JWT to response.setHeader("authorization", JWT); response.setHeader("Access-Control-Expose-Headers", "authorization"); Return result.success (maputil.builder ().put("id", user.getid ()).put("username", user.getUsername()) .put("avatar", user.getAvatar()) .put("email", user.getEmail()) .map() ); } Catch (UnknownAccountException e) {return result. fail(" User does not exist 2"); } the catch (IncorrectCredentialsException e) {return Result. The fail (" password incorrect 2 "); }} /** * @return */ @requiresAuthentication @getMapping ("/logout") public Result logout() {Subject Subject = SecurityUtils.getSubject(); // AccountProfile profile = (AccountProfile) subject.getPrincipal(); // System.out.println(profile.getId()); Logout subject.logout(); Return result. success(" exit successfully "); } @RequiresAuthentication @GetMapping("/testlogin") public Result testlogin() { User user = userService.getById(1L); return Result.success(user); }}Copy the code

(9) Blog interface development

The main functions of blog interface are: return home page information, return the specified blog information, edit and publish blog, delete blog function, edit and delete blog only in the login state can request success, the other two requests do not need to log in.

The code is as follows:

/** * @author @restController // @requestMapping ("/blog") public class BlogController {@autoWired BlogService blogService; ** @param currentPage * @return */ @getMapping ("/blogs") public Result List (@requestParam (defaultValue =) "1") Integer currentPage) { Page page = new Page(currentPage, 5); AccountProfile accountProfile = (AccountProfile) SecurityUtils.getSubject().getPrincipal(); System.out.println(accountProfile); IPage<Blog> pageDate = blogService.page(page, new QueryWrapper<Blog>().orderByDesc("created")); return Result.success(pageDate); ** @param id * @return */ @getMapping ("/blog/{id}") public Result detail(@pathvariable (name = "id")) long id) { Blog blog = blogService.getById(id); // Use assertions to determine if the article cannot be found assert. notNull(blog, "This blog has been removed!" ); Return result.success (blog); } /** * @param blog * @return */ / @requiresAuthentication @postMapping ("/blog/edit") public Result Edit (@validated @requestBody Blog Blog) {system.out.println (" Edit test 1111111111111111111 "); System.out.println(blog.toString()); System.out.println(" Current user ID: "+ shiroutil.getProfile ().getid ()); System.out.println(blog.toString()); // system.out.println (" current user ID: "+ Shiroutil.getsubjectid ()); Blog temp = null; // If the blog id is not empty, edit if (blog.getid ()! = null) { temp = blogService.getById(blog.getId()); // Each user can only edit his own article assert.istrue (temp.getUserId().equals(shiroutil.getprofile ().getid ())); } else {// If id is empty, add temp = new Blog(); // Add this article to the current user's ID temp.setUserId(shiroutil.getProfile ().getid ()); // Temp. SetCreated (localDatetime.now ()); temp.setStatus(0); } / / two object replication, specify the fields don't copy / / BeanUtil copyProperties (" conversion in front of the class ", "conversion after class"); BeanUtil.copyProperties(blog, temp, "id", "userId", "created", "status"); // saveOrUpdate this article blogservice.saveorupdate (temp); Return result. success(" operation succeeded "); } /** * delete blog * @param ID * @return */ @requiresAuthentication @postMapping ("/blog/delete/{ID}") public Result deleteBlog(@PathVariable("id") long id){ System.out.println(id); System.out.println("------------"); // int bid = Integer.parseInt(id); boolean isRemove = blogService.removeById(id); if (! IsRemove){return result. fail(" Delete failed!" ); } return result. success(" delete successfully! ") ); }}Copy the code

The above is the whole process of our background interface development. After the completion of development, we need to carry out the relevant interface test. After the completion of the test, we can carry out the development of the front page.

The last

Project source code I put in gitee, [source link], friends don’t forget ⭐star⭐ yo!

** a key three even add attention! Grey Ape is taking you to the highway! * * ✨ ✨ ✨

I’m Grey Ape, and I’ll see you next time!