SpringBoot 前后端分离开发项目

文章总体分为3大部分,Java后端接口和vue前端页面,接口联调。

从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等。所以一般以Springboot作为我们的框架基础,这是离不开的了。

然后数据层,我们常用的是Mybatis,易上手,方便维护。但是单表操作比较困难,特别是添加字段或减少字段的时候,比较繁琐,所以这里我推荐使用Mybatis Plus(https://mp.baomidou.com/),为简化开发而生,只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。

作为一个项目骨架,权限也是我们不能忽略的,Shiro配置简单,使用也简单,所以使用Shiro作为我们的的权限。

考虑到项目可能需要部署多台,这时候我们的会话等信息需要共享,Redis是现在主流的缓存中间件,也适合我们的项目。

然后因为前后端分离,所以我们使用jwt作为我们用户身份凭证。

ok,我们现在就开始搭建我们的项目脚手架!

技术栈:

  • SpringBoot
  • mybatis plus
  • shiro
  • lombok
  • redis
  • hibernate validatior
  • jwt

目录

  • SpringBoot 前后端分离开发项目
  • 一、Springboot Maven 配置
  • 二、yml开发配置
  • 三、创建数据表和默认数据
  • 四、代码自动生成
  • 五、统一消息响应结果封装
  • 六、整合shiro+jwt,并会话共享
  • 七、 登录权限校验
  • 八、JwtToken、 JwtUtils 和 JwtFilter
  • 九、全局异常处理
  • 十、配置
  • 十一、登录登出接口开发
  • 十二、博客接口开发
  • 十三、Vue前端页面开发
  • 十四、总结

一、Springboot Maven 配置

porm.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.damiao</groupId><artifactId>article</artifactId><version>0.0.1-SNAPSHOT</version><name>article</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.3.7.RELEASE</spring-boot.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-devtools</artifactId>-->
<!--            <scope>runtime</scope>-->
<!--            <optional>true</optional>-->
<!--        </dependency>--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><!--mp--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!--mp代码生成器--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.2</version></dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-core</artifactId><version>5.0.7.Final</version></dependency><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis-spring-boot-starter</artifactId><version>3.2.1</version></dependency><!-- hutool工具类--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.5.1</version></dependency><!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>2.7.0</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.7.RELEASE</version><configuration><mainClass>com.article.ArticleApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>

二、yml开发配置

application.xml

# DataSource Config
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/article?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghaiusername: rootpassword: root
mybatis-plus:mapper-locations: classpath*:/mapper/**Mapper.xml
server:port: 8081
shiro-redis:enabled: trueredis-manager:host: 127.0.0.1:6379start-cmd: redis-server.exe redis.windows.confhelp-doc: https://blog.csdn.net/qq_38220334/article/details/105527236cmd-path: D:\Program Files\Redis
markerhub:jwt:secret: f4e2e52034348f86b67cde581c0f9eb5expire: 604800header: Authorization

三、创建数据表和默认数据

CREATE TABLE `m_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(64) DEFAULT NULL,`avatar` varchar(255) DEFAULT NULL,`email` varchar(64) DEFAULT NULL,`password` varchar(64) DEFAULT NULL,`status` int(5) NOT NULL,`created` datetime DEFAULT NULL,`last_login` datetime DEFAULT NULL,PRIMARY KEY (`id`),KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `m_blog` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` bigint(20) NOT NULL,`title` varchar(255) NOT NULL,`description` varchar(255) NOT NULL,`content` longtext,`created` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,`status` tinyint(4) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
INSERT INTO `vueblog`.`m_user` (`id`, `username`, `avatar`, `email`, `password`, `status`, `created`, `last_login`) VALUES ('1', 'markerhub', 'https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg', NULL, '96e79218965eb72c92a549dd5a330112', '0', '2020-04-20 10:44:01', NULL);

四、代码自动生成

package com.damiao.article;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;import java.util.ArrayList;
import java.util.Scanner;public class CodeGenerator {public static String scanner(String tip) {Scanner scanner = new Scanner(System.in);StringBuilder help = new StringBuilder();help.append("请输入" + tip + ":");System.out.println(help.toString());if (scanner.hasNext()) {String ipt = scanner.next();if (StringUtils.isNotBlank(ipt)) {return ipt;}}throw new MybatisPlusException("请输入正确的" + tip + "!");}public static void main(String[] args) {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java");//设置代码生成路径gc.setFileOverride(true);//是否覆盖以前文件gc.setOpen(false);//是否打开生成目录gc.setAuthor("damiao");//设置项目作者名称gc.setIdType(IdType.AUTO);//设置主键策略gc.setBaseResultMap(true);//生成基本ResultMapgc.setBaseColumnList(true);//生成基本ColumnListgc.setServiceName("%sService");//去掉服务默认前缀gc.setDateType(DateType.ONLY_DATE);//设置时间类型mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/article?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("root");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setParent("com.damiao");pc.setMapper("mapper");pc.setXml("mapper.xml");pc.setEntity("pojo");pc.setService("service");pc.setServiceImpl("service.impl");pc.setController("controller");mpg.setPackageInfo(pc);// 策略配置StrategyConfig sc = new StrategyConfig();sc.setNaming(NamingStrategy.underline_to_camel);sc.setColumnNaming(NamingStrategy.underline_to_camel);sc.setEntityLombokModel(true);//自动lomboksc.setRestControllerStyle(true);sc.setControllerMappingHyphenStyle(true);sc.setLogicDeleteFieldName("deleted");//设置逻辑删除//设置自动填充配置TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE);ArrayList<TableFill> tableFills=new ArrayList<>();tableFills.add(gmt_create);tableFills.add(gmt_modified);sc.setTableFillList(tableFills);//乐观锁sc.setVersionFieldName("version");sc.setRestControllerStyle(true);//驼峰命名//  sc.setTablePrefix("tbl_"); 设置表名前缀sc.setInclude(scanner("表名,多个英文逗号分割").split(","));mpg.setStrategy(sc);// 生成代码mpg.execute();}}

五、统一消息响应结果封装

package com.damiao.common.lang;import lombok.Data;import java.io.Serializable;@Data
public class Result implements Serializable {private int code; // 200 是正常 非200 表示异常private String msg;private Object data;public static Result succ(Object data){return succ(200,"操作成功",data);}public static Result succ(int code, String msg, Object data){Result r = new Result();r.setCode(code);r.setData(data);r.setMsg(msg);return r;}public static Result fail(String msg){return fail(400, msg, "");}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.setData(data);r.setMsg(msg);return r;}
}

六、整合shiro+jwt,并会话共享

package com.damiao.article.config;import com.damiao.shiro.AccountRealm;
import com.damiao.shiro.JwtFilter;
import org.apache.shiro.mgt.SessionsSecurityManager;  // SecurityManager
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sun.security.krb5.Realm;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;@Configuration
public class ShiroConfig {@AutowiredJwtFilter jwtFilter;@Beanpublic SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();// inject redisSessionDAOsessionManager.setSessionDAO(redisSessionDAO);// other stuff...return sessionManager;}@Beanpublic SessionsSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager, RedisCacheManager redisCacheManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);//inject sessionManagersecurityManager.setSessionManager(sessionManager);// inject redisCacheManagersecurityManager.setCacheManager(redisCacheManager);// other stuff...return securityManager;}@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限chainDefinition.addPathDefinitions(filterMap);return chainDefinition;}@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);Map<String, Filter> filters = new HashMap<>();filters.put("jwt", jwtFilter);shiroFilter.setFilters(filters);Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}}

七、 登录权限校验

AccountRealm

package com.damiao.shiro;import com.damiao.pojo.MUser;
import com.damiao.service.MUserService;
import com.damiao.util.JwtUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class AccountRealm extends AuthorizingRealm {@AutowiredJwtUtils jwtUtils;@AutowiredMUserService mUserService;@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {JwtToken jwtToken = (JwtToken) token;// 获取 userId 查询出 用户 信息String userId = jwtUtils.getClaimByToken( (String) jwtToken.getPrincipal() ).getSubject();MUser mUser =  mUserService.getById(Long.valueOf(userId));// 如果 该用户为空if(mUser == null){throw new UnknownAccountException("账户不存在");}// 如果该账户被锁定if(mUser.getStatus() == -1){throw new LockedAccountException("账户已被锁定");}AccountProfile profile = new AccountProfile();BeanUtils.copyProperties(mUser, profile);return new SimpleAuthenticationInfo(profile,jwtToken.getCredentials(),getName());}
}

AccountProfile

package com.damiao.shiro;import lombok.Data;import java.io.Serializable;
import java.util.Date;@Data
public class AccountProfile implements Serializable {private Long id;private String username;private String avatar;private String email;private Date created;
}

八、JwtToken、 JwtUtils 和 JwtFilter

JwtToken

// JwtToken
package com.damiao.shiro;import org.apache.shiro.authc.AuthenticationToken;public class JwtToken implements AuthenticationToken {private String token;public JwtToken(String jwt){this.token = jwt;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

JwtUtils

package com.damiao.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Date;/*** jwt工具类*/
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "markerhub.jwt")
public class JwtUtils {private String secret;private long expire;private String header;/*** 生成jwt token*/public String generateToken(long userId) {Date nowDate = new Date();//过期时间Date expireDate = new Date(nowDate.getTime() + expire * 1000);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(userId+"").setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch (Exception e){log.debug("validate is token error ", e);return null;}}/*** token是否过期* @return  true:过期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}
}

JwtFilter

package com.damiao.shiro;import cn.hutool.json.JSONUtil;
import com.damiao.common.lang.Result;
import com.damiao.util.JwtUtils;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
//import com.hutool.json.JSONUtil;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtFilter extends AuthenticatingFilter {@AutowiredJwtUtils jwtUtils;@Overrideprotected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {HttpServletRequest request = (HttpServletRequest) servletRequest;String jwt = request.getHeader("Authorization");if(StringUtils.isEmpty(jwt)){return null;}return new JwtToken(jwt);}@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {HttpServletRequest request = (HttpServletRequest) servletRequest;String jwt = request.getHeader("Authorization");if(StringUtils.isEmpty(jwt)){return true;}else{// 校验 JWTClaims claim =  jwtUtils.getClaimByToken(jwt);if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())){throw new ExpiredCredentialsException("Token已经失效,请重新登陆");}// 执行 登录return executeLogin(servletRequest, servletResponse);}
//        return false;}@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletResponse httpServletResponse = (HttpServletResponse) response;Throwable throwable  = e.getCause() == null ? e : e.getCause();Result result = Result.fail(throwable.getMessage(),null);String json = JSONUtil.toJsonStr(result);try {httpServletResponse.getWriter().print(json);} catch (IOException ioException) {//            ioException.printStackTrace();}return false;}// 执行前 先进行跨域处理@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = WebUtils.toHttp(request);HttpServletResponse httpServletResponse = WebUtils.toHttp(response);httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());return false;}return super.preHandle(request, response);}
}

