Start developing blog admin module Chapter 1
Build table
Create a table according to the permission table relationship in Chapter 003 The sql file will be uploaded later
Generate basic addition, deletion, modification and query according to the code generator
The basic coding is not written here. There is no technical content and it takes a lot of time
The code generator uses the code generator provided by Renren open source, Everyone open source , others can be viewed by clicking the link
Click the link, and then click Ren generator
Download code
Unzip the downloaded package, and then copy the code to the project
Note that the copied path, like other projects, is a sub project
It should be noted that
- After the project is copied in, the probability idea still cannot be recognized as maven project. As before, click add as maven project
- Note the version of JDK. I use version 11, so I need to modify it. Most people may use jdk1 8 if this version is used, it does not need to be modified. The code generator also uses 1.8
- Because our parent project is only a project used for aggregation and does not have inherited functions (I think dependency management is too cumbersome), the default code generator pom file depends on the parent project to obtain dependencies from the local warehouse instead of the parent project
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- Newly added --> </parent>
Code generator structure
We mainly modify two, the first is application YML, the second is generator Properties, these two files. If you don't understand the contents of the file, you can only say what we need to modify
application.yml
- Considering the port number, 80 is used by default. It is recommended to change it to other port numbers. I change it here to 12001
- The database I use is the mysql database, so I definitely need to modify the user name and password of the database. Others will not be modified. Change the user name and password to the account password of my own database
generator.properties
This file is mainly the package name and author of the generated code. Check it yourself
I modify it here as follows:
- mainPath=com. amnesia. my_ The path of blog is the same for all modules
- package=com.amnesia.my_blog package name
- moduleName=admin is changed to its own module name. At present, it is admin, and the subsequent core modules will be changed to core
- author=amnesia author name
- email=1211441748@qq.com Mailbox
- tablePrefix = # table prefix (the class name will not contain the table prefix). The default is tablePrefix=tb_, Because the table we built has no prefix, we need to change the table prefix to empty in this place
Other information is type conversion information, which is not recommended to be modified
The third file that needs to be modified is the controller under the template file that needs to be modified in this project java. VM file, remove all the annotations about shiro and the imported classes. The current permission framework of this project uses spirng security - import org.apache.shiro.authz.annotation.RequiresPermissions;
- @RequiresPermissions(" m o d u l e N a m e : {moduleName}: moduleName:{pathName}:list")
- @RequiresPermissions(" m o d u l e N a m e : {moduleName}: moduleName:{pathName}:info")
- @RequiresPermissions(" m o d u l e N a m e : {moduleName}: moduleName:{pathName}:save")
- @RequiresPermissions(" m o d u l e N a m e : {moduleName}: moduleName:{pathName}:update")
- @RequiresPermissions("
m
o
d
u
l
e
N
a
m
e
:
{moduleName}:
moduleName:{pathName}:delete")
To delete the above 6 lines, you can copy them directly and find and delete them in the file If the subsequent permission framework uses shiro or directly uses the background management template of Renren open source, the third file may not be modified
After the above changes, you can start the code generator project
The above database connection must be correct, or the content will be empty after the project is started
Start, access
Click Ren Ren fast on the left and the multi selection box on the right to select the table. If a page is incomplete, you can click the bottom box and change it to 30,50100 per page. Click generate code to download the generated code
Unzip the downloaded file directory as shown in the figure above The sql file can be ignored. It is mainly about shiro permissions. It is not used in the current project. The main folder is the most common directory structure. As for the views file under src, it is mainly vue components, which will be used in the front-end page of development later I won't introduce it here, and I don't use these components. You can delete them
Copy all the files in the admin file to the project. At this time, the project will report a large number of errors. Now deal with these errors
Dealing with problems
Add a common dependency to the pom file. This process is generally handled when creating a project
<dependency> <groupId>com.amnesia.my_blog.common</groupId> <artifactId>blog-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
Create a uniform return result object R in the common project
Object code R
import org.apache.http.HttpStatus; import java.util.HashMap; import java.util.Map; public class R<T> extends HashMap<String, Object> { private static final long serialVersionUID = 1L; private T data; public T getData(){ return data; } public void setData(T data){ this.data = data; } public R() { put("code", 0); put("msg", "success"); } public static R error() { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unknown exception, please contact the administrator"); } public static R error(String msg) { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg); } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Map<String, Object> map) { R r = new R(); r.putAll(map); return r; } public static R ok() { return new R(); } @Override public R put(String key, Object value) { super.put(key, value); return this; } public Integer getCode(){ return (Integer) this.get("code"); } }
The Http status code is used, so the following dependencies need to be introduced into the pom file under the common module, and the R file will no longer report errors
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.13</version> <scope>compile</scope> </dependency>
Create a paging tool class in the common project
PageUtils object code
import com.baomidou.mybatisplus.core.metadata.IPage; import java.io.Serializable; import java.util.List; public class PageUtils implements Serializable { private static final long serialVersionUID = 1L; /** * Total records */ private int totalCount; /** * Records per page */ private int pageSize; /** * PageCount */ private int totalPage; /** * Current number of pages */ private int currPage; /** * List data */ private List<?> list; /** * paging * @param list List data * @param totalCount Total records * @param pageSize Records per page * @param currPage Current number of pages */ public PageUtils(List<?> list, int totalCount, int pageSize, int currPage) { this.list = list; this.totalCount = totalCount; this.pageSize = pageSize; this.currPage = currPage; this.totalPage = (int)Math.ceil((double)totalCount/pageSize); } /** * paging */ public PageUtils(IPage<?> page) { this.list = page.getRecords(); this.totalCount = (int)page.getTotal(); this.pageSize = (int)page.getSize(); this.currPage = (int)page.getCurrent(); this.totalPage = (int)page.getPages(); } /** * Total number * @return */ public int getTotalCount() { return totalCount; } public void setTotalCount(int totalCount) { this.totalCount = totalCount; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getCurrPage() { return currPage; } public void setCurrPage(int currPage) { this.currPage = currPage; } public List<?> getList() { return list; } public void setList(List<?> list) { this.list = list; } }
The paging interface of mybatis plus is used. Therefore, the dependency of mybatis plus is introduced into the pom file. The second dependency is mysql driven dependency. I use version 8 here, so I need to formulate a version This place mainly considers the path of mysql driver package after version 8.0. If there is a problem, Baidu will not report an error in the PageUtils file
<!-- Import mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency>
Create a Query object in the common project
Query object code
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.beecome.common.xss.SQLFilter; import org.apache.commons.lang.StringUtils; import java.util.Map; /** * Query parameters * * @author Mark sunlightcs@gmail.com */ public class Query<T> { public IPage<T> getPage(Map<String, Object> params) { return this.getPage(params, null, false); } public IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) { //Paging parameters long curPage = 1; long limit = 10; if(params.get(Constant.PAGE) != null){ curPage = Long.parseLong((String)params.get(Constant.PAGE)); } if(params.get(Constant.LIMIT) != null){ limit = Long.parseLong((String)params.get(Constant.LIMIT)); } //Paging object Page<T> page = new Page<>(curPage, limit); //Paging parameters params.put(Constant.PAGE, page); //sort field //Prevent SQL injection (because sidx and order are sorted by splicing SQL, there will be SQL injection risk) String orderField = SQLFilter.sqlInject((String)params.get(Constant.ORDER_FIELD)); String order = (String)params.get(Constant.ORDER); //Front end field sorting if(StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)){ if(Constant.ASC.equalsIgnoreCase(order)) { return page.addOrder(OrderItem.asc(orderField)); }else { return page.addOrder(OrderItem.desc(orderField)); } } //If there is no sort field, no sort is performed if(StringUtils.isBlank(defaultOrderField)){ return page; } //Default sort if(isAsc) { page.addOrder(OrderItem.asc(defaultOrderField)); }else { page.addOrder(OrderItem.desc(defaultOrderField)); } return page; }
Query object uses other classes, so create Constant class, SQLFilter class,
Constant object code
public class Constant { /** Super administrator ID */ public static final int SUPER_ADMIN = 1; /** * Current page number */ public static final String PAGE = "page"; /** * Number of records per page */ public static final String LIMIT = "limit"; /** * sort field */ public static final String ORDER_FIELD = "sidx"; /** * sort order */ public static final String ORDER = "order"; /** * Ascending order */ public static final String ASC = "asc"; /** * Menu type * * @author chenshun * @email sunlightcs@gmail.com * @date 2016 November 15, 2014 1:24:29 PM */ public enum MenuType { /** * catalogue */ CATALOG(0), /** * menu */ MENU(1), /** * Button */ BUTTON(2); private int value; MenuType(int value) { this.value = value; } public int getValue() { return value; } } /** * Scheduled task status * * @author chenshun * @email sunlightcs@gmail.com * @date 2016 12:07:22 am, December 3 */ public enum ScheduleStatus { /** * normal */ NORMAL(0), /** * suspend */ PAUSE(1); private int value; ScheduleStatus(int value) { this.value = value; } public int getValue() { return value; } } /** * Cloud service provider */ public enum CloudService { /** * Seven cattle cloud */ QINIU(1), /** * Alibaba cloud */ ALIYUN(2), /** * Tencent cloud */ QCLOUD(3); private int value; CloudService(int value) { this.value = value; } public int getValue() { return value; } } }
SQLFilter object code
import com.amnesia.my_blog.common.exception.RRException; import org.apache.commons.lang.StringUtils; /** * SQL filter * * @author Mark sunlightcs@gmail.com */ public class SQLFilter { /** * SQL Injection filtration * @param str String to be verified */ public static String sqlInject(String str){ if(StringUtils.isBlank(str)){ return null; } //Remove the '| | | \ character str = StringUtils.replace(str, "'", ""); str = StringUtils.replace(str, "\"", ""); str = StringUtils.replace(str, ";", ""); str = StringUtils.replace(str, "\\", ""); //Convert to lowercase str = str.toLowerCase(); //Illegal character String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; //Determine whether illegal characters are included for(String keyword : keywords){ if(str.indexOf(keyword) != -1){ throw new RRException("Contains illegal characters"); } } return str; }
The SQLFilter object uses other classes, so the RRException class is created
RRException object code
public class RRException extends RuntimeException { private static final long serialVersionUID = 1L; private String msg; private int code = 500; public RRException(String msg) { super(msg); this.msg = msg; } public RRException(String msg, Throwable e) { super(msg, e); this.msg = msg; } public RRException(String msg, int code) { super(msg); this.msg = msg; this.code = code; } public RRException(String msg, int code, Throwable e) { super(msg, e); this.msg = msg; this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }
The above also uses the StringUtils class, which is in commons Lang, so import dependencies in pom
<dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> <scope>compile</scope> </dependency>
SQLFilter class will no longer report errors, Query class will no longer report errors, and other classes will no longer report errors
Custom exception handling
However, an RRException class was created during the above writing process, but it is actually useless at present, because this class inherits the runtime exception class. This class mainly handles runtime exceptions and corresponds to the processing results, but it is not intercepted now
Therefore, it is necessary to use @ ControllerAdvice to handle the exceptions of the methods annotated by @ RequestMapping in the controller to realize custom exception handling. Therefore, the common module needs to modify the springboot launcher to a web launcher,
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> Change to <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
Therefore, it can be found that common can actually be modified into the inheritance project of the whole project. All projects obtain dependencies from this project, but individuals prefer to centralize the dependencies and do not need to remove them through tags. Therefore, those with obsessive-compulsive disorder in this place can modify the project structure themselves
Create the project's exception class BlogException
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor //Generate parametric construction method @NoArgsConstructor //Generate parameterless constructs public class BlogException extends RuntimeException { private Integer code;//Status code private String msg;//Abnormal information }
Create global exception class
Abnormal conditions will be matched from top to bottom, and the matching will stop and continue downward
import com.amnesia.my_blog.common.utils.R; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Slf4j public class GlobalExceptionHandler { //Specific exception arithmeticexception class @ExceptionHandler(ArithmeticException.class) @ResponseBody //To return data public R error(ArithmeticException e) { e.printStackTrace(); return R.error(); } //Custom exception @ExceptionHandler(BlogException.class) @ResponseBody //To return data public R error(BlogException e) { log.error(e.getMessage()); e.printStackTrace(); return R.error().put("code",e.getCode()).put("msg",e.getMsg()); } //Custom exception @ExceptionHandler(RRException.class) @ResponseBody //To return data public R error(RRException e) { log.error(e.getMessage()); e.printStackTrace(); return R.error().put("code",e.getCode()).put("msg",e.getMsg()); } //All exceptions class @ExceptionHandler(Exception.class) @ResponseBody //To return data public R error(Exception e) { log.error(e.getMessage()); e.printStackTrace(); return R.error().put("code",-1).put("msg","Program exception"); } }
Create the basic configuration file application yml
server: port: 12100 spring: application: name: blog-admin jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 profiles: active: dev mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: classpath:/mapper/**/*.xml
Create the development environment configuration file application-dev.yml
Note the connection driver of mysql, and the difference between version 5 and version 8
spring: datasource: url: jdbc:mysql://192.168.2.200:3306/mfll_admin username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
Start the project and find no other problems
Configure knife4j
Add dependencies in the pom file of the commonm module
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.7</version> </dependency>
Create knife4j configuration class
Knife4jConfig object code
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; /** * @author Amnesia * @date 2021/7/31 6:25 afternoon */ @Configuration @EnableSwagger2WebMvc public class Knife4jConfig { @Bean public Docket defaultApi2() { Docket docket=new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .description("Blog interface document") .termsOfServiceUrl("www.amnesia.top") .contact( new Contact("amnesia","www.amnesia.top","1211441748@qq.com")) .version("1.0") .build()) //Group name .groupName("2.X edition") .select() //The Controller scan package path is specified here .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); return docket; } }
Then add @ ComponentScan("com.amnesia.my_blog") on the startup class