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


    根据B站up主MarkerHub视频制作的一个笔记

    我的博客

    B站博主链接:
    https://www.bilibili.com/video/BV1PQ4y1P7hZ?p=1

    博主的开发文档:
    https://juejin.cn/post/6844903823966732302
    部署视频
    https://www.bilibili.com/video/BV17A411E7aE?p=1
    up主的部署文档
    https://juejin.im/post/6886061338804617229/

    开源项目源码 :
    up主的:
    https://github.com/MarkerHub/vueblog
    我的:
    https://gitee.com/hntianshu/vueblog

    热部署配置:
    https://www.cnblogs.com/erlongxizhu-03/p/12193646.html

    idea报Could not autowired解决办法:
    https://blog.csdn.net/yxm234786/article/details/81460752

    ElementUI官方文档
    https://element.eleme.cn/#/zh-CN/component/quickstart

    PostMan安装包下载
    https://blog.csdn.net/weixin_43184774/article/details/100578557

    VsCode保存自动格式化样式
    https://blog.csdn.net/wang0112233/article/details/90608328

    Redis的安装和启动
    https://www.cnblogs.com/pretty-sunshine/p/10615287.html

    搞定Shiro集成redis实现会话共享
    https://blog.csdn.net/m0_46995061/article/details/106751848

    nginx官网
    http://nginx.org/en/download.html

    文章目录

    简易博客项目(springboot+jwt+shiro+vue+elementUI+axios+redis+mysql)

    第一章 整合新建springboot,整合mybatisplus

    第一步 创建项目(第八步骤就行)+数据库:

    https://blog.csdn.net/weixin_43247803/article/details/113622480
    修改pom.xml借鉴

    
    
        4.0.0
        
        
            org.springframework.boot
            spring-boot-starter-parent
            2.2.6.RELEASE
            
        
        com.vueblog
        vueblog
        0.0.1-SNAPSHOT
        vueblog
        Demo project for Spring Boot
    
        
            1.8
        
    
        
            
            
                org.springframework.boot
                spring-boot-starter-web
            
            
            
                org.springframework.boot
                spring-boot-devtools
                runtime
                true
            
            
            
                mysql
                mysql-connector-java
                runtime
            
            
            
                org.projectlombok
                lombok
                true
            
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
                
                    
                        org.junit.vintage
                        junit-vintage-engine
                    
                
            
            
            
                com.baomidou
                mybatis-plus-boot-starter
                3.2.0
            
            
            
                org.springframework.boot
                spring-boot-starter-freemarker
            
            
            
                com.baomidou
                mybatis-plus-generator
                3.2.0
            
            
            
                cn.hutool
                hutool-all
                5.3.3
            
            
            
                io.jsonwebtoken
                jjwt
                0.9.1
            
            
        
        
    
        
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    修改配置文件

    # DataSource Config
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
    mybatis-plus:
      mapper-locations: classpath*:/mapper/**Mapper.xml
    server:
      port: 8081
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建数据库vueblog然后执行下面命令生成表

    DROP TABLE IF EXISTS `m_blog`;
    /*!40101 SET @saved_cs_client     = @@character_set_client */;
     SET character_set_client = utf8mb4 ;
    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=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    /*!40101 SET character_set_client = @saved_cs_client */; 
    
    DROP TABLE IF EXISTS `m_user`;
    /*!40101 SET @saved_cs_client     = @@character_set_client */;
     SET character_set_client = utf8mb4 ;
    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 AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    /*!40101 SET character_set_client = @saved_cs_client */; 
    
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    配置分页MybatisPlusConfig+生成代码(dao 、service、serviceImpl等)

    1.0 配置分页
    创建MybatisPlusConfig类(创建路径com/vueblog/config)

    package com.vueblog.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.markerhub.mapper")
    public class MybatisPlusConfig {
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            return paginationInterceptor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.0 生成代码
    创建CodeGenerator(在com.vueblog包下面)
    **修改对应的数据库:账号密码、数据库名、包配置(ctrl+f可找到对应位置) **
    然后运行输入俩表 ,号隔开是多表

    package com.vueblog;
    
    import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
    import com.baomidou.mybatisplus.core.toolkit.StringPool;
    import com.baomidou.mybatisplus.core.toolkit.StringUtils;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.InjectionConfig;
    import com.baomidou.mybatisplus.generator.config.*;
    import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    // 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
    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.isNotEmpty(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.setOutputDir("D:\test"); gc.setAuthor("anonymous"); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 gc.setServiceName("%sService"); mpg.setGlobalConfig(gc); // 数据源配置 数据库名 账号密码 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(null); pc.setParent("com.vueblog"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity // String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化! return projectPath + "/src/main/resources/mapper/" + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix("m_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    效果如下:
    在这里插入图片描述

    第三步 做测试

    1.0 UserController类(ctrl+R可全局搜索类)

    package com.vueblog.controller;
    
    
    import com.vueblog.service.UserService;
    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;
    
    /**
     * 

    * 前端控制器 *

    * * @author * @since 2021-02-05 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/index") public Object index(){ return userService.getById(1); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.0 运行项目 查看效果
    在这里插入图片描述

    第二章 统一结果封装

    这里我们用到了一个Result的类,这个用于我们的异步统一返回的结果封装。一般来说,结果里面有几个要素必要的

    • 是否成功,可用code表示(如200表示成功,400表示异常)
    • 结果消息
    • 结果数据

    • Result类(路径com.vueblog.common.lang;)

      package com.vueblog.common.lang;

      import lombok.Data;

      import java.io.Serializable;

      @Data
      public class Result implements Serializable { //序列化
      private int code; //200是正常 400表示异常
      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.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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28

      }

    • 在UserController 中引用测试

      package com.vueblog.controller;

      import com.vueblog.common.lang.Result;
      import com.vueblog.entity.User;
      import com.vueblog.service.UserService;
      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;

      /**

      • 前端控制器

      • @author

      • @since 2021-02-05
        */
        @RestController
        @RequestMapping(“/user”)
        public class UserController {
        @Autowired
        private UserService userService;

        @GetMapping(“/index”)
        public Object index(){
        User user = userService.getById(1);
        return Result.succ(200,“操作成功”,user);
        }

      }

    • 页面运行效果图 (http://localhost:8081/user/index)

    在这里插入图片描述


    第三章 Shiro整合jwt逻辑分析

    考虑到后面可能需要做集群、负载均衡等,所以就需要会话共享,而shiro的缓存和会话信息,我们一般考虑使用redis来存储这些数据,所以,我们不仅仅需要整合shiro,同时也需要整合redis。在开源的项目中,我们找到了一个starter可以快速整合shiro-redis,配置简单,这里也推荐大家使用。
    而因为我们需要做的是前后端分离项目的骨架,所以一般我们会采用token或者jwt作为跨域身份验证解决方案。所以整合shiro的过程中,我们需要引入jwt的身份验证过程。
    那么我们就开始整合:
    我们使用一个shiro-redis-spring-boot-starter的jar包,具体教程可以看官方文档:github.com/alexxiyang/…


    • 导入shiro-redis的starter包:还有jwt的工具包,以及为了简化开发,引入hutool工具包。
      pom.xml中导入:

      	 
        
              org.crazycake
              shiro-redis-spring-boot-starter
              3.2.1
           
      	 
          
              cn.hutool
              hutool-all
              5.3.3
          
              
          
              io.jsonwebtoken
              jjwt
              0.9.1
          
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    • 创建ShiroConfig
      文件路径:com/vueblog/config

      package com.vueblog.config;

      import com.vueblog.shiro.AccountRealm;
      import com.vueblog.shiro.JwtFilter;
      import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
      import org.apache.shiro.mgt.DefaultSubjectDAO;
      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 java.util.HashMap;
      import java.util.LinkedHashMap;
      import java.util.Map;
      import javax.servlet.Filter;

      /**

      • shiro启用注解拦截控制器
        */
        @Configuration
        public class ShiroConfig {
        @Autowired
        private JwtFilter jwtFilter;

        @Bean
        public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO);
        return sessionManager;
        }
        @Bean
        public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
        SessionManager sessionManager,
        RedisCacheManager redisCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(redisCacheManager);
        /*
        * 关闭shiro自带的session,详情见文档
        */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
        }
        @Bean
        public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        Map 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 filters = new HashMap<>();
         filters.put("jwt", jwtFilter);
         shiroFilter.setFilters(filters);
        
         Map filterMap = shiroFilterChainDefinition.getFilterChainMap();
        
         shiroFilter.setFilterChainDefinitionMap(filterMap);
        
         return shiroFilter;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9

        }

      }

    • 创建MybatisPlusConfig

    路径:com.vueblog.config

    package com.vueblog.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.markerhub.mapper")
    public class MybatisPlusConfig {
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            return paginationInterceptor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 创建AccountRealm
      路径: com.vueblog.shiro

      package com.vueblog.shiro;

      import org.apache.shiro.authc.AuthenticationException;
      import org.apache.shiro.authc.AuthenticationInfo;
      import org.apache.shiro.authc.AuthenticationToken;
      import org.apache.shiro.authz.AuthorizationInfo;
      import org.apache.shiro.realm.AuthorizingRealm;
      import org.apache.shiro.subject.PrincipalCollection;
      import org.springframework.stereotype.Component;

      @Component
      public class AccountRealm extends AuthorizingRealm {
      //为了让realm支持jwt的凭证校验
      @Override
      public boolean supports(AuthenticationToken token) {
      return token instanceof JwtToken;
      }

      //权限校验
      @Override
      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
      
          return null;
      }
      //登录认证校验
      @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
         JwtToken jwtToken = (JwtToken) token;
      
          return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      }

    • 注意事项如果启动不了在VueblogApplication加入@MapperScan扫描mapper

      @SpringBootApplication
      @MapperScan(basePackages = “com.vueblog.mapper”)
      public class VueblogApplication {

      public static void main(String[] args) {
          SpringApplication.run(VueblogApplication.class, args);
      }
      
      • 1
      • 2
      • 3

      }

    • 创建JwtFilter
      路径:com.vueblog.shiro;

      package com.vueblog.shiro;

      import cn.hutool.http.server.HttpServerRequest;
      import cn.hutool.json.JSONUtil;
      import com.baomidou.mybatisplus.core.toolkit.StringUtils;
      import com.vueblog.common.lang.Result;
      import com.vueblog.util.JwtUtils;
      import io.jsonwebtoken.Claims;
      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.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;

      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 {
      @Autowired
      JwtUtils jwtUtils;
      //验证token
      @Override
      protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
      HttpServletRequest request = (HttpServletRequest)servletRequest;
      //获取头部token
      String jwt = request.getHeader(“Authorization”);
      if(StringUtils.isEmpty(jwt)){
      return null;
      }
      return new JwtToken(jwt);
      }

      @Override
      protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
          HttpServletRequest request = (HttpServletRequest)servletRequest;
          //获取头部token
          String jwt = request.getHeader("Authorization");
          if(StringUtils.isEmpty(jwt)){
              return true;
          }else{
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      // 校验jwt
      Claims claims = jwtUtils.getClaimByToken(jwt);
      //校验是否为空和时间是否过期
      if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){
      throw new ExpiredCredentialsException(“token已失效,请重新登录”);

              }
              //执行登录
              return executeLogin(servletRequest,servletResponse);
          }
      }
      //捕捉错误重写方法返回Result
      @Override
      protected 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());
          //返回json
          String json = JSONUtil.toJsonStr(result);
          try {
              //打印json
              httpServletResponse.getWriter().print(json);
          }catch (IOException ioException){
      
          }
      
          return false;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      }

    • 创建JwtToken

    路径:com.vueblog.shiro

    package com.vueblog.shiro;
    
    import org.apache.shiro.authc.AuthenticationToken;
    
    public class JwtToken implements AuthenticationToken {
        private String token;
    
        public JwtToken(String jwt){
            this.token = jwt;
        }
    
        @Override
        public Object getPrincipal() {
            return token;
        }
    
        @Override
        public Object getCredentials() {
            return token;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 创建JwtUtils

    路径:com.vueblog.util

    package com.vueblog.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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 创建spring-devtools.properties

    路径:resources/WETA-INF/

    restart.include.shiro-redis=/shiro-[\w-\.]+jar
    
    • 1

    第四章 Shiro逻辑开发

    小提示:登录调用AccountRealm类下面的doGetAuthenticationInfo

    创建类AccountProfile 用于传递数据
    路径:com.vueblog.shiro

    package com.vueblog.shiro;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    @Data
    public class AccountProfile implements Serializable {
        private Long id;
    
        private String username;
    
        private String avatar;
    
        private String email;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    完善AccountRealm

    package com.vueblog.shiro;
    
    import cn.hutool.core.bean.BeanUtil;
    import com.vueblog.entity.User;
    import com.vueblog.service.UserService;
    import com.vueblog.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.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class AccountRealm extends AuthorizingRealm {
        @Autowired
        JwtUtils jwtUtils;
        @Autowired
        UserService userService;
        //为了让realm支持jwt的凭证校验
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof JwtToken;
        }
    
        //权限校验
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            return null;
        }
        //登录认证校验
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
           JwtToken jwtToken = (JwtToken) token;
           //获取userId
            String userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();
           //获取用户内容
            User user = userService.getById(Long.valueOf(userId));
            if(user == null){
                throw new UnknownAccountException("账户不存在");
            }
            if(user.getStatus() == -1){
                throw new LockedAccountException("账户不存在");
            }
    
            AccountProfile profile = new AccountProfile();
            BeanUtil.copyProperties(user,profile);//将user数据转移到profile
            //用户信息  密钥token 用户名字
            return new SimpleAuthenticationInfo(profile,jwtToken.getCredentials(),getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    第五章 异常处理

    创建GlobalExceptionHandler 类
    捕获全局异常

    package com.vueblog.common.exception;
    
    
    import com.vueblog.common.lang.Result;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.ShiroException;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * 日志输出
     * 全局捕获异常
     */
    @Slf4j
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        @ResponseStatus(HttpStatus.UNAUTHORIZED) //因为前后端分离 返回一个状态 一般是401 没有权限
        @ExceptionHandler(value =  ShiroException.class)//捕获运行时异常ShiroException是大部分异常的父类
        public Result handler(ShiroException e){
            log.error("运行时异常:-----------------{}",e);
            return Result.fail(401,e.getMessage(),null);
        }
    
    
        @ResponseStatus(HttpStatus.BAD_REQUEST) //因为前后端分离 返回一个状态
        @ExceptionHandler(value =  RuntimeException.class)//捕获运行时异常
        public Result handler(RuntimeException e){
            log.error("运行时异常:-----------------{}",e);
            return Result.fail(e.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    然而我们运行测试发现并没有拦截
    在这里插入图片描述
    因为我们没有进行登录拦截
    @RequiresAuthentication//登录拦截注解
    在这里插入图片描述
    运行效果:
    提示401登录异常
    在这里插入图片描述

    第六章 实体校验

    当我们表单数据提交的时候,前端的校验我们可以使用一些类似于jQuery Validate等js插件实现,而后端我们可以使用Hibernate validatior来做校验。
    我们使用springboot框架作为基础,那么就已经自动集成了Hibernate validatior。(校验登录非空等等)


    • User实体类中

      import javax.validation.constraints.Email;
      import javax.validation.constraints.NotBlank;

      @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 = “昵称不能为空”)
      private String username;
      @NotBlank(message = “邮箱不能为空”)
      @Email(message = “邮箱格式不正确”)
      private String email;

      ...
      
      • 1

      }

    • 在userController类中写一个方法测试

      /**
       *
       *@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);
       * GET方式无请求体,所以使用@RequestBody接收数据时,
       * 前端不能使用GET方式提交数据,
       * 而是用POST方式进行提交。在后端的同一个接收方法里,
       * @RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,
       * 而@RequestParam()可以有多个。
       *
       * @Validated注解用于检查user中填写的规则  如果不满足抛出异常
       * 可在GlobalExceptionHandler中捕获此异常 进行自定义 返回数据信息
       */
      @PostMapping("/save")
      public  Result save(@Validated @RequestBody User user){
      
          return Result.succ(user);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    在这里插入图片描述

    在这里插入图片描述
    启动postMan测试
    如果postman没法启动
    可以新建一个环境变量POSTMAN_DISABLE_GPU = true。
    1.打开高级系统设置;
    2.在“高级”选项卡中,单击“环境变量”;
    3.添加一个新的系统变量;
    4.关闭Postman并重新打开

    在这里插入图片描述

    定义捕获异常返回处理
    在捕获异常 GlobalExceptionHandler类中修改如下:

        /**
         * 实体校验异常
         * MethodArgumentNotValidException捕获实体校验异常
         */
        @ResponseStatus(HttpStatus.BAD_REQUEST) //因为前后端分离 返回一个状态
        @ExceptionHandler(value =  MethodArgumentNotValidException.class)//捕获运行时异常
        public Result handler(MethodArgumentNotValidException e){
            log.error("实体捕获异常  :-----------------{}",e);
            BindingResult bindingException = e.getBindingResult();
            //多个异常顺序抛出异常
            ObjectError objectError = bindingException.getAllErrors().stream().findFirst().get();
            return Result.fail(objectError.getDefaultMessage());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    效果如下:(变得简短了)
    在这里插入图片描述
    输入正确的格式如下:
    返回了我们需要的信息
    在这里插入图片描述

    第七章 跨域问题

    因为是前后端分析,所以跨域问题是避免不了的,我们直接在后台进行全局跨域处理:
    路径:com.vueblog.config
    注意:此配置是配置到confroller的,在confroller之前是经过jwtFilter,所以在进行访问之前配置一下Filter的跨域问题

    package com.vueblog.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 {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowCredentials(true)
                    .maxAge(3600)
                    .allowedHeaders("*");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    jwtFilter进行跨域处理:

    • 路径:com.vueblog.shiro

      /**
       * 对跨域提供支持
       * @param request
       * @param response
       * @return
       * @throws Exception
       */
      @Override
      protected 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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21

    ----基本框架已经搭建完成-----


    第七章 登录接口开发

    • 创建LoginDto

    • 路径: com.vueblog.common.dto

      package com.vueblog.common.dto;

      import lombok.Data;

      import javax.validation.constraints.NotBlank;
      import java.io.Serializable;

      @Data
      public class LoginDto implements Serializable {

      @NotBlank(message = "用户名不能为空")
      private String username;
      @NotBlank(message = "密码不能为空")
      private String password;
      
      • 1
      • 2
      • 3
      • 4

      }

    • 在GlobalExceptionHandler类中增加断言异常
      路径:com.vueblog.common.exception

      /**
      * 断言异常
      * @param e
      * @return
      */
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      @ExceptionHandler(value = IllegalArgumentException.class)
      public Result handler(IllegalArgumentException e){
      log.error(“Assert异常:------------------>{}”,e);
      return Result.fail(e.getMessage());
      }

    • 创建AccountController类
      登录和退出逻辑

      package com.vueblog.controller;

      import cn.hutool.core.lang.Assert;
      import cn.hutool.core.map.MapUtil;
      import cn.hutool.crypto.SecureUtil;
      import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
      import com.vueblog.common.dto.LoginDto;
      import com.vueblog.common.lang.Result;
      import com.vueblog.entity.User;
      import com.vueblog.service.UserService;
      import com.vueblog.util.JwtUtils;
      import org.apache.shiro.SecurityUtils;
      import org.apache.shiro.authz.annotation.RequiresAuthentication;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.servlet.http.HttpServletResponse;

      @RestController
      public class AccountController {

      @Autowired
      UserService userService;
      
      @Autowired
      JwtUtils jwtUtils;
      
      @RequestMapping("/login")
      public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response){
          User user = userService.getOne(new QueryWrapper().eq("username", loginDto.getUsername()));
          Assert.notNull(user,"用户不存在");//断言拦截
          //判断账号密码是否错误 因为是md5加密所以这里md5判断
          if(!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))){
              //密码不同则抛出异常
              return Result.fail("密码不正确");
          }
          String jwt = jwtUtils.generateToken(user.getId());
      
          //将token 放在我们的header里面
          response.setHeader("Authorization",jwt);
          response.setHeader("Access-control-Expose-Headers","Authorization");
      
          return Result.succ(MapUtil.builder()
                  .put("id",user.getId())
                  .put("username",user.getUsername())
                  .put("avatar",user.getAvatar())
                  .put("email",user.getEmail()).map()
      
          );
      }
      
      //需要认证权限才能退出登录
      @RequiresAuthentication
      @RequestMapping("/logout")
      public Result logout() {
          //退出登录
          SecurityUtils.getSubject().logout();
          return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38

      }

    运行效果:
    在这里插入图片描述

    在这里插入图片描述

    第八章 博客接口的开发

    • 创建工具类ShiroUtil
      路径om.vueblog.util,可于判断等等

      package com.vueblog.util;

      import com.vueblog.shiro.AccountProfile;
      import org.apache.shiro.SecurityUtils;

      public class ShiroUtil {
      public static AccountProfile getProfile(){

          return (AccountProfile) SecurityUtils.getSubject().getPrincipal();
      }
      
      • 1
      • 2

      }

    • 完善BlogController类

      package com.vueblog.controller;

      import cn.hutool.core.bean.BeanUtil;
      import cn.hutool.core.lang.Assert;
      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.vueblog.common.lang.Result;
      import com.vueblog.entity.Blog;
      import com.vueblog.service.BlogService;
      import com.vueblog.util.ShiroUtil;
      import org.apache.shiro.authz.annotation.RequiresAuthentication;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.*;

      import java.time.LocalDateTime;

      /**

      • 前端控制器

      • @author

      • @since 2021-02-05
        */
        @RestController
        public class BlogController {

        @Autowired
        BlogService blogService;
        //木有值默认为1
        @GetMapping(“/blogs”)
        public Result list(@RequestParam(defaultValue = “1”) Integer currentPage){
        Page page = new Page(currentPage, 5);
        IPage pageData = blogService.page(page, new QueryWrapper().orderByDesc(“created”));
        return Result.succ(pageData);
        }

        //@PathVariable动态路由
        @GetMapping(“/blog/{id}”)
        public Result detail(@PathVariable Long id){
        Blog blog = blogService.getById(id);
        //判断是否为空 为空则断言异常
        Assert.notNull(blog,“该博客已被删除”);
        return Result.succ(blog);
        }
        //@Validated校验
        //@RequestBody
        //添加删除 木有id则添加 有id则编辑
        @RequiresAuthentication //需要认证之后才能操作
        @PostMapping(“/blog/edit”)
        public Result edit(@Validated @RequestBody Blog blog){

         //一个空对象用于赋值
         Blog temp = null;
         //如果有id则是编辑
         if(blog.getId() != null){
             temp = blogService.getById(blog.getId());//将数据库的内容传递给temp
             //只能编辑自己的文章
             Assert.isTrue(temp.getUserId().longValue()== ShiroUtil.getProfile().getId().longValue(),"没有编辑权限");
        
         } else {
             temp = new Blog();
             temp.setUserId(ShiroUtil.getProfile().getId());
             temp.setCreated(LocalDateTime.now());
             temp.setStatus(0);
         }
         //将blog的值赋给temp 忽略 id userid created status 引用于hutool
         BeanUtil.copyProperties(blog,temp,"id","userId","created","status");
         blogService.saveOrUpdate(temp);
        
         return Result.succ(null);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19

        }

        //@PathVariable动态路由
        @RequiresAuthentication //需要认证之后才能操作
        @PostMapping(“/blogdel/{id}”)
        public Result del(@PathVariable Long id){
        boolean b = blogService.removeById(id);
        //判断是否为空 为空则断言异常
        if(b==true){

           return Result.succ("文章删除成功");
        
        • 1

        }else{
        return Result.fail(“文章删除失败”);
        }
        }

      }

    • 运行程序
      查询测试:

    在这里插入图片描述
    在这里插入图片描述
    新增编辑测试:
    得到token
    在这里插入图片描述
    选中header=>填写token
    在这里插入图片描述
    选中body=>raw=>json填写请求

    新增

    {
    	"title":"标题测试",
    	"description":"描述测试哦奥德萨",
    	"content":"内容测试阿斯顿撒哦i等哈送到哈桑"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    修改

    {
    	"id":21,
    	"title":"标题测试修改",
    	"description":"描述测试哦奥德萨",
    	"content":"内容测试阿斯顿撒哦i等哈送到哈桑"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    文章删除
    在这里插入图片描述

    后端总结

    后端的一个骨架基本完成然后开始我们的前端开发

    第九章 Vue前端页面开发

    前言

    接下来,我们来完成vueblog前端的部分功能。可能会使用的到技术如下:
    vue
    element-ui
    axios
    mavon-editor
    markdown-it
    github-markdown-css

    环境准备

    • node.js安装:

    https://nodejs.org/zh-cn/
    在这里插入图片描述

    安装完成之后检查下版本信息:
    在这里插入图片描述

    • 接下来,我们安装vue的环境

      安装淘宝npm

      npm install -g cnpm --registry=https://registry.npm.taobao.org

      vue-cli 安装依赖包

      cnpm install --g vue-cli

    新建项目

    选中要建立的文件cmd打开
    在这里插入图片描述
    进入你的项目目录,创建一个基于 webpack 模板的新项目: vue init webpack 项目名

    在这里插入图片描述

    输入:
    vue init webpack xx
    全部enter即可

    在这里插入图片描述
    完成
    在这里插入图片描述
    运行项目
    进入demo(项目)目录 运行

    cd demo
    npm run dev
    
    • 1
    • 2

    在这里插入图片描述
    浏览器打开
    在这里插入图片描述

    项目的架构

    ├── README.md            项目介绍
    ├── index.html           入口页面
    ├── build              构建脚本目录
    │  ├── build-server.js         运行本地构建服务器,可以访问构建后的页面
    │  ├── build.js            生产环境构建脚本
    │  ├── dev-client.js          开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新
    │  ├── dev-server.js          运行本地开发服务器
    │  ├── utils.js            构建相关工具方法
    │  ├── webpack.base.conf.js      wabpack基础配置
    │  ├── webpack.dev.conf.js       wabpack开发环境配置
    │  └── webpack.prod.conf.js      wabpack生产环境配置
    ├── config             项目配置
    │  ├── dev.env.js           开发环境变量
    │  ├── index.js            项目配置文件
    │  ├── prod.env.js           生产环境变量
    │  └── test.env.js           测试环境变量
    ├── mock              mock数据目录
    │  └── hello.js
    ├── package.json          npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
    ├── src               源码目录 
    │  ├── main.js             入口js文件
    │  ├── app.vue             根组件
    │  ├── components           公共组件目录
    │  │  └── title.vue
    │  ├── assets             资源目录,这里的资源会被wabpack构建
    │  │  └── images
    │  │    └── logo.png
    │  ├── routes             前端路由
    │  │  └── index.js
    │  ├── store              应用级数据(state)状态管理
    │  │  └── index.js
    │  └── views              页面目录
    │    ├── hello.vue
    │    └── notfound.vue
    ├── static             纯静态资源,不会被wabpack构建。
    └── test              测试文件目录(unit&e2e)
      └── unit              单元测试
        ├── index.js            入口脚本
        ├── karma.conf.js          karma配置文件
        └── specs              单测case目录
          └── Hello.spec.js
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    集成开发工具Visual Studio Code(VsCode)

    vscode前端常用插件推荐,搭建JQuery、Vue等开发环境

    汉化插件:chinese
    插件如何导入和一些推荐插件:
    https://blog.csdn.net/jiandan1127/article/details/85957003/

    使用vscode打开项目并且运行

    ctrl+`(~键)打开终端
    输入:
    npm run dev

    在这里插入图片描述
    效果如下则成功:
    在这里插入图片描述

    安装element-ui

    • 官方文档:

    https://element.eleme.cn/#/zh-CN/component/installation

    ctrl+`(~键)打开终端输入安装命令

    # 安装element-ui
    cnpm install element-ui --save
    
    • 1
    • 2

    然后我们打开项目src目录下的main.js,引入element-ui依赖。

    import Element from 'element-ui'
    import "element-ui/lib/theme-chalk/index.css"
    Vue.use(Element)
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • 测试elementUi是否引入成功

      import Vue from ‘vue’
      import Router from ‘vue-router’
      import HelloWorld from ‘@/components/HelloWorld’
      import Demo from ‘@/views/Demo’

      Vue.use(Router)

      export default new Router({
      routes: [
      {
      path: ‘/’,
      name: ‘HelloWorld’,
      component: HelloWorld
      }, {
      path: ‘/Demo’,
      name: ‘Demo’,
      component: Demo
      }
      ]
      })

    • 新建Demo.vue

    src/views/demo
    引入button按钮(官方直接复制)

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    • App.vue添加:

    在这里插入图片描述

    • 运行项目浏览器打开项目

    在这里插入图片描述

    • 显示下方则成功

    在这里插入图片描述

    补充配置:

    autoOpenBrowser修改为true。

    autoOpenBrowser:true
    
    • 1

    在这里插入图片描述

    安装axios

    接下来,我们来安装axios(www.axios-js.com/),axios是一个基于 promise 的 HTTP 库,这样我们进行前后端对接的时候,使用这个工具可以提高我们的开发效率。

    • 安装命令:

      cnpm install axios --save

    在这里插入图片描述

    • 然后同样我们在main.js中全局引入axios。

      import axios from ‘axios’
      //引用全局
      Vue.prototype.$axios = axios

    在这里插入图片描述

    页面路由

    接下来,我们先定义好路由和页面,因为我们只是做一个简单的博客项目,页面比较少,所以我们可以直接先定义好,然后在慢慢开发,这样需要用到链接的地方我们就可以直接可以使用:
    我们在views文件夹下定义几个页面:

    BlogDetail.vue(博客详情页)
    BlogEdit.vue(编辑博客)
    Blogs.vue(博客列表)
    Login.vue(登录页面)

    可以配置插件:VueHelper(新建vue项目 最上方输入vuet按键tab可直接生成模板)
    在这里插入图片描述
    在这里插入图片描述
    建立页面完成
    在这里插入图片描述

    注意:每个页面下方