九、全局异常处理

package com.damiao.common.exception;import com.damiao.common.lang.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.management.RuntimeErrorException;@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {// shiro 异常@ResponseStatus(HttpStatus.UNAUTHORIZED)@ExceptionHandler(value = ShiroException.class)public Result handler(ShiroException e){log.error("运行时shiro异常:------------------{}",e);return Result.fail(401,e.getMessage(),null);}// 业务大的异常@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = RuntimeErrorException.class)public Result handler(RuntimeException e){log.error("运行时异常:------------------{}",e);return Result.fail(e.getMessage(),null);}// 实体的异常@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handler(MethodArgumentNotValidException e){log.error("实体校验异常:------------------{}",e);BindingResult bindingResult = e.getBindingResult();ObjectError objectError =  bindingResult.getAllErrors().stream().findFirst().get();//        System.out.println(objectError.getDefaultMessage());return Result.fail(objectError.getDefaultMessage(),null);}// Assert 断言的异常@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = IllegalArgumentException.class)public Result handler(IllegalArgumentException e){log.error("Assert 断言异常:------------------{}",e);return Result.fail(e.getMessage());}}

十、配置

跨域配置

package com.damiao.article.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 CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS").allowCredentials(true).maxAge(3600).allowedHeaders("*");}
}

MyBatisPlus配置

package com.damiao.article.config;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration
@EnableTransactionManagement
@MapperScan("com.damiao.mapper")
public class MybatisPlusConfig {@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();return paginationInterceptor;}
}

ShiroConfig配置

package com.damiao.article.config;import com.damiao.shiro.AccountRealm;
import com.damiao.shiro.JwtFilter;
import org.apache.shiro.mgt.SessionsSecurityManager;  // SecurityManager
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sun.security.krb5.Realm;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;@Configuration
public class ShiroConfig {@AutowiredJwtFilter jwtFilter;@Beanpublic SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();// inject redisSessionDAOsessionManager.setSessionDAO(redisSessionDAO);// other stuff...return sessionManager;}@Beanpublic SessionsSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager, RedisCacheManager redisCacheManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);//inject sessionManagersecurityManager.setSessionManager(sessionManager);// inject redisCacheManagersecurityManager.setCacheManager(redisCacheManager);// other stuff...return securityManager;}@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限chainDefinition.addPathDefinitions(filterMap);return chainDefinition;}@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);Map<String, Filter> filters = new HashMap<>();filters.put("jwt", jwtFilter);shiroFilter.setFilters(filters);Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}
}

十一、登录登出接口开发

package com.damiao.controller;import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.damiao.common.dto.LoginDto;
import com.damiao.common.lang.Result;
import com.damiao.pojo.MUser;
import com.damiao.service.MUserService;
import com.damiao.util.JwtUtils;
import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;@RestController
public class AccountController {@AutowiredMUserService muserService;@AutowiredJwtUtils jwtUtils;@PostMapping("/login")public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response){MUser mUser = muserService.getOne(new QueryWrapper<MUser>().eq("username",loginDto.getUsername()));Assert.notNull(mUser,"用户不存在");// 获取密码if(!mUser.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))){return Result.fail("密码不正确");}// 响应头设置String jwt = jwtUtils.generateToken(mUser.getId());response.setHeader("Authorization",jwt);response.setHeader("Access-control-Expose-Headers","Authorization");// response.addHeader();//return Result.succ(MapUtil.builder().put("id", mUser.getId()).put("username", mUser.getUsername()).put("avatar", mUser.getAvatar()).put("email", mUser.getEmail()).map());}@GetMapping("/logout")public Result logout(){SecurityUtils.getSubject().logout();return Result.succ(null);}
}

十二、博客接口开发

package com.damiao.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.damiao.common.lang.Result;
import com.damiao.pojo.MBlog;
import com.damiao.pojo.MUser;
import com.damiao.service.MBlogService;
import com.damiao.util.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.Date;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;@RestController
public class BlogController {@AutowiredMBlogService mBlogService;// 获取博客列表@GetMapping("/blogs")public Result list(@RequestParam(defaultValue = "1") Integer cp){Page page = new Page(cp,5);IPage pageData =  mBlogService.page(page, new QueryWrapper<MBlog>().orderByDesc("created"));return Result.succ(pageData);}// 获取博客详情@GetMapping("/blog/{id}")public Result detail(@PathVariable(name = "id") Long id){MBlog mBlog = mBlogService.getById(id);Assert.notNull(mBlog, "该博客已被删除");return Result.succ(mBlog);}// 编辑博客文章@RequiresAuthentication@PutMapping("/blog/edit")public Result edit(@Validated @RequestBody  MBlog mBlog){System.out.println(mBlog);MBlog tempBlog = null;if(mBlog.getId() != null){tempBlog = mBlogService.getById(mBlog.getId());// 只能编辑自己的文章Assert.isTrue(tempBlog.getUserId().equals(ShiroUtil.getProfile().getId()), "没有编辑权限");}else{tempBlog = new MBlog();tempBlog.setUserId(ShiroUtil.getProfile().getId());LocalDateTime localDateTime = LocalDateTime.now();Date date = Date.from( localDateTime.atZone( ZoneId.systemDefault()).toInstant());tempBlog.setCreated(date);tempBlog.setStatus(0);}BeanUtils.copyProperties(mBlog,tempBlog,"id","userId","created","status");mBlogService.saveOrUpdate(tempBlog);return Result.succ(null);}// 删除博客文章@RequiresAuthentication@DeleteMapping("/blog/{id}")public Result delete(@PathVariable(name = "id") Long id){MBlog mBlog = mBlogService.getById(id);if(mBlog != null){// 删除mBlogService.removeById(id);}else{Assert.notNull(mBlog, "该文章不存在");}return Result.succ("操作成功");}}

十三、Vue前端页面开发

简单,略。

十四、总结

参考项目代码:https://github.com/MarkerHub/vueblog

SpringBoot+MyBatisPlus+Redis+Jwt+Shiro+Vue 完整博客文章管理前后端实战相关推荐

  1. 基于Spring Boot技术栈博客系统企业级前后端实战之课程导论(零)

    零.说明(必读) 一.课程概述 1.1 名称 1.2 功能 1.3 技术点 1.4 目标 二.核心功能 2.1 用户管理 2.2 安全设置 2.3 博客管理 2.4 评论管理 2.5 点赞管理 2.6 ...

  2. 使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理

    原文:使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理 摘要 通过对博客文章的管理,实现外键操作. 目录[-] 八.博客文章管理 1.查看文章 2.添加博客        3 ...

  3. 基于Node.js的博客文章管理系统设计与实现

    目 录 摘 要 I Abstract II 引 言 1 1.1. 项目背景 1 1.1.1. 什么是博客 1 1.1.2. 博客技术的现状 1 1.1.3. 为什么是NodeJS 1 1.2. 系统开 ...

  4. 服务器版博客系统、前后端交互1

    一.准备工作 1). 创建 maven 项目 2). 引入依赖 servlet,jackson,mysql <dependencies><!-- https://mvnreposit ...

  5. 基于Hexo框架快速搭建个人博客--文章一键发布(五)

    基于Hexo框架快速搭建个人博客--文章一键发布 一.文章对比 二.发布到Github 三.一键发布 四.总结 博客链接: 会思想的苇草i 文章链接: 基于Hexo框架快速搭建个人博客–搭建(一) 基 ...

  6. springboot+jwt+shiro+vue+elementUI+axios+redis+mysql完成一个前后端分离的博客项目(笔记,帮填坑)

    根据B站up主MarkerHub视频制作的一个笔记 我的博客 B站博主链接: https://www.bilibili.com/video/BV1PQ4y1P7hZ?p=1 博主的开发文档: http ...

  7. SpringBoot+Vue+Mybatis-plus 博客(七):完成友链管理前后端对接

    SpringBoot+Vue+Mybatis-plus 博客:个人博客介绍及效果展示 SpringBoot+Vue+Mybatis-plus 博客(一):完成博客后台前端登录页面.后端登录接口 Spr ...

  8. 基于springboot+vue个人博客搭建

    目录 博客介绍 源码地址:springboot+vue个人博客系统: 基于springboot+vue个人博客系统 在线地址 目录结构 ​编辑 项目特点 技术介绍 开发环境 项目截图 注意事项: 项目 ...

  9. Java项目:网上图书商城系统(java+SSM+Jsp+MySQL+Redis+JWT+Shiro+RabbitMQ+EasyUI)

    源码获取:博客首页 "资源" 里下载! 这个项目涉及到Shiro整合JWT.秒杀功能所具备的基本要求(限流.乐观锁.接口隐藏.JMeter高并发测试等等).消息中间件RabbitM ...

最新文章

  1. Eclipse 启动Tomcat 超时报错的解决方案
  2. java多线程发布订阅,多线程实现发布订阅升级版---遗留问题
  3. 点点客李新 | 移动社交电商行业案例干货分享
  4. MATLAB多元非线性回归
  5. Docker集群管理之Swarm介绍
  6. ContentProvider中gettype() 和MIME类型的理解
  7. arcgis api for js之echarts开源js库实现地图统计图分析
  8. Windows 8实例教程系列 - 布局控制
  9. 面向对象七大设计原则(转)
  10. C#实现简单的加密防止拷贝复制软件
  11. python matplotlib pColor 网格线 消除
  12. FireFox-background
  13. 领导说要搞微服务,我该怎么搭建开发和测试环境?
  14. 随机数字信号处理实验报告三——Levinson和Burg递推法MATLAB实现
  15. 百度地图添加家的位置图文教程
  16. android string 原理,Android中的SpannableString,Spans以及TextView绘制原理
  17. NMAD-2.14b1安装
  18. 基于FPGA等精度的实时测量频率和占空比
  19. 齿轮振动信号的数字滤波处理-含Matlab代码
  20. 计算机怎么查看网络连接的地址,各种电脑系统如何查看连接路由器的登录地址是多少?...

热门文章

  1. SpringBoot自动装箱原理
  2. 基于文本情感分析和LSTM的股票趋势预测
  3. docker版本升级
  4. 基于halo快速搭建一个属于你自己的博客网站
  5. 【前端面试必读】在js中为什么0.1+0.2不等于0.3
  6. python -- 图片压缩处理
  7. 淘宝网采用什么技术架构来实现网站高负载的(转载)
  8. Humor Me - RFC 1149 - 一首打油小诗
  9. 如何对MySQL数据库备份与还原?
  10. java crc 校验码_java实现CRC校验码