<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      項(xiàng)目實(shí)戰(zhàn)-前后端分離博客系統(tǒng)

      項(xiàng)目實(shí)戰(zhàn)-前后端分離博客系統(tǒng)

      1.項(xiàng)目介紹

      • 純后端講解
      • 完整的前臺(tái)后臺(tái)代碼編寫
      • 主流技術(shù)棧(SpringBoot,MybatisPlus,SpringSecurity,EasyExcel,Swagger2,Redis,Echarts,Vue,ElementUI....)
      • 完善細(xì)致的需求分析
      • 由易到難循序漸進(jìn)

      2.創(chuàng)建工程

      ? 我們有前臺(tái)和后臺(tái)兩套系統(tǒng)。兩套系統(tǒng)的前端工程都已經(jīng)提供好了。所以我們只需要寫兩套系統(tǒng)的后端。

      ? 但是大家思考下,實(shí)際上兩套后端系統(tǒng)的很多內(nèi)容是可能重復(fù)的。這里如果我們只是單純的創(chuàng)建兩個(gè)后端工程。那么就會(huì)有大量的重復(fù)代碼,并且需要修改的時(shí)候也需要修改兩次。這就是代碼復(fù)用性不高。

      ? 所以我們需要?jiǎng)?chuàng)建多模塊項(xiàng)目,兩套系統(tǒng)可能都會(huì)用到的代碼可以寫到一個(gè)公共模塊中,讓前臺(tái)系統(tǒng)和后臺(tái)系統(tǒng)分別取依賴公共模塊。

      ① 創(chuàng)建父模塊

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>com.sangeng</groupId>
          <artifactId>SGBlog</artifactId>
          <packaging>pom</packaging>
          <version>1.0-SNAPSHOT</version>
          <modules>
              <module>sangeng-framework</module>
              <module>sangeng-admin</module>
              <module>sangeng-blog</module>
          </modules>
      
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <java.version>1.8</java.version>
          </properties>
          <dependencyManagement>
      
      
          <dependencies>
              <!-- SpringBoot的依賴配置-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-dependencies</artifactId>
                  <version>2.5.0</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
              <!--fastjson依賴-->
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.33</version>
              </dependency>
              <!--jwt依賴-->
              <dependency>
                  <groupId>io.jsonwebtoken</groupId>
                  <artifactId>jjwt</artifactId>
                  <version>0.9.0</version>
              </dependency>
              <!--mybatisPlus依賴-->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
                  <version>3.4.3</version>
              </dependency>
      
              <!--阿里云OSS-->
              <dependency>
                  <groupId>com.aliyun.oss</groupId>
                  <artifactId>aliyun-sdk-oss</artifactId>
                  <version>3.10.2</version>
              </dependency>
      
      
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>easyexcel</artifactId>
                  <version>3.0.5</version>
              </dependency>
      
              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger2</artifactId>
                  <version>2.9.2</version>
              </dependency>
              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger-ui</artifactId>
                  <version>2.9.2</version>
              </dependency>
          </dependencies>
      
      
          </dependencyManagement>
      
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <version>3.1</version>
                      <configuration>
                          <source>${java.version}</source>
                          <target>${java.version}</target>
                          <encoding>${project.build.sourceEncoding}</encoding>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      </project>
      
      

      ②創(chuàng)建公共子模塊 sangeng-framework

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <parent>
              <artifactId>SGBlog</artifactId>
              <groupId>com.sangeng</groupId>
              <version>1.0-SNAPSHOT</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>sangeng-framework</artifactId>
      
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <!--lombk-->
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <optional>true</optional>
              </dependency>
              <!--junit-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
              </dependency>
              <!--SpringSecurity啟動(dòng)器-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-security</artifactId>
              </dependency>
              <!--redis依賴-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
              </dependency>
              <!--fastjson依賴-->
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
              </dependency>
              <!--jwt依賴-->
              <dependency>
                  <groupId>io.jsonwebtoken</groupId>
                  <artifactId>jjwt</artifactId>
              </dependency>
              <!--mybatisPlus依賴-->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
              </dependency>
              <!--mysql數(shù)據(jù)庫驅(qū)動(dòng)-->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
              </dependency>
      
              <!--阿里云OSS-->
              <dependency>
                  <groupId>com.aliyun.oss</groupId>
                  <artifactId>aliyun-sdk-oss</artifactId>
              </dependency>
      
              <!--AOP-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-aop</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>easyexcel</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger2</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger-ui</artifactId>
              </dependency>
      
          </dependencies>
      </project>
      
      

      ③創(chuàng)建博客后臺(tái)模塊sangeng-admin

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <parent>
              <artifactId>SGBlog</artifactId>
              <groupId>com.sangeng</groupId>
              <version>1.0-SNAPSHOT</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>sangeng-admin</artifactId>
      
          <dependencies>
              <dependency>
                  <groupId>com.sangeng</groupId>
                  <artifactId>sangeng-framework</artifactId>
                  <version>1.0-SNAPSHOT</version>
              </dependency>
          </dependencies>
      </project>
      
      

      ④創(chuàng)建博客前臺(tái)模塊sangeng-blog

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <parent>
              <artifactId>SGBlog</artifactId>
              <groupId>com.sangeng</groupId>
              <version>1.0-SNAPSHOT</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>sangeng-blog</artifactId>
      
          <dependencies>
              <dependency>
                  <groupId>com.sangeng</groupId>
                  <artifactId>sangeng-framework</artifactId>
                  <version>1.0-SNAPSHOT</version>
              </dependency>
          </dependencies>
      
      </project>
      
      

      3.博客前臺(tái)

      3.0 準(zhǔn)備工作

      3.1 SpringBoot和MybatisPuls整合配置測試

      ①創(chuàng)建啟動(dòng)類

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @SpringBootApplication
      @MapperScan("com.sangeng.mapper")
      public class SanGengBlogApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(SanGengBlogApplication.class,args);
          }
      }
      

      ②創(chuàng)建application.yml配置文件

      server:
        port: 7777
      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/sg_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        servlet:
          multipart:
            max-file-size: 2MB
            max-request-size: 5MB
      mybatis-plus:
        configuration:
          # 日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        global-config:
          db-config:
            logic-delete-field: delFlag
            logic-delete-value: 1
            logic-not-delete-value: 0
            id-type: auto
      
      

      ③ SQL語句

      ? SQL腳本:SGBlog\資源\SQL\sg_article.sql

      ④ 創(chuàng)建實(shí)體類,Mapper,Service

      ? 注意思考這些文件應(yīng)該寫在哪個(gè)模塊下?

      @SuppressWarnings("serial")
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @TableName("sg_article")
      public class Article  {
          @TableId
          private Long id;
          //標(biāo)題
          private String title;
          //文章內(nèi)容
          private String content;
          //文章類型:1 文章 2草稿
          private String type;
          //文章摘要
          private String summary;
          //所屬分類id
          private Long categoryId;
          //縮略圖
          private String thumbnail;
          //是否置頂(0否,1是)
          private String isTop;
          //狀態(tài)(0已發(fā)布,1草稿)
          private String status;
          //評論數(shù)
          private Integer commentCount;
          //訪問量
          private Long viewCount;
          //是否允許評論 1是,0否
          private String isComment;
          
          private Long createBy;
          
          private Date createTime;
          
          private Long updateBy;
          
          private Date updateTime;
          //刪除標(biāo)志(0代表未刪除,1代表已刪除)
          private Integer delFlag;
      
      }
      
      
      
      public interface ArticleMapper extends BaseMapper<Article> {
      
      
      }
      
      
      public interface ArticleService extends IService<Article> {
      }
      
      
      @Service
      public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
      
      }
      

      ⑤ 創(chuàng)建Controller測試接口

      ? 注意思考這些文件應(yīng)該寫在哪個(gè)模塊下?

      @RestController
      @RequestMapping("/article")
      public class ArticleController {
      
          @Autowired
          private ArticleService articleService;
      
          @GetMapping("/list")
          public List<Article> test(){
              return articleService.list();
          }
      }
      
      

      ? 我們可以暫時(shí)先注釋掉sangeng-framework中的SpringSecurity依賴方便測試

      3.1 熱門文章列表

      3.1.0 文章表分析

      ? 通過需求去分析需要有哪些字段。

      3.1.1 需求

      ? 需要查詢?yōu)g覽量最高的前10篇文章的信息。要求展示文章標(biāo)題和瀏覽量。把能讓用戶自己點(diǎn)擊跳轉(zhuǎn)到具體的文章詳情進(jìn)行瀏覽。

      ? 注意:不能把草稿展示出來,不能把刪除了的文章查詢出來。要按照瀏覽量進(jìn)行降序排序。

      3.1.2 接口設(shè)計(jì)

      ? 見接口文檔

      3.1.3 基礎(chǔ)版本代碼實(shí)現(xiàn)

      ①準(zhǔn)備工作

      統(tǒng)一響應(yīng)類和響應(yīng)枚舉

      package com.sangeng.domain;
      
      import com.fasterxml.jackson.annotation.JsonInclude;
      import com.sangeng.enums.AppHttpCodeEnum;
      
      import java.io.Serializable;
      
      @JsonInclude(JsonInclude.Include.NON_NULL)
      public class ResponseResult<T> implements Serializable {
          private Integer code;
          private String msg;
          private T data;
      
          public ResponseResult() {
              this.code = AppHttpCodeEnum.SUCCESS.getCode();
              this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
          }
      
          public ResponseResult(Integer code, T data) {
              this.code = code;
              this.data = data;
          }
      
          public ResponseResult(Integer code, String msg, T data) {
              this.code = code;
              this.msg = msg;
              this.data = data;
          }
      
          public ResponseResult(Integer code, String msg) {
              this.code = code;
              this.msg = msg;
          }
      
          public static ResponseResult errorResult(int code, String msg) {
              ResponseResult result = new ResponseResult();
              return result.error(code, msg);
          }
          public static ResponseResult okResult() {
              ResponseResult result = new ResponseResult();
              return result;
          }
          public static ResponseResult okResult(int code, String msg) {
              ResponseResult result = new ResponseResult();
              return result.ok(code, null, msg);
          }
      
          public static ResponseResult okResult(Object data) {
              ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
              if(data!=null) {
                  result.setData(data);
              }
              return result;
          }
      
          public static ResponseResult errorResult(AppHttpCodeEnum enums){
              return setAppHttpCodeEnum(enums,enums.getMsg());
          }
      
          public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
              return setAppHttpCodeEnum(enums,msg);
          }
      
          public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
              return okResult(enums.getCode(),enums.getMsg());
          }
      
          private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
              return okResult(enums.getCode(),msg);
          }
      
          public ResponseResult<?> error(Integer code, String msg) {
              this.code = code;
              this.msg = msg;
              return this;
          }
      
          public ResponseResult<?> ok(Integer code, T data) {
              this.code = code;
              this.data = data;
              return this;
          }
      
          public ResponseResult<?> ok(Integer code, T data, String msg) {
              this.code = code;
              this.data = data;
              this.msg = msg;
              return this;
          }
      
          public ResponseResult<?> ok(T data) {
              this.data = data;
              return this;
          }
      
          public Integer getCode() {
              return code;
          }
      
          public void setCode(Integer code) {
              this.code = code;
          }
      
          public String getMsg() {
              return msg;
          }
      
          public void setMsg(String msg) {
              this.msg = msg;
          }
      
          public T getData() {
              return data;
          }
      
          public void setData(T data) {
              this.data = data;
          }
      
      
      
      }
      
      package com.sangeng.enums;
      
      public enum AppHttpCodeEnum {
          // 成功
          SUCCESS(200,"操作成功"),
          // 登錄
          NEED_LOGIN(401,"需要登錄后操作"),
          NO_OPERATOR_AUTH(403,"無權(quán)限操作"),
          SYSTEM_ERROR(500,"出現(xiàn)錯(cuò)誤"),
          USERNAME_EXIST(501,"用戶名已存在"),
           PHONENUMBER_EXIST(502,"手機(jī)號已存在"), EMAIL_EXIST(503, "郵箱已存在"),
          REQUIRE_USERNAME(504, "必需填寫用戶名"),
          LOGIN_ERROR(505,"用戶名或密碼錯(cuò)誤");
          int code;
          String msg;
      
          AppHttpCodeEnum(int code, String errorMessage){
              this.code = code;
              this.msg = errorMessage;
          }
      
          public int getCode() {
              return code;
          }
      
          public String getMsg() {
              return msg;
          }
      }
      
      

      ② 代碼實(shí)現(xiàn)

      @RestController
      @RequestMapping("/article")
      public class ArticleController {
      
          @Autowired
          private ArticleService articleService;
          
          @GetMapping("/hotArticleList")
          public ResponseResult hotArticleList(){
      
              ResponseResult result =  articleService.hotArticleList();
              return result;
          }
      }
      
      
      public interface ArticleService extends IService<Article> {
          ResponseResult hotArticleList();
      }
      
      
      @Service
      public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
      
          @Override
          public ResponseResult hotArticleList() {
              //查詢熱門文章 封裝成ResponseResult返回
              LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
              //必須是正式文章
              queryWrapper.eq(Article::getStatus,0);
              //按照瀏覽量進(jìn)行排序
              queryWrapper.orderByDesc(Article::getViewCount);
              //最多只查詢10條
              Page<Article> page = new Page(1,10);
              page(page,queryWrapper);
      
              List<Article> articles = page.getRecords();
              return ResponseResult.okResult(articles);
          }
      }
      
      

      ③ 解決跨域問題

      @Configuration
      public class WebConfig implements WebMvcConfigurer {
      
          @Override
          public void addCorsMappings(CorsRegistry registry) {
            // 設(shè)置允許跨域的路徑
              registry.addMapping("/**")
                      // 設(shè)置允許跨域請求的域名
                      .allowedOriginPatterns("*")
                      // 是否允許cookie
                      .allowCredentials(true)
                      // 設(shè)置允許的請求方式
                      .allowedMethods("GET", "POST", "DELETE", "PUT")
                      // 設(shè)置允許的header屬性
                      .allowedHeaders("*")
                      // 跨域允許時(shí)間
                      .maxAge(3600);
          }
      
      }
      

      3.1.4 使用VO優(yōu)化

      ? 目前我們的響應(yīng)格式其實(shí)是不符合接口文檔的標(biāo)準(zhǔn)的,多返回了很多字段。這是因?yàn)槲覀儾樵兂鰜淼慕Y(jié)果是Article來封裝的,Article中字段比較多。

      ? 我們在項(xiàng)目中一般最后還要把VO來接受查詢出來的結(jié)果。一個(gè)接口對應(yīng)一個(gè)VO,這樣即使接口響應(yīng)字段要修改也只要改VO即可。

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class HotArticleVo {
          private Long id;
          //標(biāo)題
          private String title;
      
          //訪問量
          private Long viewCount;
      }
      
      
      @Service
      public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
      
          @Override
          public ResponseResult hotArticleList() {
              //查詢熱門文章 封裝成ResponseResult返回
              LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
              //必須是正式文章
              queryWrapper.eq(Article::getStatus,0);
              //按照瀏覽量進(jìn)行排序
              queryWrapper.orderByDesc(Article::getViewCount);
              //最多只查詢10條
              Page<Article> page = new Page(1,10);
              page(page,queryWrapper);
      
              List<Article> articles = page.getRecords();
              //bean拷貝
              List<HotArticleVo> articleVos = new ArrayList<>();
              for (Article article : articles) {
                  HotArticleVo vo = new HotArticleVo();
                  BeanUtils.copyProperties(article,vo);
                  articleVos.add(vo);
              }
      
              return ResponseResult.okResult(articleVos);
          }
      }
      

      3.1.5 字面值處理

      ? 實(shí)際項(xiàng)目中都不允許直接在代碼中使用字面值。都需要定義成常量來使用。這種方式有利于提高代碼的可維護(hù)性。

      public class SystemConstants
      {
          /**
           *  文章是草稿
           */
          public static final int ARTICLE_STATUS_DRAFT = 1;
          /**
           *  文章是正常分布狀態(tài)
           */
          public static final int ARTICLE_STATUS_NORMAL = 0;
          
      }
      
      @Service
      public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
      
          @Override
          public ResponseResult hotArticleList() {
              //查詢熱門文章 封裝成ResponseResult返回
              LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
              //必須是正式文章
              queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
              //按照瀏覽量進(jìn)行排序
              queryWrapper.orderByDesc(Article::getViewCount);
              //最多只查詢10條
              Page<Article> page = new Page(1,10);
              page(page,queryWrapper);
      
              List<Article> articles = page.getRecords();
              //bean拷貝
              List<HotArticleVo> articleVos = new ArrayList<>();
              for (Article article : articles) {
                  HotArticleVo vo = new HotArticleVo();
                  BeanUtils.copyProperties(article,vo);
                  articleVos.add(vo);
              }
      
              return ResponseResult.okResult(articleVos);
          }
      }
      

      3.2 Bean拷貝工具類封裝

      public class BeanCopyUtils {
      
          private BeanCopyUtils() {
          }
      
          public static <V> V copyBean(Object source,Class<V> clazz) {
              //創(chuàng)建目標(biāo)對象
              V result = null;
              try {
                  result = clazz.newInstance();
                  //實(shí)現(xiàn)屬性copy
                  BeanUtils.copyProperties(source, result);
              } catch (Exception e) {
                  e.printStackTrace();
              }
              //返回結(jié)果
              return result;
          }
          public static <O,V> List<V> copyBeanList(List<O> list,Class<V> clazz){
              return list.stream()
                      .map(o -> copyBean(o, clazz))
                      .collect(Collectors.toList());
          }
      }
      
      

      3.2 查詢分類列表

      3.2.0 分類表分析

      ? 通過需求去分析需要有哪些字段。

      ? 建表SQL及初始化數(shù)據(jù)見:SGBlog\資源\SQL\sg_category.sql

      3.2.1 需求

      image-20220202111056036

      ? 頁面上需要展示分類列表,用戶可以點(diǎn)擊具體的分類查看該分類下的文章列表。

      ? 注意: ①要求只展示有發(fā)布正式文章的分類 ②必須是正常狀態(tài)的分類

      3.2.2 接口設(shè)計(jì)

      ? 見接口文檔

      3.2.3 EasyCode代碼模板

      ##導(dǎo)入宏定義
      $!{define.vm}
      
      ##保存文件(宏定義)
      #save("/entity", ".java")
      
      ##包路徑(宏定義)
      #setPackageSuffix("entity")
      
      ##自動(dòng)導(dǎo)入包(全局變量)
      $!{autoImport.vm}
      
      import java.io.Serializable;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      ##表注釋(宏定義)
      #tableComment("表實(shí)體類")
      @SuppressWarnings("serial")
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @TableName("$!{tableInfo.obj.name}")
      public class $!{tableInfo.name}  {
      #foreach($column in $tableInfo.pkColumn)
          #if(${column.comment})//${column.comment}#end
      @TableId
          private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
      #end
      
      #foreach($column in $tableInfo.otherColumn)
          #if(${column.comment})//${column.comment}#end
      
          private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
      #end
      
      
      
      }
      
      
      ##導(dǎo)入宏定義
      $!{define.vm}
      
      ##設(shè)置表后綴(宏定義)
      #setTableSuffix("Mapper")
      
      ##保存文件(宏定義)
      #save("/mapper", "Mapper.java")
      
      ##包路徑(宏定義)
      #setPackageSuffix("mapper")
      
      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      
      
      ##表注釋(宏定義)
      #tableComment("表數(shù)據(jù)庫訪問層")
      public interface $!{tableName} extends BaseMapper<$!tableInfo.name> {
      
      }
      
      
      ##導(dǎo)入宏定義
      $!{define.vm}
      
      ##設(shè)置表后綴(宏定義)
      #setTableSuffix("Service")
      
      ##保存文件(宏定義)
      #save("/service", "Service.java")
      
      ##包路徑(宏定義)
      #setPackageSuffix("service")
      
      import com.baomidou.mybatisplus.extension.service.IService;
      
      
      ##表注釋(宏定義)
      #tableComment("表服務(wù)接口")
      public interface $!{tableName} extends IService<$!tableInfo.name> {
      
      }
      
      
      ##導(dǎo)入宏定義
      $!{define.vm}
      
      ##設(shè)置表后綴(宏定義)
      #setTableSuffix("ServiceImpl")
      
      ##保存文件(宏定義)
      #save("/service/impl", "ServiceImpl.java")
      
      ##包路徑(宏定義)
      #setPackageSuffix("service.impl")
      
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import org.springframework.stereotype.Service;
      
      ##表注釋(宏定義)
      #tableComment("表服務(wù)實(shí)現(xiàn)類")
      @Service("$!tool.firstLowerCase($tableInfo.name)Service")
      public class $!{tableName} extends ServiceImpl<$!{tableInfo.name}Mapper, $!{tableInfo.name}> implements $!{tableInfo.name}Service {
      
      }
      
      

      3.2.4 代碼實(shí)現(xiàn)

      @RestController
      @RequestMapping("/category")
      public class CategoryController {
      
          @Autowired
          private CategoryService categoryService;
      
          @GetMapping("/getCategoryList")
          public ResponseResult getCategoryList(){
             return categoryService.getCategoryList();
          }
      }
      
          
      
      public interface CategoryService extends IService<Category> {
      
      
          ResponseResult getCategoryList();
      
      }
      
      @Service("categoryService")
      public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
      
          @Autowired
          private ArticleService articleService;
      
          @Override
          public ResponseResult getCategoryList() {
              //查詢文章表  狀態(tài)為已發(fā)布的文章
              LambdaQueryWrapper<Article> articleWrapper = new LambdaQueryWrapper<>();
              articleWrapper.eq(Article::getStatus,SystemConstants.ARTICLE_STATUS_NORMAL);
              List<Article> articleList = articleService.list(articleWrapper);
              //獲取文章的分類id,并且去重
              Set<Long> categoryIds = articleList.stream()
                      .map(article -> article.getCategoryId())
                      .collect(Collectors.toSet());
      
              //查詢分類表
              List<Category> categories = listByIds(categoryIds);
              categories = categories.stream().
                      filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus()))
                      .collect(Collectors.toList());
              //封裝vo
              List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);
      
              return ResponseResult.okResult(categoryVos);
          }
      }
      
      

      3.3 分頁查詢文章列表

      3.3.1 需求

      ? 在首頁和分類頁面都需要查詢文章列表。

      ? 首頁:查詢所有的文章

      ? 分類頁面:查詢對應(yīng)分類下的文章

      ? 要求:①只能查詢正式發(fā)布的文章 ②置頂?shù)奈恼乱@示在最前面

      3.3.2 接口設(shè)計(jì)

      ? 見文檔

      3.3.3 代碼實(shí)現(xiàn)

      MP支持分頁配置

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @Configuration
      public class MbatisPlusConfig {
      
          /**
           * 3.4.0之后版本
           * @return
           */
          @Bean
          public MybatisPlusInterceptor mybatisPlusInterceptor(){
              MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
              mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
              return mybatisPlusInterceptor;
          }
      }
      

      在ArticleController中

          @GetMapping("/articleList")
          public ResponseResult articleList(Integer pageNum,Integer pageSize,Long categoryId){
              return articleService.articleList(pageNum,pageSize,categoryId);
          }
      

      在ArticleService中

      ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId);
      

      在ArticleServiceImpl中

      @Service
      public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
      
          @Autowired
          private CategoryService categoryService;
      
          @Override
          public ResponseResult hotArticleList() {
              //查詢熱門文章 封裝成ResponseResult返回
              LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
              //必須是正式文章
              queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
              //按照瀏覽量進(jìn)行排序
              queryWrapper.orderByDesc(Article::getViewCount);
              //最多只查詢10條
              Page<Article> page = new Page(1,10);
              page(page,queryWrapper);
      
              List<Article> articles = page.getRecords();
              //bean拷貝
      //        List<HotArticleVo> articleVos = new ArrayList<>();
      //        for (Article article : articles) {
      //            HotArticleVo vo = new HotArticleVo();
      //            BeanUtils.copyProperties(article,vo);
      //            articleVos.add(vo);
      //        }
              List<HotArticleVo> vs = BeanCopyUtils.copyBeanList(articles, HotArticleVo.class);
              return ResponseResult.okResult(vs);
          }
      
          @Override
          public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {
              //查詢條件
              LambdaQueryWrapper<Article> lambdaQueryWrapper = new LambdaQueryWrapper<>();
              // 如果 有categoryId 就要 查詢時(shí)要和傳入的相同
              lambdaQueryWrapper.eq(Objects.nonNull(categoryId)&&categoryId>0 ,Article::getCategoryId,categoryId);
              // 狀態(tài)是正式發(fā)布的
              lambdaQueryWrapper.eq(Article::getStatus,SystemConstants.ARTICLE_STATUS_NORMAL);
              // 對isTop進(jìn)行降序
              lambdaQueryWrapper.orderByDesc(Article::getIsTop);
      
              //分頁查詢
              Page<Article> page = new Page<>(pageNum,pageSize);
              page(page,lambdaQueryWrapper);
      
              List<Article> articles = page.getRecords();
              //查詢categoryName
              articles.stream()
                      .map(article -> article.setCategoryName(categoryService.getById(article.getCategoryId()).getName()))
                      .collect(Collectors.toList());
              //articleId去查詢articleName進(jìn)行設(shè)置
      //        for (Article article : articles) {
      //            Category category = categoryService.getById(article.getCategoryId());
      //            article.setCategoryName(category.getName());
      //        }
      
              //封裝查詢結(jié)果
              List<ArticleListVo> articleListVos = BeanCopyUtils.copyBeanList(page.getRecords(), ArticleListVo.class);
      
              PageVo pageVo = new PageVo(articleListVos,page.getTotal());
              return ResponseResult.okResult(pageVo);
          }
      }
      

      PageVo

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class PageVo {
          private List rows;
          private Long total;
      }
      
      

      ArticleListVo

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class ArticleListVo {
      
          private Long id;
          //標(biāo)題
          private String title;
          //文章摘要
          private String summary;
          //所屬分類名
          private String categoryName;
          //縮略圖
          private String thumbnail;
      
      
          //訪問量
          private Long viewCount;
      
          private Date createTime;
      
      
      }
      

      在Article中增加一個(gè)字段

          @TableField(exist = false)
          private String categoryName;
      

      3.3.4 FastJson配置

          @Bean//使用@Bean注入fastJsonHttpMessageConvert
          public HttpMessageConverter fastJsonHttpMessageConverters() {
              //1.需要定義一個(gè)Convert轉(zhuǎn)換消息的對象
              FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
              FastJsonConfig fastJsonConfig = new FastJsonConfig();
              fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
              fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
      		
              SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);
      
              fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);
              fastConverter.setFastJsonConfig(fastJsonConfig);
              HttpMessageConverter<?> converter = fastConverter;
              return converter;
          }
      
          @Override
          public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
              converters.add(fastJsonHttpMessageConverters());
          }
      

      3.4 文章詳情接口

      3.4.1 需求

      ? 要求在文章列表點(diǎn)擊閱讀全文時(shí)能夠跳轉(zhuǎn)到文章詳情頁面,可以讓用戶閱讀文章正文。

      ? 要求:①要在文章詳情中展示其分類名

      3.4.2 接口設(shè)計(jì)

      請求方式 請求路徑
      Get /article/

      響應(yīng)格式:

      {
        "code": 200,
        "data": {
          "categoryId": "1",
          "categoryName": "java",
          "content": "內(nèi)容",
          "createTime": "2022-01-23 23:20:11",
          "id": "1",
          "isComment": "0",
          "title": "SpringSecurity從入門到精通",
          "viewCount": "114"
        },
        "msg": "操作成功"
      }
      

      3.4.3 代碼實(shí)現(xiàn)

      ArticleController中新增

          @GetMapping("/{id}")
          public ResponseResult getArticleDetail(@PathVariable("id") Long id){
              return articleService.getArticleDetail(id);
          }
      

      Service

      ResponseResult getArticleDetail(Long id);
      

      ServiceImpl

          @Override
          public ResponseResult getArticleDetail(Long id) {
              //根據(jù)id查詢文章
              Article article = getById(id);
              //轉(zhuǎn)換成VO
              ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
              //根據(jù)分類id查詢分類名
              Long categoryId = articleDetailVo.getCategoryId();
              Category category = categoryService.getById(categoryId);
              if(category!=null){
                  articleDetailVo.setCategoryName(category.getName());
              }
              //封裝響應(yīng)返回
              return ResponseResult.okResult(articleDetailVo);
          }
      

      3.5 友聯(lián)查詢

      3.5.0 友鏈表分析

      ? 通過需求去分析需要有哪些字段。

      ? 建表SQL及初始化數(shù)據(jù)見:SGBlog\資源\SQL\sg_link.sql

      3.5.1 需求

      ? 在友鏈頁面要查詢出所有的審核通過的友鏈。

      3.5.2 接口設(shè)計(jì)

      請求方式 請求路徑
      Get /link/getAllLink

      響應(yīng)格式:

      {
        "code": 200,
        "data": [
          {
            "address": "https://www.baidu.com",
            "description": "sda",
            "id": "1",
            "logo": "圖片url1",
            "name": "sda"
          },
          {
            "address": "https://www.qq.com",
            "description": "dada",
            "id": "2",
            "logo": "圖片url2",
            "name": "sda"
          }
        ],
        "msg": "操作成功"
      }
      

      3.5.3 代碼實(shí)現(xiàn)

      Controller

      @RestController
      @RequestMapping("/link")
      public class LinkController {
      
          @Autowired
          private LinkService linkService;
      
          @GetMapping("/getAllLink")
          public ResponseResult getAllLink(){
              return linkService.getAllLink();
          }
      }
      
      

      Service

      public interface LinkService extends IService<Link> {
      
          ResponseResult getAllLink();
      }
      
      
      

      ServiceImpl

      @Service("linkService")
      public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {
      
          @Override
          public ResponseResult getAllLink() {
              //查詢所有審核通過的
              LambdaQueryWrapper<Link> queryWrapper = new LambdaQueryWrapper<>();
              queryWrapper.eq(Link::getStatus, SystemConstants.LINK_STATUS_NORMAL);
              List<Link> links = list(queryWrapper);
              //轉(zhuǎn)換成vo
              List<LinkVo> linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);
              //封裝返回
              return ResponseResult.okResult(linkVos);
          }
      }
      
      

      SystemConstants

          /**
           * 友鏈狀態(tài)為審核通過
           */
          public static final String  LINK_STATUS_NORMAL = "0";
      

      3.6 登錄功能實(shí)現(xiàn)

      ? 使用我們前臺(tái)和后臺(tái)的認(rèn)證授權(quán)統(tǒng)一都使用SpringSecurity安全框架來實(shí)現(xiàn)。

      3.6.0 需求

      ? 需要實(shí)現(xiàn)登錄功能

      ? 有些功能必須登錄后才能使用,未登錄狀態(tài)是不能使用的。

      3.6.1 接口設(shè)計(jì)

      請求方式 請求路徑
      POST /login

      請求體:

      {
          "userName":"sg",
          "password":"1234"
      }
      

      響應(yīng)格式:

      {
          "code": 200,
          "data": {
              "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODBmOThmYmJkNmI0NjM0OWUyZjY2NTM0NGNjZWY2NSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0Mzg3NDMxNiwiZXhwIjoxNjQzOTYwNzE2fQ.ldLBUvNIxQCGemkCoMgT_0YsjsWndTg5tqfJb77pabk",
              "userInfo": {
                  "avatar": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2F3bf9c263bc0f2ac5c3a7feb9e218d07475573ec8.gi",
                  "email": "23412332@qq.com",
                  "id": 1,
                  "nickName": "sg333",
                  "sex": "1"
              }
          },
          "msg": "操作成功"
      }
      

      3.6.2 表分析

      ? 建表SQL及初始化數(shù)據(jù)見:SGBlog\資源\SQL\sys_user.sql

      ? 順便生成下User和UserMapper后面會(huì)用到

      3.6.3 思路分析

      登錄

      ? ①自定義登錄接口

      ? 調(diào)用ProviderManager的方法進(jìn)行認(rèn)證 如果認(rèn)證通過生成jwt

      ? 把用戶信息存入redis中

      ? ②自定義UserDetailsService

      ? 在這個(gè)實(shí)現(xiàn)類中去查詢數(shù)據(jù)庫

      ? 注意配置passwordEncoder為BCryptPasswordEncoder

      校驗(yàn):

      ? ①定義Jwt認(rèn)證過濾器

      ? 獲取token

      ? 解析token獲取其中的userid

      ? 從redis中獲取用戶信息

      ? 存入SecurityContextHolder

      3.6.4 準(zhǔn)備工作

      ①添加依賴

      注意放開Security依賴的注釋

              <!--redis依賴-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
              </dependency>
              <!--fastjson依賴-->
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.33</version>
              </dependency>
              <!--jwt依賴-->
              <dependency>
                  <groupId>io.jsonwebtoken</groupId>
                  <artifactId>jjwt</artifactId>
                  <version>0.9.0</version>
              </dependency>
      

      ②工具類和相關(guān)配置類

      ? 見 :SGBlog\資源\登錄功能所需資源

      3.6.5 登錄接口代碼實(shí)現(xiàn)

      BlogLoginController
      @RestController
      public class BlogLoginController {
          @Autowired
          private BlogLoginService blogLoginService;
      
          @PostMapping("/login")
          public ResponseResult login(@RequestBody User user){
              return blogLoginService.login(user);
          }
      }
      
      
      BlogLoginService
      public interface BlogLoginService {
          ResponseResult login(User user);
      }
      
      
      SecurityConfig
      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Bean
          public PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/login").anonymous()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
      
              http.logout().disable();
              //允許跨域
              http.cors();
          }
          @Override
          @Bean
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      }
      
      BlogLoginServiceImpl
      @Service
      public class BlogLoginServiceImpl implements BlogLoginService {
      
          @Autowired
          private AuthenticationManager authenticationManager;
      
          @Autowired
          private RedisCache redisCache;
      
          @Override
          public ResponseResult login(User user) {
              UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
              Authentication authenticate = authenticationManager.authenticate(authenticationToken);
              //判斷是否認(rèn)證通過
              if(Objects.isNull(authenticate)){
                  throw new RuntimeException("用戶名或密碼錯(cuò)誤");
              }
              //獲取userid 生成token
              LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
              String userId = loginUser.getUser().getId().toString();
              String jwt = JwtUtil.createJWT(userId);
              //把用戶信息存入redis
              redisCache.setCacheObject("bloglogin:"+userId,loginUser);
      
              //把token和userinfo封裝 返回
              //把User轉(zhuǎn)換成UserInfoVo
              UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
              BlogUserLoginVo vo = new BlogUserLoginVo(jwt,userInfoVo);
              return ResponseResult.okResult(vo);
          }
      }
      
      UserDetailServiceImpl
      @Service
      public class UserDetailsServiceImpl implements UserDetailsService {
      
          @Autowired
          private UserMapper userMapper;
      
          @Override
          public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              //根據(jù)用戶名查詢用戶信息
              LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
              queryWrapper.eq(User::getUserName,username);
              User user = userMapper.selectOne(queryWrapper);
              //判斷是否查到用戶  如果沒查到拋出異常
              if(Objects.isNull(user)){
                  throw new RuntimeException("用戶不存在");
              }
              //返回用戶信息
              // TODO 查詢權(quán)限信息封裝
              return new LoginUser(user);
          }
      }
      
      LoginUser
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class LoginUser implements UserDetails {
      
          private User user;
      
      
          @Override
          public Collection<? extends GrantedAuthority> getAuthorities() {
              return null;
          }
      
          @Override
          public String getPassword() {
              return user.getPassword();
          }
      
          @Override
          public String getUsername() {
              return user.getUserName();
          }
      
          @Override
          public boolean isAccountNonExpired() {
              return true;
          }
      
          @Override
          public boolean isAccountNonLocked() {
              return true;
          }
      
          @Override
          public boolean isCredentialsNonExpired() {
              return true;
          }
      
          @Override
          public boolean isEnabled() {
              return true;
          }
      }
      
      
      BlogUserLoginVo
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class BlogUserLoginVo {
      
          private String token;
          private UserInfoVo userInfo;
      }
      
      UserInfoVo
      @Data
      @Accessors(chain = true)
      public class UserInfoVo {
          /**
           * 主鍵
           */
          private Long id;
      
          /**
           * 昵稱
           */
          private String nickName;
      
          /**
           * 頭像
           */
          private String avatar;
      
          private String sex;
      
          private String email;
      
      
      }
      
      

      3.6.6 登錄校驗(yàn)過濾器代碼實(shí)現(xiàn)

      思路

      ? ①定義Jwt認(rèn)證過濾器

      ? 獲取token

      ? 解析token獲取其中的userid

      ? 從redis中獲取用戶信息

      ? 存入SecurityContextHolder

      JwtAuthenticationTokenFilter
      @Component
      public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
      
          @Autowired
          private RedisCache redisCache;
      
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
              //獲取請求頭中的token
              String token = request.getHeader("token");
              if(!StringUtils.hasText(token)){
                  //說明該接口不需要登錄  直接放行
                  filterChain.doFilter(request, response);
                  return;
              }
              //解析獲取userid
              Claims claims = null;
              try {
                  claims = JwtUtil.parseJWT(token);
              } catch (Exception e) {
                  e.printStackTrace();
                  //token超時(shí)  token非法
                  //響應(yīng)告訴前端需要重新登錄
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                  WebUtils.renderString(response, JSON.toJSONString(result));
                  return;
              }
              String userId = claims.getSubject();
              //從redis中獲取用戶信息
              LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);
              //如果獲取不到
              if(Objects.isNull(loginUser)){
                  //說明登錄過期  提示重新登錄
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                  WebUtils.renderString(response, JSON.toJSONString(result));
                  return;
              }
              //存入SecurityContextHolder
              UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
              SecurityContextHolder.getContext().setAuthentication(authenticationToken);
      
              filterChain.doFilter(request, response);
          }
      
      
      }
      
      SecurityConfig
      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          @Bean
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      
          @Autowired
          private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/login").anonymous()
                      //jwt過濾器測試用,如果測試沒有問題吧這里刪除了
                      .antMatchers("/link/getAllLink").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
      
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      
          @Bean
          public PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
      }
      
      

      3.7 認(rèn)證授權(quán)失敗處理

      ? 目前我們的項(xiàng)目在認(rèn)證出錯(cuò)或者權(quán)限不足的時(shí)候響應(yīng)回來的Json是Security的異常處理結(jié)果。但是這個(gè)響應(yīng)的格式肯定是不符合我們項(xiàng)目的接口規(guī)范的。所以需要自定義異常處理。

      ? AuthenticationEntryPoint 認(rèn)證失敗處理器

      ? AccessDeniedHandler 授權(quán)失敗處理器

      @Component
      public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
      
          @Override
          public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
              authException.printStackTrace();
              //InsufficientAuthenticationException
              //BadCredentialsException
              ResponseResult result = null;
              if(authException instanceof BadCredentialsException){
                  result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(),authException.getMessage());
              }else if(authException instanceof InsufficientAuthenticationException){
                  result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
              }else{
                  result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),"認(rèn)證或授權(quán)失敗");
              }
              //響應(yīng)給前端
              WebUtils.renderString(response, JSON.toJSONString(result));
          }
      }
      
      
      @Component
      public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
          @Override
          public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
              accessDeniedException.printStackTrace();
              ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
              //響應(yīng)給前端
              WebUtils.renderString(response, JSON.toJSONString(result));
          }
      }
      
      

      配置Security異常處理器

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          @Bean
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      
          @Autowired
          private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
          @Autowired
          AuthenticationEntryPoint authenticationEntryPoint;
          @Autowired
          AccessDeniedHandler accessDeniedHandler;
      
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/login").anonymous()
                      //jwt過濾器測試用,如果測試沒有問題吧這里刪除了
                      .antMatchers("/link/getAllLink").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
              //配置異常處理器
              http.exceptionHandling()
                      .authenticationEntryPoint(authenticationEntryPoint)
                      .accessDeniedHandler(accessDeniedHandler);
      
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      
          @Bean
          public PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
      }
      
      

      3.8 統(tǒng)一異常處理

      ? 實(shí)際我們在開發(fā)過程中可能需要做很多的判斷校驗(yàn),如果出現(xiàn)了非法情況我們是期望響應(yīng)對應(yīng)的提示的。但是如果我們每次都自己手動(dòng)去處理就會(huì)非常麻煩。我們可以選擇直接拋出異常的方式,然后對異常進(jìn)行統(tǒng)一處理。把異常中的信息封裝成ResponseResult響應(yīng)給前端。

      ?

      SystemException

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      public class SystemException extends RuntimeException{
      
          private int code;
      
          private String msg;
      
          public int getCode() {
              return code;
          }
      
          public String getMsg() {
              return msg;
          }
      
          public SystemException(AppHttpCodeEnum httpCodeEnum) {
              super(httpCodeEnum.getMsg());
              this.code = httpCodeEnum.getCode();
              this.msg = httpCodeEnum.getMsg();
          }
          
      }
      
      

      GlobalExceptionHandler

      @RestControllerAdvice
      @Slf4j
      public class GlobalExceptionHandler {
      
          @ExceptionHandler(SystemException.class)
          public ResponseResult systemExceptionHandler(SystemException e){
              //打印異常信息
              log.error("出現(xiàn)了異常! {}",e);
              //從異常對象中獲取提示信息封裝返回
              return ResponseResult.errorResult(e.getCode(),e.getMsg());
          }
      
      
          @ExceptionHandler(Exception.class)
          public ResponseResult exceptionHandler(Exception e){
              //打印異常信息
              log.error("出現(xiàn)了異常! {}",e);
              //從異常對象中獲取提示信息封裝返回
              return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
          }
      }
      
      

      3.9 退出登錄接口

      3.9.1 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      POST /logout 需要token請求頭

      響應(yīng)格式:

      {
          "code": 200,
          "msg": "操作成功"
      }
      

      3.9.2 代碼實(shí)現(xiàn)

      要實(shí)現(xiàn)的操作:

      ? 刪除redis中的用戶信息

      BlogLoginController

          @PostMapping("/logout")
          public ResponseResult logout(){
              return blogLoginService.logout();
          }
      

      BlogLoginService

      ResponseResult logout();
      

      BlogLoginServiceImpl

          @Override
          public ResponseResult logout() {
              //獲取token 解析獲取userid
              Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
              LoginUser loginUser = (LoginUser) authentication.getPrincipal();
              //獲取userid
              Long userId = loginUser.getUser().getId();
              //刪除redis中的用戶信息
              redisCache.deleteObject("bloglogin:"+userId);
              return ResponseResult.okResult();
          }
      

      SecurityConfig

      要關(guān)閉默認(rèn)的退出登錄功能。并且要配置我們的退出登錄接口需要認(rèn)證才能訪問

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/login").anonymous()
                  	//注銷接口需要認(rèn)證才能訪問
                      .antMatchers("/logout").authenticated()
                      //jwt過濾器測試用,如果測試沒有問題吧這里刪除了
                      .antMatchers("/link/getAllLink").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
              //配置異常處理器
              http.exceptionHandling()
                      .authenticationEntryPoint(authenticationEntryPoint)
                      .accessDeniedHandler(accessDeniedHandler);
      		//關(guān)閉默認(rèn)的注銷功能
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      

      3.10 查詢評論列表接口

      3.10.1 需求

      ? 文章詳情頁面要展示這篇文章下的評論列表。

      ? 效果如下:

      image-20220208214106296

      3.10.2 評論表分析

      ? 通過需求去分析需要有哪些字段。

      ? 建表SQL及初始化數(shù)據(jù)見:SGBlog\資源\SQL\sg_comment.sql

      ? 順便生成下對應(yīng)的代碼

      3.10.3 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      GET /comment/commentList 不需要token請求頭

      Query格式請求參數(shù):

      articleId:文章id

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      響應(yīng)格式:

      {
          "code": 200,
          "data": {
              "rows": [
                  {
                      "articleId": "1",
                      "children": [
                          {
                              "articleId": "1",
                              "content": "你說啥?",
                              "createBy": "1",
                              "createTime": "2022-01-30 10:06:21",
                              "id": "20",
                              "rootId": "1",
                              "toCommentId": "1",
                              "toCommentUserId": "1",
                              "toCommentUserName": "sg333",
                              "username": "sg333"
                          }
                      ],
                      "content": "asS",
                      "createBy": "1",
                      "createTime": "2022-01-29 07:59:22",
                      "id": "1",
                      "rootId": "-1",
                      "toCommentId": "-1",
                      "toCommentUserId": "-1",
                      "username": "sg333"
                  }
              ],
              "total": "15"
          },
          "msg": "操作成功"
      }
      

      3.10.4 代碼實(shí)現(xiàn)

      3.10.4.1 不考慮子評論

      CommentController

      @RestController
      @RequestMapping("/comment")
      public class CommentController {
      
          @Autowired
          private CommentService commentService;
      
          @GetMapping("/commentList")
          public ResponseResult commentList(Long articleId,Integer pageNum,Integer pageSize){
              return commentService.commentList(articleId,pageNum,pageSize);
          }
      }
      
      

      CommentService

      public interface CommentService extends IService<Comment> {
      
          ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize);
      }
      
      

      CommentServiceImpl

      @Service("commentService")
      public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
      
          @Autowired
          private UserService userService;
      
          @Override
          public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
              //查詢對應(yīng)文章的根評論
              LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
              //對articleId進(jìn)行判斷
              queryWrapper.eq(Comment::getArticleId,articleId);
              //根評論 rootId為-1
              queryWrapper.eq(Comment::getRootId,-1);
      
              //分頁查詢
              Page<Comment> page = new Page(pageNum,pageSize);
              page(page,queryWrapper);
      
              List<CommentVo> commentVoList = toCommentVoList(page.getRecords());
      
              return ResponseResult.okResult(new PageVo(commentVoList,page.getTotal()));
          }
      
          private List<CommentVo> toCommentVoList(List<Comment> list){
              List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
              //遍歷vo集合
              for (CommentVo commentVo : commentVos) {
                  //通過creatyBy查詢用戶的昵稱并賦值
                  String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
                  commentVo.setUsername(nickName);
                  //通過toCommentUserId查詢用戶的昵稱并賦值
                  //如果toCommentUserId不為-1才進(jìn)行查詢
                  if(commentVo.getToCommentUserId()!=-1){
                      String toCommentUserName = userService.getById(commentVo.getToCommentUserId()).getNickName();
                      commentVo.setToCommentUserName(toCommentUserName);
                  }
              }
              return commentVos;
          }
      }
      
      
      

      CommentVo

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class CommentVo {
          private Long id;
          //文章id
          private Long articleId;
          //根評論id
          private Long rootId;
          //評論內(nèi)容
          private String content;
          //所回復(fù)的目標(biāo)評論的userid
          private Long toCommentUserId;
          private String toCommentUserName;
          //回復(fù)目標(biāo)評論id
          private Long toCommentId;
      
          private Long createBy;
      
          private Date createTime;
      
          private String username;
      }
      
      
      3.10.4.2 查詢子評論

      CommentVo在之前的基礎(chǔ)上增加了 private List children;

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class CommentVo {
          private Long id;
          //文章id
          private Long articleId;
          //根評論id
          private Long rootId;
          //評論內(nèi)容
          private String content;
          //所回復(fù)的目標(biāo)評論的userid
          private Long toCommentUserId;
          private String toCommentUserName;
          //回復(fù)目標(biāo)評論id
          private Long toCommentId;
      
          private Long createBy;
      
          private Date createTime;
      
          private String username;
      
          private List<CommentVo> children;
      }
      
      

      CommentServiceImpl

      @Service("commentService")
      public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
      
          @Autowired
          private UserService userService;
      
          @Override
          public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
              //查詢對應(yīng)文章的根評論
              LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
              //對articleId進(jìn)行判斷
              queryWrapper.eq(Comment::getArticleId,articleId);
              //根評論 rootId為-1
              queryWrapper.eq(Comment::getRootId,-1);
      
              //分頁查詢
              Page<Comment> page = new Page(pageNum,pageSize);
              page(page,queryWrapper);
      
              List<CommentVo> commentVoList = toCommentVoList(page.getRecords());
      
              //查詢所有根評論對應(yīng)的子評論集合,并且賦值給對應(yīng)的屬性
              for (CommentVo commentVo : commentVoList) {
                  //查詢對應(yīng)的子評論
                  List<CommentVo> children = getChildren(commentVo.getId());
                  //賦值
                  commentVo.setChildren(children);
              }
      
              return ResponseResult.okResult(new PageVo(commentVoList,page.getTotal()));
          }
      
          /**
           * 根據(jù)根評論的id查詢所對應(yīng)的子評論的集合
           * @param id 根評論的id
           * @return
           */
          private List<CommentVo> getChildren(Long id) {
      
              LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
              queryWrapper.eq(Comment::getRootId,id);
              queryWrapper.orderByAsc(Comment::getCreateTime);
              List<Comment> comments = list(queryWrapper);
      
              List<CommentVo> commentVos = toCommentVoList(comments);
              return commentVos;
          }
      
          private List<CommentVo> toCommentVoList(List<Comment> list){
              List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
              //遍歷vo集合
              for (CommentVo commentVo : commentVos) {
                  //通過creatyBy查詢用戶的昵稱并賦值
                  String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
                  commentVo.setUsername(nickName);
                  //通過toCommentUserId查詢用戶的昵稱并賦值
                  //如果toCommentUserId不為-1才進(jìn)行查詢
                  if(commentVo.getToCommentUserId()!=-1){
                      String toCommentUserName = userService.getById(commentVo.getToCommentUserId()).getNickName();
                      commentVo.setToCommentUserName(toCommentUserName);
                  }
              }
              return commentVos;
          }
      }
      

      3.11 發(fā)表評論接口

      3.11.1 需求

      ? 用戶登錄后可以對文章發(fā)表評論,也可以對評論進(jìn)行回復(fù)。

      ? 用戶登錄后也可以在友鏈頁面進(jìn)行評論。

      3.11.2 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      POST /comment 需要token頭
      請求體:

      回復(fù)了文章:

      {"articleId":1,"type":0,"rootId":-1,"toCommentId":-1,"toCommentUserId":-1,"content":"評論了文章"}
      

      回復(fù)了某條評論:

      {"articleId":1,"type":0,"rootId":"3","toCommentId":"3","toCommentUserId":"1","content":"回復(fù)了某條評論"}
      

      如果是友鏈評論,type應(yīng)該為1

      響應(yīng)格式:
      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      3.11.3 代碼實(shí)現(xiàn)

      CommentController

          @PostMapping
          public ResponseResult addComment(@RequestBody Comment comment){
              return commentService.addComment(comment);
          }
      

      CommentService

      ResponseResult addComment(Comment comment);
      

      CommentServiceImpl

          @Override
          public ResponseResult addComment(Comment comment) {
              //評論內(nèi)容不能為空
              if(!StringUtils.hasText(comment.getContent())){
                  throw new SystemException(AppHttpCodeEnum.CONTENT_NOT_NULL);
              }
              save(comment);
              return ResponseResult.okResult();
          }
      

      SecurityUtils

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      public class SecurityUtils
      {
      
          /**
           * 獲取用戶
           **/
          public static LoginUser getLoginUser()
          {
              return (LoginUser) getAuthentication().getPrincipal();
          }
      
          /**
           * 獲取Authentication
           */
          public static Authentication getAuthentication() {
              return SecurityContextHolder.getContext().getAuthentication();
          }
      
          public static Boolean isAdmin(){
              Long id = getLoginUser().getUser().getId();
              return id != null && 1L == id;
          }
      
          public static Long getUserId() {
              return getLoginUser().getUser().getId();
          }
      }
      

      配置MP字段自動(dòng)填充

      @Component
      public class MyMetaObjectHandler implements MetaObjectHandler {
          @Override
          public void insertFill(MetaObject metaObject) {
              Long userId = null;
              try {
                  userId = SecurityUtils.getUserId();
              } catch (Exception e) {
                  e.printStackTrace();
                  userId = -1L;//表示是自己創(chuàng)建
              }
              this.setFieldValByName("createTime", new Date(), metaObject);
              this.setFieldValByName("createBy",userId , metaObject);
              this.setFieldValByName("updateTime", new Date(), metaObject);
              this.setFieldValByName("updateBy", userId, metaObject);
          }
      
          @Override
          public void updateFill(MetaObject metaObject) {
              this.setFieldValByName("updateTime", new Date(), metaObject);
              this.setFieldValByName(" ", SecurityUtils.getUserId(), metaObject);
          }
      }
      

      用注解標(biāo)識(shí)哪些字段在什么情況下需要自動(dòng)填充

          /**
           * 創(chuàng)建人的用戶id
           */
          @TableField(fill = FieldFill.INSERT)
          private Long createBy;
          /**
           * 創(chuàng)建時(shí)間
           */
          @TableField(fill = FieldFill.INSERT)
          private Date createTime;
          /**
           * 更新人
           */
          @TableField(fill = FieldFill.INSERT_UPDATE)
          private Long updateBy;
          /**
           * 更新時(shí)間
           */
          @TableField(fill = FieldFill.INSERT_UPDATE)
          private Date updateTime;
      

      3.12 友聯(lián)評論列表

      3.12.1 需求

      ? 友鏈頁面也需要查詢對應(yīng)的評論列表。

      3.12.2 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      GET /comment/linkCommentList 不需要token請求頭

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      響應(yīng)格式:

      {
          "code": 200,
          "data": {
              "rows": [
                  {
                      "articleId": "1",
                      "children": [
                          {
                              "articleId": "1",
                              "content": "回復(fù)友鏈評論3",
                              "createBy": "1",
                              "createTime": "2022-01-30 10:08:50",
                              "id": "23",
                              "rootId": "22",
                              "toCommentId": "22",
                              "toCommentUserId": "1",
                              "toCommentUserName": "sg333",
                              "username": "sg333"
                          }
                      ],
                      "content": "友鏈評論2",
                      "createBy": "1",
                      "createTime": "2022-01-30 10:08:28",
                      "id": "22",
                      "rootId": "-1",
                      "toCommentId": "-1",
                      "toCommentUserId": "-1",
                      "username": "sg333"
                  }
              ],
              "total": "1"
          },
          "msg": "操作成功"
      }
      

      3.12.3 代碼實(shí)現(xiàn)

      CommentController 修改了之前的文章評論列表接口,并且增加了新的友聯(lián)評論接口

          @GetMapping("/commentList")
          public ResponseResult commentList(Long articleId,Integer pageNum,Integer pageSize){
              return commentService.commentList(SystemConstants.ARTICLE_COMMENT,articleId,pageNum,pageSize);
          }   
          @GetMapping("/linkCommentList")
          public ResponseResult linkCommentList(Integer pageNum,Integer pageSize){
              return commentService.commentList(SystemConstants.LINK_COMMENT,null,pageNum,pageSize);
          }
      

      SystemConstants增加了兩個(gè)常量

          /**
           * 評論類型為:文章評論
           */
          public static final String ARTICLE_COMMENT = "0";
          /**
           * 評論類型為:友聯(lián)評論
           */
          public static final String LINK_COMMENT = "1";
      

      CommentService修改了commentList方法,增加了一個(gè)參數(shù)commentType

      ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize);
      

      CommentServiceImpl修改commentList方法的代碼,必須commentType為0的時(shí)候才增加articleId的判斷,并且增加了一個(gè)評論類型的添加。

          @Override
          public ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {
              //查詢對應(yīng)文章的根評論
              LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
              //對articleId進(jìn)行判斷
              queryWrapper.eq(SystemConstants.ARTICLE_COMMENT.equals(commentType),Comment::getArticleId,articleId);
              //根評論 rootId為-1
              queryWrapper.eq(Comment::getRootId,-1);
      
              //評論類型
              queryWrapper.eq(Comment::getType,commentType);
      
              //分頁查詢
              Page<Comment> page = new Page(pageNum,pageSize);
              page(page,queryWrapper);
      
              List<CommentVo> commentVoList = toCommentVoList(page.getRecords());
      
              //查詢所有根評論對應(yīng)的子評論集合,并且賦值給對應(yīng)的屬性
              for (CommentVo commentVo : commentVoList) {
                  //查詢對應(yīng)的子評論
                  List<CommentVo> children = getChildren(commentVo.getId());
                  //賦值
                  commentVo.setChildren(children);
              }
      
              return ResponseResult.okResult(new PageVo(commentVoList,page.getTotal()));
          }
      

      3.13 個(gè)人信息查詢接口

      3.13.1 需求

      ? 進(jìn)入個(gè)人中心的時(shí)候需要能夠查看當(dāng)前用戶信息

      3.13.2 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      GET /user/userInfo 需要token請求頭

      不需要參數(shù)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"avatar":"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2F3bf9c263bc0f2ac5c3a7feb9e218d07475573ec8.gi",
      		"email":"23412332@qq.com",
      		"id":"1",
      		"nickName":"sg333",
      		"sex":"1"
      	},
      	"msg":"操作成功"
      }
      

      3.13.3 代碼實(shí)現(xiàn)

      UserController

      @RestController
      @RequestMapping("/user")
      public class UserController {
      
          @Autowired
          private UserService userService;
      
          @GetMapping("/userInfo")
          public ResponseResult userInfo(){
              return userService.userInfo();
          }
      }
      
      

      UserService增加方法定義

      public interface UserService extends IService<User> {
      
          ResponseResult userInfo();
      
      }
      
      

      UserServiceImpl實(shí)現(xiàn)userInfo方法

          @Override
          public ResponseResult userInfo() {
              //獲取當(dāng)前用戶id
              Long userId = SecurityUtils.getUserId();
              //根據(jù)用戶id查詢用戶信息
              User user = getById(userId);
              //封裝成UserInfoVo
              UserInfoVo vo = BeanCopyUtils.copyBean(user,UserInfoVo.class);
              return ResponseResult.okResult(vo);
          }
      

      SecurityConfig配置該接口必須認(rèn)證后才能訪問

         @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/login").anonymous()
                      //注銷接口需要認(rèn)證才能訪問
                      .antMatchers("/logout").authenticated()
                  	//個(gè)人信息接口必須登錄后才能訪問
                      .antMatchers("/user/userInfo").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
              //配置異常處理器
              http.exceptionHandling()
                      .authenticationEntryPoint(authenticationEntryPoint)
                      .accessDeniedHandler(accessDeniedHandler);
              //關(guān)閉默認(rèn)的注銷功能
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      

      3.14 頭像上傳接口

      3.14.1 需求

      ? 在個(gè)人中心點(diǎn)擊編輯的時(shí)候可以上傳頭像圖片。上傳完頭像后,可以用于更新個(gè)人信息接口。

      3.14.2 OSS

      3.14.2.1 為什么要使用OSS

      ? 因?yàn)槿绻褕D片視頻等文件上傳到自己的應(yīng)用的Web服務(wù)器,在讀取圖片的時(shí)候會(huì)占用比較多的資源。影響應(yīng)用服務(wù)器的性能。

      ? 所以我們一般使用OSS(Object Storage Service對象存儲(chǔ)服務(wù))存儲(chǔ)圖片或視頻。

      3.14.2.2 七牛云基本使用測試

      image-20220227224537701

      image-20220227224443813

      秘鑰

      image-20220228230512598

      image-20220228230933808

      3.14.2.3 七牛云測試代碼編寫

      ①添加依賴

              <dependency>
                  <groupId>com.qiniu</groupId>
                  <artifactId>qiniu-java-sdk</artifactId>
                  <version>[7.7.0, 7.7.99]</version>
              </dependency>
      

      ②復(fù)制修改案例代碼

      application.yml

      oss:
        accessKey: xxxx
        secretKey: xxxx
        bucket: sg-blog
      

      OSSTest.java

      @SpringBootTest
      @ConfigurationProperties(prefix = "oss")
      public class OSSTest {
      
          private String accessKey;
          private String secretKey;
          private String bucket;
      
          public void setAccessKey(String accessKey) {
              this.accessKey = accessKey;
          }
      
          public void setSecretKey(String secretKey) {
              this.secretKey = secretKey;
          }
      
          public void setBucket(String bucket) {
              this.bucket = bucket;
          }
      
          @Test
          public void testOss(){
              //構(gòu)造一個(gè)帶指定 Region 對象的配置類
              Configuration cfg = new Configuration(Region.autoRegion());
              //...其他參數(shù)參考類注釋
      
              UploadManager uploadManager = new UploadManager(cfg);
              //...生成上傳憑證,然后準(zhǔn)備上傳
      //        String accessKey = "your access key";
      //        String secretKey = "your secret key";
      //        String bucket = "sg-blog";
      
              //默認(rèn)不指定key的情況下,以文件內(nèi)容的hash值作為文件名
              String key = "2022/sg.png";
      
              try {
      //            byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
      //            ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);
      
      
                  InputStream inputStream = new FileInputStream("C:\\Users\\root\\Desktop\\Snipaste_2022-02-28_22-48-37.png");
                  Auth auth = Auth.create(accessKey, secretKey);
                  String upToken = auth.uploadToken(bucket);
      
                  try {
                      Response response = uploadManager.put(inputStream,key,upToken,null, null);
                      //解析上傳成功的結(jié)果
                      DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
                      System.out.println(putRet.key);
                      System.out.println(putRet.hash);
                  } catch (QiniuException ex) {
                      Response r = ex.response;
                      System.err.println(r.toString());
                      try {
                          System.err.println(r.bodyString());
                      } catch (QiniuException ex2) {
                          //ignore
                      }
                  }
              } catch (Exception ex) {
                  //ignore
              }
      
          }
      }
      

      3.14.2 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      POST /upload 需要token

      參數(shù):

      ? img,值為要上傳的文件

      請求頭:

      ? Content-Type :multipart/form-data;

      響應(yīng)格式:

      {
          "code": 200,
          "data": "文件訪問鏈接",
          "msg": "操作成功"
      }
      

      3.14.3 代碼實(shí)現(xiàn)

      @RestController
      public class UploadController {
          @Autowired
          private UploadService uploadService;
      
          @PostMapping("/upload")
          public ResponseResult uploadImg(MultipartFile img){
              return uploadService.uploadImg(img);
          }
      }
      
      
      public interface UploadService {
          ResponseResult uploadImg(MultipartFile img);
      }
      
      
      @Service
      @Data
      @ConfigurationProperties(prefix = "oss")
      public class OssUploadService implements UploadService {
          @Override
          public ResponseResult uploadImg(MultipartFile img) {
              //判斷文件類型
              //獲取原始文件名
              String originalFilename = img.getOriginalFilename();
              //對原始文件名進(jìn)行判斷
              if(!originalFilename.endsWith(".png")){
                  throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);
              }
      
              //如果判斷通過上傳文件到OSS
              String filePath = PathUtils.generateFilePath(originalFilename);
              String url = uploadOss(img,filePath);//  2099/2/3/wqeqeqe.png
              return ResponseResult.okResult(url);
          }
      
          private String accessKey;
          private String secretKey;
          private String bucket;
      
      
          private String uploadOss(MultipartFile imgFile, String filePath){
              //構(gòu)造一個(gè)帶指定 Region 對象的配置類
              Configuration cfg = new Configuration(Region.autoRegion());
              //...其他參數(shù)參考類注釋
              UploadManager uploadManager = new UploadManager(cfg);
              //默認(rèn)不指定key的情況下,以文件內(nèi)容的hash值作為文件名
              String key = filePath;
              try {
                  InputStream inputStream = imgFile.getInputStream();
                  Auth auth = Auth.create(accessKey, secretKey);
                  String upToken = auth.uploadToken(bucket);
                  try {
                      Response response = uploadManager.put(inputStream,key,upToken,null, null);
                      //解析上傳成功的結(jié)果
                      DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
                      System.out.println(putRet.key);
                      System.out.println(putRet.hash);
                      return "http://r7yxkqloa.bkt.clouddn.com/"+key;
                  } catch (QiniuException ex) {
                      Response r = ex.response;
                      System.err.println(r.toString());
                      try {
                          System.err.println(r.bodyString());
                      } catch (QiniuException ex2) {
                          //ignore
                      }
                  }
              } catch (Exception ex) {
                  //ignore
              }
              return "www";
          }
      }
      
      

      PathUtils

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      public class PathUtils {
      
          public static String generateFilePath(String fileName){
              //根據(jù)日期生成路徑   2022/1/15/
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
              String datePath = sdf.format(new Date());
              //uuid作為文件名
              String uuid = UUID.randomUUID().toString().replaceAll("-", "");
              //后綴和文件后綴一致
              int index = fileName.lastIndexOf(".");
              // test.jpg -> .jpg
              String fileType = fileName.substring(index);
              return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
          }
      }
      
      

      3.15 更新個(gè)人信息接口

      3.15.1 需求

      ? 在編輯完個(gè)人資料后點(diǎn)擊保存會(huì)對個(gè)人資料進(jìn)行更新。

      3.15.2 接口設(shè)計(jì)

      ?

      請求方式 請求地址 請求頭
      PUT /user/userInfo 需要token請求頭

      參數(shù)

      請求體中json格式數(shù)據(jù):

      {
          "avatar":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/01/31/948597e164614902ab1662ba8452e106.png",
          "email":"23412332@qq.com",
          "id":"1",
          "nickName":"sg333",
          "sex":"1"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      3.15.3 代碼實(shí)現(xiàn)

      UserController

          @PutMapping("/userInfo")
          public ResponseResult updateUserInfo(@RequestBody User user){
              return userService.updateUserInfo(user);
          }
      

      UserService

      ResponseResult updateUserInfo(User user);
      

      UserServiceImpl

          @Override
          public ResponseResult updateUserInfo(User user) {
              updateById(user);
              return ResponseResult.okResult();
          }
      

      3.16 用戶注冊

      3.16.1 需求

      ? 要求用戶能夠在注冊界面完成用戶的注冊。要求用戶名,昵稱,郵箱不能和數(shù)據(jù)庫中原有的數(shù)據(jù)重復(fù)。如果某項(xiàng)重復(fù)了注冊失敗并且要有對應(yīng)的提示。并且要求用戶名,密碼,昵稱,郵箱都不能為空。

      ? 注意:密碼必須密文存儲(chǔ)到數(shù)據(jù)庫中。

      3.16.2 接口設(shè)計(jì)

      ?

      請求方式 請求地址 請求頭
      POST /user/register 不需要token請求頭

      參數(shù)

      請求體中json格式數(shù)據(jù):

      {
        "email": "string",
        "nickName": "string",
        "password": "string",
        "userName": "string"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      3.16.3 代碼實(shí)現(xiàn)

      UserController

          @PostMapping("/register")
          public ResponseResult register(@RequestBody User user){
              return userService.register(user);
          }
      

      UserService

      ResponseResult register(User user);
      

      UserServiceImpl

          @Autowired
          private PasswordEncoder passwordEncoder;
          @Override
          public ResponseResult register(User user) {
              //對數(shù)據(jù)進(jìn)行非空判斷
              if(!StringUtils.hasText(user.getUserName())){
                  throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
              }
              if(!StringUtils.hasText(user.getPassword())){
                  throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
              }
              if(!StringUtils.hasText(user.getEmail())){
                  throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
              }
              if(!StringUtils.hasText(user.getNickName())){
                  throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
              }
              //對數(shù)據(jù)進(jìn)行是否存在的判斷
              if(userNameExist(user.getUserName())){
                  throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
              }
              if(nickNameExist(user.getNickName())){
                  throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
              }
              //...
              //對密碼進(jìn)行加密
              String encodePassword = passwordEncoder.encode(user.getPassword());
              user.setPassword(encodePassword);
              //存入數(shù)據(jù)庫
              save(user);
              return ResponseResult.okResult();
          }
      
      
      public enum AppHttpCodeEnum {
          // 成功
          SUCCESS(200,"操作成功"),
          // 登錄
          NEED_LOGIN(401,"需要登錄后操作"),
          NO_OPERATOR_AUTH(403,"無權(quán)限操作"),
          SYSTEM_ERROR(500,"出現(xiàn)錯(cuò)誤"),
          USERNAME_EXIST(501,"用戶名已存在"),
           PHONENUMBER_EXIST(502,"手機(jī)號已存在"), EMAIL_EXIST(503, "郵箱已存在"),
          REQUIRE_USERNAME(504, "必需填寫用戶名"),
          CONTENT_NOT_NULL(506, "評論內(nèi)容不能為空"),
          FILE_TYPE_ERROR(507, "文件類型錯(cuò)誤,請上傳png文件"),
          USERNAME_NOT_NULL(508, "用戶名不能為空"),
          NICKNAME_NOT_NULL(509, "昵稱不能為空"),
          PASSWORD_NOT_NULL(510, "密碼不能為空"),
          EMAIL_NOT_NULL(511, "郵箱不能為空"),
          NICKNAME_EXIST(512, "昵稱已存在"),
          LOGIN_ERROR(505,"用戶名或密碼錯(cuò)誤");
          int code;
          String msg;
      
          AppHttpCodeEnum(int code, String errorMessage){
              this.code = code;
              this.msg = errorMessage;
          }
      
          public int getCode() {
              return code;
          }
      
          public String getMsg() {
              return msg;
          }
      }
      
      

      3.17 AOP實(shí)現(xiàn)日志記錄

      3.17.1 需求

      ? 需要通過日志記錄接口調(diào)用信息。便于后期調(diào)試排查。并且可能有很多接口都需要進(jìn)行日志的記錄。

      ? 接口被調(diào)用時(shí)日志打印格式如下:

      image-20220313133714102

      3.17.2 思路分析

      ? 相當(dāng)于是對原有的功能進(jìn)行增強(qiáng)。并且是批量的增強(qiáng),這個(gè)時(shí)候就非常適合用AOP來進(jìn)行實(shí)現(xiàn)。

      ?

      3.17.3 代碼實(shí)現(xiàn)

      日志打印格式

              log.info("=======Start=======");
              // 打印請求 URL
              log.info("URL            : {}",);
              // 打印描述信息
              log.info("BusinessName   : {}", );
              // 打印 Http method
              log.info("HTTP Method    : {}", );
              // 打印調(diào)用 controller 的全路徑以及執(zhí)行方法
              log.info("Class Method   : {}.{}", );
              // 打印請求的 IP
              log.info("IP             : {}",);
              // 打印請求入?yún)?        log.info("Request Args   : {}",);
              // 打印出參
              log.info("Response       : {}", );
              // 結(jié)束后換行
              log.info("=======End=======" + System.lineSeparator());
      

      3.18 更新瀏覽次數(shù)

      3.18.1 需求

      ? 在用戶瀏覽博文時(shí)要實(shí)現(xiàn)對應(yīng)博客瀏覽量的增加。

      3.18.2 思路分析

      ? 我們只需要在每次用戶瀏覽博客時(shí)更新對應(yīng)的瀏覽數(shù)即可。

      ? 但是如果直接操作博客表的瀏覽量的話,在并發(fā)量大的情況下會(huì)出現(xiàn)什么問題呢?

      ? 如何去優(yōu)化呢?

      ?

      ①在應(yīng)用啟動(dòng)時(shí)把博客的瀏覽量存儲(chǔ)到redis中

      ②更新瀏覽量時(shí)去更新redis中的數(shù)據(jù)

      ③每隔10分鐘把Redis中的瀏覽量更新到數(shù)據(jù)庫中

      ④讀取文章瀏覽量時(shí)從redis讀取

      3.18.3 鋪墊知識(shí)

      3.18.3.1 CommandLineRunner實(shí)現(xiàn)項(xiàng)目啟動(dòng)時(shí)預(yù)處理

      ? 如果希望在SpringBoot應(yīng)用啟動(dòng)時(shí)進(jìn)行一些初始化操作可以選擇使用CommandLineRunner來進(jìn)行處理。

      ? 我們只需要實(shí)現(xiàn)CommandLineRunner接口,并且把對應(yīng)的bean注入容器。把相關(guān)初始化的代碼重新到需要重新的方法中。

      ? 這樣就會(huì)在應(yīng)用啟動(dòng)的時(shí)候執(zhí)行對應(yīng)的代碼。

      @Component
      public class TestRunner implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("程序初始化");
          }
      }
      
      
      3.18.3.2 定時(shí)任務(wù)

      ? 定時(shí)任務(wù)的實(shí)現(xiàn)方式有很多,比如XXL-Job等。但是其實(shí)核心功能和概念都是類似的,很多情況下只是調(diào)用的API不同而已。

      ? 這里就先用SpringBoot為我們提供的定時(shí)任務(wù)的API來實(shí)現(xiàn)一個(gè)簡單的定時(shí)任務(wù),讓大家先對定時(shí)任務(wù)里面的一些核心概念有個(gè)大致的了解。

      實(shí)現(xiàn)步驟

      ① 使用@EnableScheduling注解開啟定時(shí)任務(wù)功能

      ? 我們可以在配置類上加上@EnableScheduling

      @SpringBootApplication
      @MapperScan("com.sangeng.mapper")
      @EnableScheduling
      public class SanGengBlogApplication {
          public static void main(String[] args) {
              SpringApplication.run(SanGengBlogApplication.class,args);
          }
      }
      

      ② 確定定時(shí)任務(wù)執(zhí)行代碼,并配置任務(wù)執(zhí)行時(shí)間

      ? 使用@Scheduled注解標(biāo)識(shí)需要定時(shí)執(zhí)行的代碼。注解的cron屬性相當(dāng)于是任務(wù)的執(zhí)行時(shí)間。目前可以使用 0/5 * * * * ? 進(jìn)行測試,代表從0秒開始,每隔5秒執(zhí)行一次。

      ? 注意:對應(yīng)的bean要注入容器,否則不會(huì)生效。

      @Component
      public class TestJob {
      
          @Scheduled(cron = "0/5 * * * * ?")
          public void testJob(){
              //要執(zhí)行的代碼
              System.out.println("定時(shí)任務(wù)執(zhí)行了");
          }
      }
      
      
      3.18.3.2.1 cron 表達(dá)式語法

      ? cron表達(dá)式是用來設(shè)置定時(shí)任務(wù)執(zhí)行時(shí)間的表達(dá)式。

      ? 很多情況下我們可以用 : 在線Cron表達(dá)式生成器 來幫助我們理解cron表達(dá)式和書寫cron表達(dá)式。

      ? 但是我們還是有需要學(xué)習(xí)對應(yīng)的Cron語法的,這樣可以更有利于我們書寫Cron表達(dá)式。

      如上我們用到的 0/5 * * * * ? *,cron表達(dá)式由七部分組成,中間由空格分隔,這七部分從左往右依次是:

      秒(059),分鐘(059),小時(shí)(0~23),日期(1-月最后一天),月份(1-12),星期幾(1-7,1表示星期日),年份(一般該項(xiàng)不設(shè)置,直接忽略掉,即可為空值)

      通用特殊字符:, - * / (可以在任意部分使用)

      星號表示任意值,例如:

      * * * * * ?
      

      表示 “ 每年每月每天每時(shí)每分每秒 ” 。

      ,

      可以用來定義列表,例如 :

      1,2,3 * * * * ?
      

      表示 “ 每年每月每天每時(shí)每分的每個(gè)第1秒,第2秒,第3秒 ” 。

      定義范圍,例如:

      1-3 * * * * ?
      

      表示 “ 每年每月每天每時(shí)每分的第1秒至第3秒 ”。

      /

      每隔多少,例如

      5/10 * * * * ?
      

      表示 “ 每年每月每天每時(shí)每分,從第5秒開始,每10秒一次 ” 。即 “ / ” 的左側(cè)是開始值,右側(cè)是間隔。如果是從 “ 0 ” 開始的話,也可以簡寫成 “ /10 ”

      日期部分還可允許特殊字符: ? L W

      星期部分還可允許的特殊字符: ? L #

      ?

      只可用在日期和星期部分。表示沒有具體的值,使用?要注意沖突。日期和星期兩個(gè)部分如果其中一個(gè)部分設(shè)置了值,則另一個(gè)必須設(shè)置為 “ ? ”。

      例如:

      0\* * * 2 * ?
       和
      0\* * * ? * 2
      

      同時(shí)使用?和同時(shí)不使用?都是不對的

      例如下面寫法就是錯(cuò)的

      * * * 2 * 2
       和
      * * * ? * ?
      
      

      W

      只能用在日期中,表示當(dāng)月中最接近某天的工作日

      0 0 0 31W * ?
      

      表示最接近31號的工作日,如果31號是星期六,則表示30號,即星期五,如果31號是星期天,則表示29號,即星期五。如果31號是星期三,則表示31號本身,即星期三。

      L

      表示最后(Last),只能用在日期和星期中

      在日期中表示每月最后一天,在一月份中表示31號,在六月份中表示30號

      也可以表示每月倒是第N天。例如: L-2表示每個(gè)月的倒數(shù)第2天

      0 0 0 LW * ?
      LW可以連起來用,表示每月最后一個(gè)工作日,即每月最后一個(gè)星期五

      在星期中表示7即星期六

      0 0 0 ? * L
      表示每個(gè)星期六
      0 0 0 ? * 6L
      若前面有其他值的話,則表示最后一個(gè)星期幾,即每月的最后一個(gè)星期五
      

      只能用在星期中,表示第幾個(gè)星期幾

      0 0 0 ? * 6#3
      表示每個(gè)月的第三個(gè)星期五。
      

      3.18.4 接口設(shè)計(jì)

      ?

      請求方式 請求地址 請求頭
      PUT /article/updateViewCount/ 不需要token請求頭

      參數(shù)

      ? 請求路徑中攜帶文章id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      3.18.5 代碼實(shí)現(xiàn)

      ①在應(yīng)用啟動(dòng)時(shí)把博客的瀏覽量存儲(chǔ)到redis中

      ? 實(shí)現(xiàn)CommandLineRunner接口,在應(yīng)用啟動(dòng)時(shí)初始化緩存。

      @Component
      public class ViewCountRunner implements CommandLineRunner {
      
          @Autowired
          private ArticleMapper articleMapper;
      
          @Autowired
          private RedisCache redisCache;
      
          @Override
          public void run(String... args) throws Exception {
              //查詢博客信息  id  viewCount
              List<Article> articles = articleMapper.selectList(null);
              Map<String, Integer> viewCountMap = articles.stream()
                      .collect(Collectors.toMap(article -> article.getId().toString(), article -> {
                          return article.getViewCount().intValue();//
                      }));
              //存儲(chǔ)到redis中
              redisCache.setCacheMap("article:viewCount",viewCountMap);
          }
      }
      
      
      ②更新瀏覽量時(shí)去更新redsi中的數(shù)據(jù)

      RedisCache增加方法

          public void incrementCacheMapValue(String key,String hKey,long v){
              redisTemplate.boundHashOps(key).increment(hKey, v);
          }
      

      ArticleController中增加方法更新閱讀數(shù)

          @PutMapping("/updateViewCount/{id}")
          public ResponseResult updateViewCount(@PathVariable("id") Long id){
              return articleService.updateViewCount(id);
          }
      

      ArticleService中增加方法

      ResponseResult updateViewCount(Long id);
      

      ArticleServiceImpl中實(shí)現(xiàn)方法

          @Override
          public ResponseResult updateViewCount(Long id) {
              //更新redis中對應(yīng) id的瀏覽量
              redisCache.incrementCacheMapValue("article:viewCount",id.toString(),1);
              return ResponseResult.okResult();
          }
      
      ③定時(shí)任務(wù)每隔10分鐘把Redis中的瀏覽量更新到數(shù)據(jù)庫中

      Article中增加構(gòu)造方法

          public Article(Long id, long viewCount) {
              this.id = id;
              this.viewCount = viewCount;
          }
      
      @Component
      public class UpdateViewCountJob {
      
          @Autowired
          private RedisCache redisCache;
      
          @Autowired
          private ArticleService articleService;
      
          @Scheduled(cron = "0/5 * * * * ?")
          public void updateViewCount(){
              //獲取redis中的瀏覽量
              Map<String, Integer> viewCountMap = redisCache.getCacheMap("article:viewCount");
      
              List<Article> articles = viewCountMap.entrySet()
                      .stream()
                      .map(entry -> new Article(Long.valueOf(entry.getKey()), entry.getValue().longValue()))
                      .collect(Collectors.toList());
              //更新到數(shù)據(jù)庫中
              articleService.updateBatchById(articles);
      
          }
      }
      
      
      ④讀取文章瀏覽量時(shí)從redis讀取
          @Override
          public ResponseResult getArticleDetail(Long id) {
              //根據(jù)id查詢文章
              Article article = getById(id);
              //從redis中獲取viewCount
              Integer viewCount = redisCache.getCacheMapValue("article:viewCount", id.toString());
              article.setViewCount(viewCount.longValue());
              //轉(zhuǎn)換成VO
              ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
              //根據(jù)分類id查詢分類名
              Long categoryId = articleDetailVo.getCategoryId();
              Category category = categoryService.getById(categoryId);
              if(category!=null){
                  articleDetailVo.setCategoryName(category.getName());
              }
              //封裝響應(yīng)返回
              return ResponseResult.okResult(articleDetailVo);
          }
      
      

      4. Swagger2

      4.1 簡介

      ? Swagger 是一套基于 OpenAPI 規(guī)范構(gòu)建的開源工具,可以幫助我們設(shè)計(jì)、構(gòu)建、記錄以及使用 Rest API。

      4.2 為什么使用Swagger

      ? 當(dāng)下很多公司都采取前后端分離的開發(fā)模式,前端和后端的工作由不同的工程師完成。在這種開發(fā)模式下,維持一份及時(shí)更新且完整的 Rest API 文檔將會(huì)極大的提高我們的工作效率。傳統(tǒng)意義上的文檔都是后端開發(fā)人員手動(dòng)編寫的,相信大家也都知道這種方式很難保證文檔的及時(shí)性,這種文檔久而久之也就會(huì)失去其參考意義,反而還會(huì)加大我們的溝通成本。而 Swagger 給我們提供了一個(gè)全新的維護(hù) API 文檔的方式,下面我們就來了解一下它的優(yōu)點(diǎn):

      1.代碼變,文檔變。只需要少量的注解,Swagger 就可以根據(jù)代碼自動(dòng)生成 API 文檔,很好的保證了文檔的時(shí)效性。
      2.跨語言性,支持 40 多種語言。
      3.Swagger UI 呈現(xiàn)出來的是一份可交互式的 API 文檔,我們可以直接在文檔頁面嘗試 API 的調(diào)用,省去了準(zhǔn)備復(fù)雜的調(diào)用參數(shù)的過程。

      4.3 快速入門

      4.3.1 引入依賴

              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger2</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.springfox</groupId>
                  <artifactId>springfox-swagger-ui</artifactId>
              </dependency>
      
      

      4.3.2 啟用Swagger2

      ? 在啟動(dòng)類上或者配置類加 @EnableSwagger2 注解

      @SpringBootApplication
      @MapperScan("com.sangeng.mapper")
      @EnableScheduling
      @EnableSwagger2
      public class SanGengBlogApplication {
          public static void main(String[] args) {
              SpringApplication.run(SanGengBlogApplication.class,args);
          }
      }
      

      4.3.3 測試

      ? 訪問:http://localhost:7777/swagger-ui.html 注意其中l(wèi)ocalhost和7777要調(diào)整成實(shí)際項(xiàng)目的域名和端口號。

      4.4 具體配置

      4.4.1 Controller配置

      4.4.1 @Api 注解

      屬性介紹:

      tags 設(shè)置標(biāo)簽

      description 設(shè)置描述信息

      @RestController
      @RequestMapping("/comment")
      @Api(tags = "評論",description = "評論相關(guān)接口")
      public class CommentController {
      }
      

      4.4.2 接口配置

      4.4.2.1 接口描述配置@ApiOperation
          @GetMapping("/linkCommentList")
          @ApiOperation(value = "友鏈評論列表",notes = "獲取一頁友鏈評論")
          public ResponseResult linkCommentList(Integer pageNum,Integer pageSize){
              return commentService.commentList(SystemConstants.LINK_COMMENT,null,pageNum,pageSize);
          }
      
      4.4.2.2 接口參數(shù)描述

      @ApiImplicitParam 用于描述接口的參數(shù),但是一個(gè)接口可能有多個(gè)參數(shù),所以一般與 @ApiImplicitParams 組合使用。

          @GetMapping("/linkCommentList")
          @ApiOperation(value = "友鏈評論列表",notes = "獲取一頁友鏈評論")
          @ApiImplicitParams({
                 @ApiImplicitParam(name = "pageNum",value = "頁號"),
                 @ApiImplicitParam(name = "pageSize",value = "每頁大小")
          }
          )
          public ResponseResult linkCommentList(Integer pageNum,Integer pageSize){
              return commentService.commentList(SystemConstants.LINK_COMMENT,null,pageNum,pageSize);
          }
      

      4.4.3 實(shí)體類配置

      4.4.3.1 實(shí)體的描述配置@ApiModel

      @ApiModel用于描述實(shí)體類。

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @ApiModel(description = "添加評論dto")
      public class AddCommentDto{
          //..
      }
      
      4.4.3.2 實(shí)體的屬性的描述配置@ApiModelProperty

      @ApiModelProperty用于描述實(shí)體的屬性

          @ApiModelProperty(notes = "評論類型(0代表文章評論,1代表友鏈評論)")
          private String type;
      

      4.4.4 文檔信息配置

      @Configuration
      public class SwaggerConfig {
          @Bean
          public Docket customDocket() {
              return new Docket(DocumentationType.SWAGGER_2)
                      .apiInfo(apiInfo())
                      .select()
                      .apis(RequestHandlerSelectors.basePackage("com.sangeng.controller"))
                      .build();
          }
      
          private ApiInfo apiInfo() {
              Contact contact = new Contact("團(tuán)隊(duì)名", "http://www.my.com", "my@my.com");
              return new ApiInfoBuilder()
                      .title("文檔標(biāo)題")
                      .description("文檔描述")
                      .contact(contact)   // 聯(lián)系方式
                      .version("1.1.0")  // 版本
                      .build();
          }
      }
      

      5. 博客后臺(tái)

      5.0 準(zhǔn)備工作

      前端工程啟動(dòng)

      npm install

      npm run dev

      ①創(chuàng)建啟動(dòng)類

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @SpringBootApplication
      @MapperScan("com.sangeng.mapper")
      public class BlogAdminApplication {
          public static void main(String[] args) {
              SpringApplication.run(BlogAdminApplication.class, args);
          }
      }
      
      

      ②創(chuàng)建application.yml配置文件

      server:
        port: 8989
      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/sg_blog?characterEncoding=utf-8&serverTimezone=UTC
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        servlet:
          multipart:
            max-file-size: 2MB
            max-request-size: 5MB
      
      mybatis-plus:
        configuration:
          # 日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        global-config:
          db-config:
            logic-delete-field: delFlag
            logic-delete-value: 1
            logic-not-delete-value: 0
            id-type: auto
      
      
      

      ③ SQL語句

      ? SQL腳本:SGBlog\資源\SQL\sg_tag.sql

      ④ 創(chuàng)建實(shí)體類,Mapper,Service

      ? 注意思考這些文件應(yīng)該寫在哪個(gè)模塊下?

      Tag

      @SuppressWarnings("serial")
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @TableName("sg_tag")
      public class Tag  {
          @TableId
          private Long id;
      
          
          private Long createBy;
          
          private Date createTime;
          
          private Long updateBy;
          
          private Date updateTime;
          //刪除標(biāo)志(0代表未刪除,1代表已刪除)
          private Integer delFlag;
          //備注
          private String remark;
          //標(biāo)簽名
          private String name;
      
      
      
      }
      
      

      TagMapper

      /**
       * 標(biāo)簽(Tag)表數(shù)據(jù)庫訪問層
       *
       * @author makejava
       * @since 2022-07-19 22:33:35
       */
      public interface TagMapper extends BaseMapper<Tag> {
      
      }
      
      
      
      

      TagService

      /**
       * 標(biāo)簽(Tag)表服務(wù)接口
       *
       * @author makejava
       * @since 2022-07-19 22:33:38
       */
      public interface TagService extends IService<Tag> {
      
      }
      
      

      TagServiceImpl

      /**
       * 標(biāo)簽(Tag)表服務(wù)實(shí)現(xiàn)類
       *
       * @author makejava
       * @since 2022-07-19 22:33:38
       */
      @Service("tagService")
      public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagService {
      
      }
      
      
      

      ⑤ 創(chuàng)建Controller測試接口

      ? 注意思考這些文件應(yīng)該寫在哪個(gè)模塊下?

      TagController /content/tag

      @RestController
      @RequestMapping("/content/tag")
      public class TagController {
          @Autowired
          private TagService tagService;
      
          @GetMapping("/list")
          public ResponseResult list(){
              return ResponseResult.okResult(tagService.list());
          }
      }
      
      
      

      ⑥添加security相關(guān)類

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          @Bean
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      
          @Autowired
          private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
          @Autowired
          AuthenticationEntryPoint authenticationEntryPoint;
          @Autowired
          AccessDeniedHandler accessDeniedHandler;
      
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
      //                .antMatchers("/login").anonymous()
      //                //注銷接口需要認(rèn)證才能訪問
      //                .antMatchers("/logout").authenticated()
      //                .antMatchers("/user/userInfo").authenticated()
      //                .antMatchers("/upload").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().permitAll();
      
              //配置異常處理器
              http.exceptionHandling()
                      .authenticationEntryPoint(authenticationEntryPoint)
                      .accessDeniedHandler(accessDeniedHandler);
              //關(guān)閉默認(rèn)的注銷功能
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      
          @Bean
          public PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
      }
      
      
      @Component
      public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
      
          @Autowired
          private RedisCache redisCache;
      
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
              //獲取請求頭中的token
              String token = request.getHeader("token");
              if(!StringUtils.hasText(token)){
                  //說明該接口不需要登錄  直接放行
                  filterChain.doFilter(request, response);
                  return;
              }
              //解析獲取userid
              Claims claims = null;
              try {
                  claims = JwtUtil.parseJWT(token);
              } catch (Exception e) {
                  e.printStackTrace();
                  //token超時(shí)  token非法
                  //響應(yīng)告訴前端需要重新登錄
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                  WebUtils.renderString(response, JSON.toJSONString(result));
                  return;
              }
              String userId = claims.getSubject();
              //從redis中獲取用戶信息
              LoginUser loginUser = redisCache.getCacheObject("login:" + userId);
              //如果獲取不到
              if(Objects.isNull(loginUser)){
                  //說明登錄過期  提示重新登錄
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                  WebUtils.renderString(response, JSON.toJSONString(result));
                  return;
              }
              //存入SecurityContextHolder
              UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
              SecurityContextHolder.getContext().setAuthentication(authenticationToken);
      
              filterChain.doFilter(request, response);
          }
      
      
      }
      
      

      5.1 后臺(tái)登錄

      ? 后臺(tái)的認(rèn)證授權(quán)也使用SpringSecurity安全框架來實(shí)現(xiàn)。

      5.1.0 需求

      ? 需要實(shí)現(xiàn)登錄功能

      ? 后臺(tái)所有功能都必須登錄才能使用。

      5.1.1 接口設(shè)計(jì)

      請求方式 請求路徑
      POST /user/login

      請求體:

      {
          "userName":"sg",
          "password":"1234"
      }
      

      響應(yīng)格式:

      {
          "code": 200,
          "data": {
              "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODBmOThmYmJkNmI0NjM0OWUyZjY2NTM0NGNjZWY2NSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0Mzg3NDMxNiwiZXhwIjoxNjQzOTYwNzE2fQ.ldLBUvNIxQCGemkCoMgT_0YsjsWndTg5tqfJb77pabk"
          },
          "msg": "操作成功"
      }
      

      5.1.2 思路分析

      登錄

      ? ①自定義登錄接口

      ? 調(diào)用ProviderManager的方法進(jìn)行認(rèn)證 如果認(rèn)證通過生成jwt

      ? 把用戶信息存入redis中

      ? ②自定義UserDetailsService

      ? 在這個(gè)實(shí)現(xiàn)類中去查詢數(shù)據(jù)庫

      ? 注意配置passwordEncoder為BCryptPasswordEncoder

      校驗(yàn):

      ? ①定義Jwt認(rèn)證過濾器

      ? 獲取token

      ? 解析token獲取其中的userid

      ? 從redis中獲取用戶信息

      ? 存入SecurityContextHolder

      5.1.3 準(zhǔn)備工作

      ①添加依賴

      前面已經(jīng)添加過相關(guān)依賴,不需要做什么處理

              <!--redis依賴-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
              </dependency>
              <!--fastjson依賴-->
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.33</version>
              </dependency>
              <!--jwt依賴-->
              <dependency>
                  <groupId>io.jsonwebtoken</groupId>
                  <artifactId>jjwt</artifactId>
                  <version>0.9.0</version>
              </dependency>
      

      5.1.4 登錄接口代碼實(shí)現(xiàn)

      LoginController

      復(fù)制一份BlogLoginController ,命名為LoginController,其中注入 LoginService

      請求地址修改為/user/login即可

      @RestController
      public class LoginController {
          @Autowired
          private LoginService loginService;
      
          @PostMapping("/user/login")
          public ResponseResult login(@RequestBody User user){
              if(!StringUtils.hasText(user.getUserName())){
                  //提示 必須要傳用戶名
                  throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
              }
              return loginService.login(user);
          }
      
      }
      
      LoginService

      復(fù)制一份BlogLoginService命名為LoginService即可

      public interface LoginService {
          ResponseResult login(User user);
      
      }
      
      
      SecurityConfig

      之前已經(jīng)復(fù)制過了

      SystemLoginServiceImpl

      復(fù)制一份,LoginServiceImpl,命名為SystemLoginServiceImpl 實(shí)現(xiàn) LoginService

      login方法中存redis的key的前綴修改為login

      返回的數(shù)據(jù)中只要返回token

      @Service
      public class SystemLoginServiceImpl implements LoginService {
      
          @Autowired
          private AuthenticationManager authenticationManager;
      
          @Autowired
          private RedisCache redisCache;
      
          @Override
          public ResponseResult login(User user) {
              UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
              Authentication authenticate = authenticationManager.authenticate(authenticationToken);
              //判斷是否認(rèn)證通過
              if(Objects.isNull(authenticate)){
                  throw new RuntimeException("用戶名或密碼錯(cuò)誤");
              }
              //獲取userid 生成token
              LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
              String userId = loginUser.getUser().getId().toString();
              String jwt = JwtUtil.createJWT(userId);
              //把用戶信息存入redis
              redisCache.setCacheObject("login:"+userId,loginUser);
      
              //把token封裝 返回
      		Map<String,String> map = new HashMap<>();
              map.put("token",jwt);
              return ResponseResult.okResult(map);
          }
      }
      
      UserDetailServiceImpl

      復(fù)用原來的即可

      LoginUser

      復(fù)用原來的即可

      5.2 后臺(tái)權(quán)限控制及動(dòng)態(tài)路由

      需求

      ? 后臺(tái)系統(tǒng)需要能實(shí)現(xiàn)不同的用戶權(quán)限可以看到不同的功能。

      ? 用戶只能使用他的權(quán)限所允許使用的功能。

      功能設(shè)計(jì)

      ? 之前在我的SpringSecurity的課程中就介紹過RBAC權(quán)限模型。沒有學(xué)習(xí)過的可以去看下 RBAC權(quán)限模型 。這里我們就是在RBAC權(quán)限模型的基礎(chǔ)上去實(shí)現(xiàn)這個(gè)功能。

      ?

      表分析

      ? 通過需求去分析需要有哪些字段。

      ? 建表SQL及初始化數(shù)據(jù)見:SGBlog\資源\SQL\sg_menu.sql

      接口設(shè)計(jì)

      getInfo接口

      請求方式 請求地址 請求頭
      GET /getInfo 需要token請求頭

      請求參數(shù):

      響應(yīng)格式:

      如果用戶id為1代表管理員,roles 中只需要有admin,permissions中需要有所有菜單類型為C或者F的,狀態(tài)為正常的,未被刪除的權(quán)限

      {
      	"code":200,
      	"data":{
      		"permissions":[
      			"system:user:list",
                  "system:role:list",
      			"system:menu:list",
      			"system:user:query",
      			"system:user:add"
                  //此次省略1000字
      		],
      		"roles":[
      			"admin"
      		],
      		"user":{
      			"avatar":"http://r7yxkqloa.bkt.clouddn.com/2022/03/05/75fd15587811443a9a9a771f24da458d.png",
      			"email":"23412332@qq.com",
      			"id":1,
      			"nickName":"sg3334",
      			"sex":"1"
      		}
      	},
      	"msg":"操作成功"
      }
      
      getRouters接口
      請求方式 請求地址 請求頭
      GET /getRouters 需要token請求頭

      請求參數(shù):

      響應(yīng)格式:

      ? 前端為了實(shí)現(xiàn)動(dòng)態(tài)路由的效果,需要后端有接口能返回用戶所能訪問的菜單數(shù)據(jù)。

      ? 注意:返回的菜單數(shù)據(jù)需要體現(xiàn)父子菜單的層級關(guān)系

      ? 如果用戶id為1代表管理員,menus中需要有所有菜單類型為C或者M(jìn)的,狀態(tài)為正常的,未被刪除的權(quán)限

      ? 數(shù)據(jù)格式如下:

      {
      	"code":200,
      	"data":{
      		"menus":[
      			{
      				"children":[],
      				"component":"content/article/write/index",
      				"createTime":"2022-01-08 11:39:58",
      				"icon":"build",
      				"id":2023,
      				"menuName":"寫博文",
      				"menuType":"C",
      				"orderNum":"0",
      				"parentId":0,
      				"path":"write",
      				"perms":"content:article:writer",
      				"status":"0",
      				"visible":"0"
      			},
      			{
      				"children":[
      					{
      						"children":[],
      						"component":"system/user/index",
      						"createTime":"2021-11-12 18:46:19",
      						"icon":"user",
      						"id":100,
      						"menuName":"用戶管理",
      						"menuType":"C",
      						"orderNum":"1",
      						"parentId":1,
      						"path":"user",
      						"perms":"system:user:list",
      						"status":"0",
      						"visible":"0"
      					},
      					{
      						"children":[],
      						"component":"system/role/index",
      						"createTime":"2021-11-12 18:46:19",
      						"icon":"peoples",
      						"id":101,
      						"menuName":"角色管理",
      						"menuType":"C",
      						"orderNum":"2",
      						"parentId":1,
      						"path":"role",
      						"perms":"system:role:list",
      						"status":"0",
      						"visible":"0"
      					},
      					{
      						"children":[],
      						"component":"system/menu/index",
      						"createTime":"2021-11-12 18:46:19",
      						"icon":"tree-table",
      						"id":102,
      						"menuName":"菜單管理",
      						"menuType":"C",
      						"orderNum":"3",
      						"parentId":1,
      						"path":"menu",
      						"perms":"system:menu:list",
      						"status":"0",
      						"visible":"0"
      					}
      				],
      				"createTime":"2021-11-12 18:46:19",
      				"icon":"system",
      				"id":1,
      				"menuName":"系統(tǒng)管理",
      				"menuType":"M",
      				"orderNum":"1",
      				"parentId":0,
      				"path":"system",
      				"perms":"",
      				"status":"0",
      				"visible":"0"
      			}
      		]
      	},
      	"msg":"操作成功"
      }
      

      代碼實(shí)現(xiàn)

      準(zhǔn)備工作

      ? 生成menu和role表對于的類

      getInfo接口
      @Data
      @Accessors(chain = true)
      @AllArgsConstructor
      @NoArgsConstructor
      public class AdminUserInfoVo {
      
          private List<String> permissions;
      
          private List<String> roles;
      
          private UserInfoVo user;
      }
      
      @RestController
      public class LoginController {
          @Autowired
          private LoginService loginService;
      
          @Autowired
          private MenuService menuService;
      
          @Autowired
          private RoleService roleService;
      
          @PostMapping("/user/login")
          public ResponseResult login(@RequestBody User user){
              if(!StringUtils.hasText(user.getUserName())){
                  //提示 必須要傳用戶名
                  throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
              }
              return loginService.login(user);
          }
      
          @GetMapping("getInfo")
          public ResponseResult<AdminUserInfoVo> getInfo(){
              //獲取當(dāng)前登錄的用戶
              LoginUser loginUser = SecurityUtils.getLoginUser();
              //根據(jù)用戶id查詢權(quán)限信息
              List<String> perms = menuService.selectPermsByUserId(loginUser.getUser().getId());
              //根據(jù)用戶id查詢角色信息
              List<String> roleKeyList = roleService.selectRoleKeyByUserId(loginUser.getUser().getId());
      
              //獲取用戶信息
              User user = loginUser.getUser();
              UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
              //封裝數(shù)據(jù)返回
      
              AdminUserInfoVo adminUserInfoVo = new AdminUserInfoVo(perms,roleKeyList,userInfoVo);
              return ResponseResult.okResult(adminUserInfoVo);
          }
      
      }
      

      RoleServiceImpl selectRoleKeyByUserId方法

      @Service("menuService")
      public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
      
          @Override
          public List<String> selectPermsByUserId(Long id) {
              //如果是管理員,返回所有的權(quán)限
              if(id == 1L){
                  LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
                  wrapper.in(Menu::getMenuType,SystemConstants.MENU,SystemConstants.BUTTON);
                  wrapper.eq(Menu::getStatus,SystemConstants.STATUS_NORMAL);
                  List<Menu> menus = list(wrapper);
                  List<String> perms = menus.stream()
                          .map(Menu::getPerms)
                          .collect(Collectors.toList());
                  return perms;
              }
              //否則返回所具有的權(quán)限
              return getBaseMapper().selectPermsByUserId(id);
          }
      }
      

      MenuMapper

      /**
       * 菜單權(quán)限表(Menu)表數(shù)據(jù)庫訪問層
       *
       * @author makejava
       * @since 2022-08-09 22:32:07
       */
      public interface MenuMapper extends BaseMapper<Menu> {
      
          List<String> selectPermsByUserId(Long userId);
      }
      
      
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
      <mapper namespace="com.sangeng.mapper.MenuMapper">
      
          <select id="selectPermsByUserId" resultType="java.lang.String">
              SELECT
                  DISTINCT m.perms
              FROM
                  `sys_user_role` ur
                  LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                  LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
              WHERE
                  ur.`user_id` = #{userId} AND
                  m.`menu_type` IN ('C','F') AND
                  m.`status` = 0 AND
                  m.`del_flag` = 0
          </select>
      </mapper>
      

      MenuServiceImpl selectPermsByUserId方法

      @Service("roleService")
      public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
      
          @Override
          public List<String> selectRoleKeyByUserId(Long id) {
              //判斷是否是管理員 如果是返回集合中只需要有admin
              if(id == 1L){
                  List<String> roleKeys = new ArrayList<>();
                  roleKeys.add("admin");
                  return roleKeys;
              }
              //否則查詢用戶所具有的角色信息
              return getBaseMapper().selectRoleKeyByUserId(id);
          }
      }
      
      public interface RoleMapper extends BaseMapper<Role> {
      
          List<String> selectRoleKeyByUserId(Long userId);
      }
      
      
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
      <mapper namespace="com.sangeng.mapper.RoleMapper">
          <select id="selectRoleKeyByUserId" resultType="java.lang.String">
              SELECT
                  r.`role_key`
              FROM
                  `sys_user_role` ur
                  LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
              WHERE
                  ur.`user_id` = #{userId} AND
                  r.`status` = 0 AND
                  r.`del_flag` = 0
          </select>
      </mapper>
      
      getRouters接口

      RoutersVo

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class RoutersVo {
      
          private List<Menu> menus;
      }
      
      

      LoginController

          @GetMapping("getRouters")
          public ResponseResult<RoutersVo> getRouters(){
              Long userId = SecurityUtils.getUserId();
              //查詢menu 結(jié)果是tree的形式
              List<Menu> menus = menuService.selectRouterMenuTreeByUserId(userId);
              //封裝數(shù)據(jù)返回
              return ResponseResult.okResult(new RoutersVo(menus));
          }
      
      

      MenuService

      public interface MenuService extends IService<Menu> {
      
          List<String> selectPermsByUserId(Long id);
      
          List<Menu> selectRouterMenuTreeByUserId(Long userId);
      }
      
      

      MenuServiceImpl

      @Override
          public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
              MenuMapper menuMapper = getBaseMapper();
              List<Menu> menus = null;
              //判斷是否是管理員
              if(SecurityUtils.isAdmin()){
                  //如果是 獲取所有符合要求的Menu
                  menus = menuMapper.selectAllRouterMenu();
              }else{
                  //否則  獲取當(dāng)前用戶所具有的Menu
                  menus = menuMapper.selectRouterMenuTreeByUserId(userId);
              }
      
              //構(gòu)建tree
              //先找出第一層的菜單  然后去找他們的子菜單設(shè)置到children屬性中
              List<Menu> menuTree = builderMenuTree(menus,0L);
              return menuTree;
          }
      
          private List<Menu> builderMenuTree(List<Menu> menus, Long parentId) {
              List<Menu> menuTree = menus.stream()
                      .filter(menu -> menu.getParentId().equals(parentId))
                      .map(menu -> menu.setChildren(getChildren(menu, menus)))
                      .collect(Collectors.toList());
              return menuTree;
          }
      
          /**
           * 獲取存入?yún)?shù)的 子Menu集合
           * @param menu
           * @param menus
           * @return
           */
          private List<Menu> getChildren(Menu menu, List<Menu> menus) {
              List<Menu> childrenList = menus.stream()
                      .filter(m -> m.getParentId().equals(menu.getId()))
                      .map(m->m.setChildren(getChildren(m,menus)))
                      .collect(Collectors.toList());
              return childrenList;
          }
      

      MenuMapper.java

          List<Menu> selectAllRouterMenu();
      
          List<Menu> selectRouterMenuTreeByUserId(Long userId);
      

      MenuMapper.xml

       <select id="selectAllRouterMenu" resultType="com.sangeng.domain.entity.Menu">
              SELECT
                DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
              FROM
                  `sys_menu` m
              WHERE
                  m.`menu_type` IN ('C','M') AND
                  m.`status` = 0 AND
                  m.`del_flag` = 0
              ORDER BY
                  m.parent_id,m.order_num
          </select>
          <select id="selectRouterMenuTreeByUserId" resultType="com.sangeng.domain.entity.Menu">
              SELECT
                DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
              FROM
                  `sys_user_role` ur
                  LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                  LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
              WHERE
                  ur.`user_id` = #{userId} AND
                  m.`menu_type` IN ('C','M') AND
                  m.`status` = 0 AND
                  m.`del_flag` = 0
              ORDER BY
                  m.parent_id,m.order_num
          </select>
      

      查詢的列:

      SELECT DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time

      注意需要按照parent_id和order_num排序

      5.3 退出登錄接口

      5.3.1 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      POST /user/logout 需要token請求頭

      響應(yīng)格式:

      {
          "code": 200,
          "msg": "操作成功"
      }
      

      5.3.2 代碼實(shí)現(xiàn)

      要實(shí)現(xiàn)的操作:

      ? 刪除redis中的用戶信息

      LoginController

          @PostMapping("/user/logout")
          public ResponseResult logout(){
              return loginServcie.logout();
          }
      

      LoginService

      ResponseResult logout();
      

      SystemLoginServiceImpl

          @Override
          public ResponseResult logout() {
              //獲取當(dāng)前登錄的用戶id
              Long userId = SecurityUtils.getUserId();
              //刪除redis中對應(yīng)的值
              redisCache.deleteObject("login:"+userId);
              return ResponseResult.okResult();
          }
      

      SecurityConfig

      要關(guān)閉默認(rèn)的退出登錄功能。并且要配置我們的退出登錄接口需要認(rèn)證才能訪問

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      //關(guān)閉csrf
                      .csrf().disable()
                      //不通過Session獲取SecurityContext
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                      .authorizeRequests()
                      // 對于登錄接口 允許匿名訪問
                      .antMatchers("/user/login").anonymous()
      //                //注銷接口需要認(rèn)證才能訪問
      //                .antMatchers("/logout").authenticated()
      //                .antMatchers("/user/userInfo").authenticated()
      //                .antMatchers("/upload").authenticated()
                      // 除上面外的所有請求全部不需要認(rèn)證即可訪問
                      .anyRequest().authenticated();
      
              //配置異常處理器
              http.exceptionHandling()
                      .authenticationEntryPoint(authenticationEntryPoint)
                      .accessDeniedHandler(accessDeniedHandler);
              //關(guān)閉默認(rèn)的注銷功能
              http.logout().disable();
              //把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
              http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              //允許跨域
              http.cors();
          }
      

      5.4 查詢標(biāo)簽列表

      5.4.0 需求

      ? 為了方便后期對文章進(jìn)行管理,需要提供標(biāo)簽的功能,一個(gè)文章可以有多個(gè)標(biāo)簽。

      ? 在后臺(tái)需要分頁查詢標(biāo)簽功能,要求能根據(jù)標(biāo)簽名進(jìn)行分頁查詢。 后期可能會(huì)增加備注查詢等需求

      ? 注意:不能把刪除了的標(biāo)簽查詢出來。

      5.4.1 標(biāo)簽表分析

      ? 通過需求去分析需要有哪些字段。

      5.4.2 接口設(shè)計(jì)

      請求方式 請求路徑
      Get content/tag/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      name:標(biāo)簽名

      remark:備注

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"id":4,
      				"name":"Java",
      				"remark":"sdad"
      			}
      		],
      		"total":1
      	},
      	"msg":"操作成功"
      }
      

      5.4.3 代碼實(shí)現(xiàn)

      Controller

      @RestController
      @RequestMapping("/content/tag")
      public class TagController {
          @Autowired
          private TagService tagService;
      
          @GetMapping("/list")
          public ResponseResult<PageVo> list(Integer pageNum, Integer pageSize, TagListDto tagListDto){
              return tagService.pageTagList(pageNum,pageSize,tagListDto);
          }
      }
      
      
      

      Service

      public interface TagService extends IService<Tag> {
      
          ResponseResult<PageVo> pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);
      }
      
      
      @Service("tagService")
      public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagService {
      
          @Override
          public ResponseResult<PageVo> pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {
              //分頁查詢
              LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
              queryWrapper.eq(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());
              queryWrapper.eq(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());
      
              Page<Tag> page = new Page<>();
              page.setCurrent(pageNum);
              page.setSize(pageSize);
              page(page, queryWrapper);
              //封裝數(shù)據(jù)返回
              PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
              return ResponseResult.okResult(pageVo);
          }
      }
      

      5.5 新增標(biāo)簽

      5.5.0 需求

      ? 點(diǎn)擊標(biāo)簽管理的新增按鈕可以實(shí)現(xiàn)新增標(biāo)簽的功能。

      5.5.1 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      POST /content/tag 需要token請求頭

      請求體格式:

      {"name":"c#","remark":"c++++"}
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.5.2 測試

      測試時(shí)注意,添加到數(shù)據(jù)庫中的記錄有沒有 創(chuàng)建時(shí)間,更新時(shí)間,創(chuàng)建人,更新人字段。

      5.6 刪除標(biāo)簽

      5.6.1 接口設(shè)計(jì)

      請求方式 請求地址 請求頭
      DELETE /content/tag/ 需要token請求頭

      請求參數(shù)在path中

      例如:content/tag/6 代表刪除id為6的標(biāo)簽數(shù)據(jù)

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.6.2 測試

      注意測試刪除后在列表中是否查看不到該條數(shù)據(jù)

      數(shù)據(jù)庫中該條數(shù)據(jù)還是存在的,只是修改了邏輯刪除字段的值

      5.7 修改標(biāo)簽

      5.7.1 接口設(shè)計(jì)

      5.7.1.1 獲取標(biāo)簽信息
      請求方式 請求地址 請求頭
      GET /content/tag/ 需要token請求頭

      請求參數(shù)在path中

      例如:content/tag/6 代表獲取id為6的標(biāo)簽數(shù)據(jù)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
              "id":4,
              "name":"Java",
              "remark":"sdad"
      	},
      	"msg":"操作成功"
      }
      
      5.7.1.2 修改標(biāo)簽接口
      請求方式 請求地址 請求頭
      PUT /content/tag 需要token請求頭

      請求體格式:

      {"id":7,"name":"c#","remark":"c++++"}
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.8 寫博文

      5.8.1 需求

      ? 需要提供寫博文的功能,寫博文時(shí)需要關(guān)聯(lián)分類和標(biāo)簽。

      ? 可以上傳縮略圖,也可以在正文中添加圖片。

      ? 文章可以直接發(fā)布,也可以保存到草稿箱。

      5.8.2 表分析

      ? 標(biāo)簽和文章需要關(guān)聯(lián)所以需要一張關(guān)聯(lián)表。

      ? SQL腳本:SGBlog\資源\SQL\sg_article_tag.sql

      5.8.2 接口設(shè)計(jì)

      ? 思考下需要哪些接口才能實(shí)現(xiàn)這個(gè)功能?

      5.8.2.1 查詢所有分類接口
      請求方式 請求地址 請求頭
      GET /content/category/listAllCategory 需要token請求頭

      請求參數(shù):

      ? 無

      響應(yīng)格式:

      {
      	"code":200,
      	"data":[
      		{
      			"description":"wsd",
      			"id":1,
      			"name":"java"
      		},
      		{
      			"description":"wsd",
      			"id":2,
      			"name":"PHP"
      		}
      	],
      	"msg":"操作成功"
      }
      
      5.8.2.2 查詢所有標(biāo)簽接口
      請求方式 請求地址 請求頭
      GET /content/tag/listAllTag 需要token請求頭

      請求參數(shù):

      ? 無

      響應(yīng)格式:

      {
      	"code":200,
      	"data":[
      		{
      			"id":1,
      			"name":"Mybatis"
      		},
      		{
      			"id":4,
      			"name":"Java"
      		}
      	],
      	"msg":"操作成功"
      }
      
      5.8.2.3 上傳圖片
      請求方式 請求地址 請求頭
      POST /upload 需要token請求頭

      參數(shù):

      ? img,值為要上傳的文件

      請求頭:

      ? Content-Type :multipart/form-data;

      響應(yīng)格式:

      {
          "code": 200,
          "data": "文件訪問鏈接",
          "msg": "操作成功"
      }
      
      5.8.2.4 新增博文
      請求方式 請求地址 請求頭
      POST /content/article 需要token請求頭

      請求體格式:

      {
          "title":"測試新增博文",
          "thumbnail":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/21/4ceebc07e7484beba732f12b0d2c43a9.png",
          "isTop":"0",
          "isComment":"0",
          "content":"# 一級標(biāo)題\n## 二級標(biāo)題\n![Snipaste_20220228_224837.png](https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/21/c3af554d4a0f4935b4073533a4c26ee8.png)\n正文",
          "tags":[
              1,
              4
          ],
          "categoryId":1,
          "summary":"哈哈",
          "status":"1"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.8.3 代碼實(shí)現(xiàn)

      5.8.3.1 查詢所有分類接口

      CategoryController

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @RestController
      @RequestMapping("/content/category")
      public class CategoryController {
          @Autowired
          private CategoryService categoryService;
          
          @GetMapping("/listAllCategory")
          public ResponseResult listAllCategory(){
              List<CategoryVo> list = categoryService.listAllCategory();
              return ResponseResult.okResult(list);
          }
      
          
      }
      
      

      CategoryVo修改,增加description屬性

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class CategoryVo {
      
          private Long id;
          private String name;
          //描述
          private String description;
      }
      

      CategoryService增加listAllCategory方法

      public interface CategoryService extends IService<Category> {
      
      
          ResponseResult getCategoryList();
      
          List<CategoryVo> listAllCategory();
      }
      
      

      SystemConstants中增加常量

          /** 正常狀態(tài) */
          public static final String NORMAL = "0";
      

      CategoryServiceImpl增加方法

          @Override
          public List<CategoryVo> listAllCategory() {
              LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<>();
              wrapper.eq(Category::getStatus, SystemConstants.NORMAL);
              List<Category> list = list(wrapper);
              List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(list, CategoryVo.class);
              return categoryVos;
          }
      
      5.8.3.2 查詢所有標(biāo)簽接口

      TagVo

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class TagVo {
          private Long id;
      
          //標(biāo)簽名
          private String name;
      
      
      
      }
      
      

      TagController

          @GetMapping("/listAllTag")
          public ResponseResult listAllTag(){
              List<TagVo> list = tagService.listAllTag();
              return ResponseResult.okResult(list);
          }
      

      TagService 增加listAllTag方法

      List<TagVo> listAllTag();
      
      

      TagServiceImpl

          @Override
          public List<TagVo> listAllTag() {
              LambdaQueryWrapper<Tag> wrapper = new LambdaQueryWrapper<>();
              wrapper.select(Tag::getId,Tag::getName);
              List<Tag> list = list(wrapper);
              List<TagVo> tagVos = BeanCopyUtils.copyBeanList(list, TagVo.class);
              return tagVos;
          }
      
      5.8.3.3 上傳圖片接口

      在sangeng-admin中增加UploadController

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @RestController
      public class UploadController {
      
          @Autowired
          private UploadService uploadService;
      
          @PostMapping("/upload")
          public ResponseResult uploadImg(@RequestParam("img") MultipartFile multipartFile) {
              try {
                  return uploadService.uploadImg(multipartFile);
              } catch (IOException e) {
                  e.printStackTrace();
                  throw new RuntimeException("文件上傳上傳失敗");
              }
          }
      }
      
      5.8.3.4 新增博文接口

      ArticleController

      /**
       * @Author 三更  B站: https://space.bilibili.com/663528522
       */
      @RestController
      @RequestMapping("/content/article")
      public class ArticleController {
      
          @Autowired
          private ArticleService articleService;
      
          @PostMapping
          public ResponseResult add(@RequestBody AddArticleDto article){
              return articleService.add(article);
          }
      
      
      }
      
      

      AddArticleDto

      注意增加tags屬性用于接收文章關(guān)聯(lián)標(biāo)簽的id

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class AddArticleDto {
      
          private Long id;
          //標(biāo)題
          private String title;
          //文章內(nèi)容
          private String content;
          //文章摘要
          private String summary;
          //所屬分類id
          private Long categoryId;
      
          //縮略圖
          private String thumbnail;
          //是否置頂(0否,1是)
          private String isTop;
          //狀態(tài)(0已發(fā)布,1草稿)
          private String status;
          //訪問量
          private Long viewCount;
          //是否允許評論 1是,0否
          private String isComment;
          private List<Long> tags;
      
      }
      
      

      Article 修改這樣創(chuàng)建時(shí)間創(chuàng)建人修改時(shí)間修改人可以自動(dòng)填充

          @TableField(fill = FieldFill.INSERT)
          private Long createBy;
          @TableField(fill = FieldFill.INSERT)
          private Date createTime;
          @TableField(fill = FieldFill.INSERT_UPDATE)
          private Long updateBy;
          @TableField(fill = FieldFill.INSERT_UPDATE)
          private Date updateTime;
      

      ArticleService增加方法

      ResponseResult add(AddArticleDto article);
      

      創(chuàng)建ArticleTag表相關(guān)的實(shí)體類,mapper,service,serviceimpl等

      @TableName(value="sg_article_tag")
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class ArticleTag implements Serializable {
          private static final long serialVersionUID = 625337492348897098L;
          
          /**
          * 文章id
          */
          private Long articleId;
          /**
          * 標(biāo)簽id
          */
          private Long tagId;
      
      
      
      }
      

      ArticleServiceImpl增加如下代碼

          @Autowired
          private ArticleTagService articleTagService;
      
          @Override
          @Transactional
          public ResponseResult add(AddArticleDto articleDto) {
              //添加 博客
              Article article = BeanCopyUtils.copyBean(articleDto, Article.class);
              save(article);
      
      
              List<ArticleTag> articleTags = articleDto.getTags().stream()
                      .map(tagId -> new ArticleTag(article.getId(), tagId))
                      .collect(Collectors.toList());
      
              //添加 博客和標(biāo)簽的關(guān)聯(lián)
              articleTagService.saveBatch(articleTags);
              return ResponseResult.okResult();
          }
      

      5.9 導(dǎo)出所有分類到Excel

      5.9.1 需求

      ? 在分類管理中點(diǎn)擊導(dǎo)出按鈕可以把所有的分類導(dǎo)出到Excel文件中。

      ?

      5.9.2 技術(shù)方案

      ? 使用EasyExcel實(shí)現(xiàn)Excel的導(dǎo)出操作。

      ? https://github.com/alibaba/easyexcel

      ? https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#示例代碼-1

      5.9.3 接口設(shè)計(jì)

      ?

      請求方式 請求地址 請求頭
      GET /content/category/export 需要token請求頭

      請求參數(shù):

      ? 無

      響應(yīng)格式:

      成功的話可以直接導(dǎo)出一個(gè)Excel文件

      失敗的話響應(yīng)格式如下:

      {
      	"code":500,
      	"msg":"出現(xiàn)錯(cuò)誤"
      }
      

      5.9.4 代碼實(shí)現(xiàn)

      工具類方法修改

      WebUtils

          public static void setDownLoadHeader(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
              response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
              response.setCharacterEncoding("utf-8");
              String fname= URLEncoder.encode(filename,"UTF-8").replaceAll("\\+", "%20");
              response.setHeader("Content-disposition","attachment; filename="+fname);
          }
      

      CategoryController

          @GetMapping("/export")
          public void export(HttpServletResponse response){
              try {
                  //設(shè)置下載文件的請求頭
                  WebUtils.setDownLoadHeader("分類.xlsx",response);
                  //獲取需要導(dǎo)出的數(shù)據(jù)
                  List<Category> categoryVos = categoryService.list();
      
                  List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(categoryVos, ExcelCategoryVo.class);
                  //把數(shù)據(jù)寫入到Excel中
                  EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分類導(dǎo)出")
                          .doWrite(excelCategoryVos);
      
              } catch (Exception e) {
                  //如果出現(xiàn)異常也要響應(yīng)json
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
                  WebUtils.renderString(response, JSON.toJSONString(result));
              }
          }
      

      ExcelCategoryVo

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class ExcelCategoryVo {
          @ExcelProperty("分類名")
          private String name;
          //描述
          @ExcelProperty("描述")
          private String description;
      
          //狀態(tài)0:正常,1禁用
          @ExcelProperty("狀態(tài)0:正常,1禁用")
          private String status;
      }
      
      

      5.10 權(quán)限控制

      5.10.1 需求

      ? 需要對導(dǎo)出分類的接口做權(quán)限控制。

      sg eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJkZGJkNjM5MWJiZTA0NmMzOTc4NDg1ZTcxNWQ3YjQ0MSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjI0NDE4NywiZXhwIjoxNjYyMzMwNTg3fQ.z4JGwFN3lWyVbOCbhikCe-O4D6SvCQFEE5eQY3jDJkw

      sangeng

      eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0Y2I1ZjhmMTc0Mjk0NzM0YjI4Y2M1NTQzYjQ2Yjc1YyIsInN1YiI6IjYiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjI0NDQzMywiZXhwIjoxNjYyMzMwODMzfQ.yEkbyGYXBp5ndnyq-3acdgpvqx2mnI8B9fK9f3Y6Jco

      5.10.2 代碼實(shí)現(xiàn)

      SecurityConfig

      @EnableGlobalMethodSecurity(prePostEnabled = true)
      

      UserDetailsServiceImpl

      @Service
      public class UserDetailsServiceImpl implements UserDetailsService {
      
          @Autowired
          private UserMapper userMapper;
      
          @Autowired
          private MenuMapper menuMapper;
      
          @Override
          public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              //根據(jù)用戶名查詢用戶信息
              LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
              queryWrapper.eq(User::getUserName,username);
              User user = userMapper.selectOne(queryWrapper);
              //判斷是否查到用戶  如果沒查到拋出異常
              if(Objects.isNull(user)){
                  throw new RuntimeException("用戶不存在");
              }
              //返回用戶信息
              if(user.getType().equals(SystemConstants.ADMAIN)){
                  List<String> list = menuMapper.selectPermsByUserId(user.getId());
                  return new LoginUser(user,list);
              }
              return new LoginUser(user,null);
          }
      }
      
      

      LoginUser

      增加屬性

      private List<String> permissions;
      

      PermissionService

      hasPermisson

      @Service("ps")
      public class PermissionService {
      
          /**
           * 判斷當(dāng)前用戶是否具有permission
           * @param permission 要判斷的權(quán)限
           * @return
           */
          public boolean hasPermission(String permission){
              //如果是超級管理員  直接返回true
              if(SecurityUtils.isAdmin()){
                  return true;
              }
              //否則  獲取當(dāng)前登錄用戶所具有的權(quán)限列表 如何判斷是否存在permission
              List<String> permissions = SecurityUtils.getLoginUser().getPermissions();
              return permissions.contains(permission);
          }
      }
      

      CategoryController

          @PreAuthorize("@ps.hasPermission('content:category:export')")
          @GetMapping("/export")
          public void export(HttpServletResponse response){
              try {
                  //設(shè)置下載文件的請求頭
                  WebUtils.setDownLoadHeader("分類.xlsx",response);
                  //獲取需要導(dǎo)出的數(shù)據(jù)
                  List<Category> categoryVos = categoryService.list();
      
                  List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(categoryVos, ExcelCategoryVo.class);
                  //把數(shù)據(jù)寫入到Excel中
                  EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分類導(dǎo)出")
                          .doWrite(excelCategoryVos);
      
              } catch (Exception e) {
                  //如果出現(xiàn)異常也要響應(yīng)json
                  ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
                  WebUtils.renderString(response, JSON.toJSONString(result));
              }
          }
      

      5.11 文章列表

      5.10.1 需求

      ? 為了對文章進(jìn)行管理,需要提供文章列表,

      ? 在后臺(tái)需要分頁查詢文章功能,要求能根據(jù)標(biāo)題和摘要模糊查詢

      ? 注意:不能把刪除了的文章查詢出來

      5.10.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      Get /content/article/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      title:文章標(biāo)題

      summary:文章摘要

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"categoryId":"1",
      				"content":"嘻嘻嘻嘻嘻嘻",
      				"createTime":"2022-01-24 07:20:11",
      				"id":"1",
      				"isComment":"0",
      				"isTop":"1",
      				"status":"0",
      				"summary":"SpringSecurity框架教程-Spring Security+JWT實(shí)現(xiàn)項(xiàng)目級前端分離認(rèn)證授權(quán)",
      				"thumbnail":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/01/31/948597e164614902ab1662ba8452e106.png",
      				"title":"SpringSecurity從入門到精通",
      				"viewCount":"161"
      			}
      		],
      		"total":"1"
      	},
      	"msg":"操作成功"
      }
      

      ?

      5.12 修改文章

      5.12.1 需求

      ? 點(diǎn)擊文章列表中的修改按鈕可以跳轉(zhuǎn)到寫博文頁面。回顯示該文章的具體信息。

      ? 用戶可以在該頁面修改文章信息。點(diǎn)擊更新按鈕后修改文章。

      5.12.2 分析

      ? 這個(gè)功能的實(shí)現(xiàn)首先需要能夠根據(jù)文章id查詢文章的詳細(xì)信息這樣才能實(shí)現(xiàn)文章的回顯。

      ? 如何需要提供更新文章的接口。

      5.12.3 接口設(shè)計(jì)

      5.12.3.1 查詢文章詳情接口
      請求方式 請求路徑 是否需求token頭
      Get content/article/

      Path格式請求參數(shù):

      id: 文章id

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"categoryId":"1",
      		"content":"xxxxxxx",
      		"createBy":"1",
      		"createTime":"2022-08-28 15:15:46",
      		"delFlag":0,
      		"id":"10",
      		"isComment":"0",
      		"isTop":"1",
      		"status":"0",
      		"summary":"啊實(shí)打?qū)?,
      		"tags":[
      			"1",
      			"4",
      			"5"
      		],
      		"thumbnail":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/28/7659aac2b74247fe8ebd9e054b916dbf.png",
      		"title":"委屈餓驅(qū)蚊器",
      		"updateBy":"1",
      		"updateTime":"2022-08-28 15:15:46",
      		"viewCount":"0"
      	},
      	"msg":"操作成功"
      }
      
      5.12.3.2 更新文章接口
      請求方式 請求路徑 是否需求token頭
      PUT content/article

      請求體參數(shù)格式:

      {
          "categoryId":"1",
          "content":"![Snipaste_20220228_224837.png](https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/28/f3938a0368c540ee909ba7f7079a829a.png)\n\n# 十大\n## 時(shí)代的",
          "createBy":"1",
          "createTime":"2022-08-28 15:15:46",
          "delFlag":0,
          "id":"10",
          "isComment":"0",
          "isTop":"1",
          "status":"0",
          "summary":"啊實(shí)打?qū)?",
          "tags":[
              "1",
              "4",
              "5"
          ],
          "thumbnail":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/28/7659aac2b74247fe8ebd9e054b916dbf.png",
          "title":"委屈餓驅(qū)蚊器",
          "updateBy":"1",
          "updateTime":"2022-08-28 15:15:46",
          "viewCount":"0"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.13 刪除文章

      5.13.1 需求

      ? 點(diǎn)擊文章后面的刪除按鈕可以刪除該文章

      ? 注意:是邏輯刪除不是物理刪除

      5.13.2 接口設(shè)計(jì)

      ?

      請求方式 請求路徑 是否需求token頭
      DELETE content/article/

      Path請求參數(shù):

      id:要?jiǎng)h除的文章id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.14 菜單列表

      5.14.1 需求

      ? 需要展示菜單列表,不需要分頁。

      ? 可以針對菜單名進(jìn)行模糊查詢

      ? 也可以針對菜單的狀態(tài)進(jìn)行查詢。

      ? 菜單要按照父菜單id和orderNum進(jìn)行排序

      5.14.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      GET system/menu/list

      Query請求參數(shù):

      status : 狀態(tài)

      menuName: 菜單名

      響應(yīng)格式:

      {
      	"code":200,
      	"data":[
      		{
      			"component":"content/article/write/index",
      			"icon":"build",
      			"id":"2023",
      			"isFrame":1,
      			"menuName":"寫博文",
      			"menuType":"C",
      			"orderNum":0,
      			"parentId":"0",
      			"path":"write",
      			"perms":"content:article:writer",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"system",
      			"id":"1",
      			"isFrame":1,
      			"menuName":"系統(tǒng)管理",
      			"menuType":"M",
      			"orderNum":1,
      			"parentId":"0",
      			"path":"system",
      			"perms":"",
      			"remark":"系統(tǒng)管理目錄",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"table",
      			"id":"2017",
      			"isFrame":1,
      			"menuName":"內(nèi)容管理",
      			"menuType":"M",
      			"orderNum":4,
      			"parentId":"0",
      			"path":"content",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"system/user/index",
      			"icon":"user",
      			"id":"100",
      			"isFrame":1,
      			"menuName":"用戶管理",
      			"menuType":"C",
      			"orderNum":1,
      			"parentId":"1",
      			"path":"user",
      			"perms":"system:user:list",
      			"remark":"用戶管理菜單",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"system/role/index",
      			"icon":"peoples",
      			"id":"101",
      			"isFrame":1,
      			"menuName":"角色管理",
      			"menuType":"C",
      			"orderNum":2,
      			"parentId":"1",
      			"path":"role",
      			"perms":"system:role:list",
      			"remark":"角色管理菜單",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"system/menu/index",
      			"icon":"tree-table",
      			"id":"102",
      			"isFrame":1,
      			"menuName":"菜單管理",
      			"menuType":"C",
      			"orderNum":3,
      			"parentId":"1",
      			"path":"menu",
      			"perms":"system:menu:list",
      			"remark":"菜單管理菜單",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1001",
      			"isFrame":1,
      			"menuName":"用戶查詢",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:query",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1002",
      			"isFrame":1,
      			"menuName":"用戶新增",
      			"menuType":"F",
      			"orderNum":2,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:add",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1003",
      			"isFrame":1,
      			"menuName":"用戶修改",
      			"menuType":"F",
      			"orderNum":3,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:edit",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1004",
      			"isFrame":1,
      			"menuName":"用戶刪除",
      			"menuType":"F",
      			"orderNum":4,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:remove",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1005",
      			"isFrame":1,
      			"menuName":"用戶導(dǎo)出",
      			"menuType":"F",
      			"orderNum":5,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:export",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1006",
      			"isFrame":1,
      			"menuName":"用戶導(dǎo)入",
      			"menuType":"F",
      			"orderNum":6,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:import",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1007",
      			"isFrame":1,
      			"menuName":"重置密碼",
      			"menuType":"F",
      			"orderNum":7,
      			"parentId":"100",
      			"path":"",
      			"perms":"system:user:resetPwd",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1008",
      			"isFrame":1,
      			"menuName":"角色查詢",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"101",
      			"path":"",
      			"perms":"system:role:query",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1009",
      			"isFrame":1,
      			"menuName":"角色新增",
      			"menuType":"F",
      			"orderNum":2,
      			"parentId":"101",
      			"path":"",
      			"perms":"system:role:add",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1010",
      			"isFrame":1,
      			"menuName":"角色修改",
      			"menuType":"F",
      			"orderNum":3,
      			"parentId":"101",
      			"path":"",
      			"perms":"system:role:edit",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1011",
      			"isFrame":1,
      			"menuName":"角色刪除",
      			"menuType":"F",
      			"orderNum":4,
      			"parentId":"101",
      			"path":"",
      			"perms":"system:role:remove",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1012",
      			"isFrame":1,
      			"menuName":"角色導(dǎo)出",
      			"menuType":"F",
      			"orderNum":5,
      			"parentId":"101",
      			"path":"",
      			"perms":"system:role:export",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1013",
      			"isFrame":1,
      			"menuName":"菜單查詢",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"102",
      			"path":"",
      			"perms":"system:menu:query",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1014",
      			"isFrame":1,
      			"menuName":"菜單新增",
      			"menuType":"F",
      			"orderNum":2,
      			"parentId":"102",
      			"path":"",
      			"perms":"system:menu:add",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1015",
      			"isFrame":1,
      			"menuName":"菜單修改",
      			"menuType":"F",
      			"orderNum":3,
      			"parentId":"102",
      			"path":"",
      			"perms":"system:menu:edit",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"",
      			"icon":"#",
      			"id":"1016",
      			"isFrame":1,
      			"menuName":"菜單刪除",
      			"menuType":"F",
      			"orderNum":4,
      			"parentId":"102",
      			"path":"",
      			"perms":"system:menu:remove",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"content/article/index",
      			"icon":"build",
      			"id":"2019",
      			"isFrame":1,
      			"menuName":"文章管理",
      			"menuType":"C",
      			"orderNum":0,
      			"parentId":"2017",
      			"path":"article",
      			"perms":"content:article:list",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"content/category/index",
      			"icon":"example",
      			"id":"2018",
      			"isFrame":1,
      			"menuName":"分類管理",
      			"menuType":"C",
      			"orderNum":1,
      			"parentId":"2017",
      			"path":"category",
      			"perms":"content:category:list",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"content/link/index",
      			"icon":"404",
      			"id":"2022",
      			"isFrame":1,
      			"menuName":"友鏈管理",
      			"menuType":"C",
      			"orderNum":4,
      			"parentId":"2017",
      			"path":"link",
      			"perms":"content:link:list",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"component":"content/tag/index",
      			"icon":"button",
      			"id":"2021",
      			"isFrame":1,
      			"menuName":"標(biāo)簽管理",
      			"menuType":"C",
      			"orderNum":6,
      			"parentId":"2017",
      			"path":"tag",
      			"perms":"content:tag:index",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"#",
      			"id":"2028",
      			"isFrame":1,
      			"menuName":"導(dǎo)出分類",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"2018",
      			"path":"",
      			"perms":"content:category:export",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"#",
      			"id":"2024",
      			"isFrame":1,
      			"menuName":"友鏈新增",
      			"menuType":"F",
      			"orderNum":0,
      			"parentId":"2022",
      			"path":"",
      			"perms":"content:link:add",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"#",
      			"id":"2025",
      			"isFrame":1,
      			"menuName":"友鏈修改",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"2022",
      			"path":"",
      			"perms":"content:link:edit",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"#",
      			"id":"2026",
      			"isFrame":1,
      			"menuName":"友鏈刪除",
      			"menuType":"F",
      			"orderNum":1,
      			"parentId":"2022",
      			"path":"",
      			"perms":"content:link:remove",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		},
      		{
      			"icon":"#",
      			"id":"2027",
      			"isFrame":1,
      			"menuName":"友鏈查詢",
      			"menuType":"F",
      			"orderNum":2,
      			"parentId":"2022",
      			"path":"",
      			"perms":"content:link:query",
      			"remark":"",
      			"status":"0",
      			"visible":"0"
      		}
      	],
      	"msg":"操作成功"
      }
      

      5.15 新增菜單

      5.15.1 需求

      ? 可以新增菜單

      5.15.2 接口設(shè)計(jì)

      ?

      請求方式 請求路徑 是否需求token頭
      POST content/article

      請求體參數(shù):

      ? Menu類對應(yīng)的json格式

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.16 修改菜單

      5.16.1 需求

      ? 能夠修改菜單,但是修改的時(shí)候不能把父菜單設(shè)置為當(dāng)前菜單,如果設(shè)置了需要給出相應(yīng)的提示。并且修改失敗。

      5.16.2 接口設(shè)計(jì)

      5.16.2.1 根據(jù)id查詢菜單數(shù)據(jù)
      請求方式 請求路徑 是否需求token頭
      Get system/menu/

      Path格式請求參數(shù):

      id: 菜單id

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"icon":"table",
      		"id":"2017",
      		"menuName":"內(nèi)容管理",
      		"menuType":"M",
      		"orderNum":"4",
      		"parentId":"0",
      		"path":"content",
      		"remark":"",
      		"status":"0",
      		"visible":"0"
      	},
      	"msg":"操作成功"
      }
      
      5.16.2.2 更新菜單
      請求方式 請求路徑 是否需求token頭
      PUT system/menu

      請求體參數(shù):

      ? Menu類對應(yīng)的json格式

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      如果把父菜單設(shè)置為當(dāng)前菜單:

      {
      	"code":500,
      	"msg":"修改菜單'寫博文'失敗,上級菜單不能選擇自己"
      }
      

      5.17 刪除菜單

      5.17.1 需求

      ? 能夠刪除菜單,但是如果要?jiǎng)h除的菜單有子菜單則提示:存在子菜單不允許刪除 并且刪除失敗。

      5.17.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      DELETE content/article/

      Path參數(shù):

      menuId:要?jiǎng)h除菜單的id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      如果要?jiǎng)h除的菜單有子菜單則

      {
      	"code":500,
      	"msg":"存在子菜單不允許刪除"
      }
      

      5.18 角色列表

      5.18.1 需求

      ? 需要有角色列表分頁查詢的功能。

      ? 要求能夠針對角色名稱進(jìn)行模糊查詢。

      ? 要求能夠針對狀態(tài)進(jìn)行查詢。

      ? 要求按照role_sort進(jìn)行升序排列。

      5.18.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      GET system/role/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      roleName:角色名稱

      status:狀態(tài)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"id":"12",
      				"roleKey":"link",
      				"roleName":"友鏈審核員",
      				"roleSort":"1",
      				"status":"0"
      			}
      		],
      		"total":"1"
      	},
      	"msg":"操作成功"
      }
      

      5.19 改變角色狀態(tài)

      5.19.1 需求

      ? 要求能夠修改角色的停啟用狀態(tài)

      5.19.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      PUT system/role/changeStatus

      請求體:

      {"roleId":"11","status":"1"}
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.20 新增角色!!

      5.20.1 需求

      ? 需要提供新增角色的功能。新增角色時(shí)能夠直接設(shè)置角色所關(guān)聯(lián)的菜單權(quán)限。

      5.20.2 接口設(shè)計(jì)

      5.20.2.1 獲取菜單樹接口
      請求方式 請求路徑 是否需求token頭
      GET /system/menu/treeselect

      無需請求參數(shù)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":[
      		{
      			"children":[],
      			"id":"2023",
      			"label":"寫博文",
      			"parentId":"0"
      		},
      		{
      			"children":[
      				{
      					"children":[
      						{
      							"children":[],
      							"id":"1001",
      							"label":"用戶查詢",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1002",
      							"label":"用戶新增",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1003",
      							"label":"用戶修改",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1004",
      							"label":"用戶刪除",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1005",
      							"label":"用戶導(dǎo)出",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1006",
      							"label":"用戶導(dǎo)入",
      							"parentId":"100"
      						},
      						{
      							"children":[],
      							"id":"1007",
      							"label":"重置密碼",
      							"parentId":"100"
      						}
      					],
      					"id":"100",
      					"label":"用戶管理",
      					"parentId":"1"
      				},
      				{
      					"children":[
      						{
      							"children":[],
      							"id":"1008",
      							"label":"角色查詢",
      							"parentId":"101"
      						},
      						{
      							"children":[],
      							"id":"1009",
      							"label":"角色新增",
      							"parentId":"101"
      						},
      						{
      							"children":[],
      							"id":"1010",
      							"label":"角色修改",
      							"parentId":"101"
      						},
      						{
      							"children":[],
      							"id":"1011",
      							"label":"角色刪除",
      							"parentId":"101"
      						},
      						{
      							"children":[],
      							"id":"1012",
      							"label":"角色導(dǎo)出",
      							"parentId":"101"
      						}
      					],
      					"id":"101",
      					"label":"角色管理",
      					"parentId":"1"
      				},
      				{
      					"children":[
      						{
      							"children":[],
      							"id":"1013",
      							"label":"菜單查詢",
      							"parentId":"102"
      						},
      						{
      							"children":[],
      							"id":"1014",
      							"label":"菜單新增",
      							"parentId":"102"
      						},
      						{
      							"children":[],
      							"id":"1015",
      							"label":"菜單修改",
      							"parentId":"102"
      						},
      						{
      							"children":[],
      							"id":"1016",
      							"label":"菜單刪除",
      							"parentId":"102"
      						}
      					],
      					"id":"102",
      					"label":"菜單管理",
      					"parentId":"1"
      				}
      			],
      			"id":"1",
      			"label":"系統(tǒng)管理",
      			"parentId":"0"
      		},
      		{
      			"children":[
      				{
      					"children":[],
      					"id":"2019",
      					"label":"文章管理",
      					"parentId":"2017"
      				},
      				{
      					"children":[
      						{
      							"children":[],
      							"id":"2028",
      							"label":"導(dǎo)出分類",
      							"parentId":"2018"
      						}
      					],
      					"id":"2018",
      					"label":"分類管理",
      					"parentId":"2017"
      				},
      				{
      					"children":[
      						{
      							"children":[],
      							"id":"2024",
      							"label":"友鏈新增",
      							"parentId":"2022"
      						},
      						{
      							"children":[],
      							"id":"2025",
      							"label":"友鏈修改",
      							"parentId":"2022"
      						},
      						{
      							"children":[],
      							"id":"2026",
      							"label":"友鏈刪除",
      							"parentId":"2022"
      						},
      						{
      							"children":[],
      							"id":"2027",
      							"label":"友鏈查詢",
      							"parentId":"2022"
      						}
      					],
      					"id":"2022",
      					"label":"友鏈管理",
      					"parentId":"2017"
      				},
      				{
      					"children":[],
      					"id":"2021",
      					"label":"標(biāo)簽管理",
      					"parentId":"2017"
      				}
      			],
      			"id":"2017",
      			"label":"內(nèi)容管理",
      			"parentId":"0"
      		}
      	],
      	"msg":"操作成功"
      }
      
      5.20.2.2 新增角色接口
      請求方式 請求路徑 是否需求token頭
      POST system/role

      請求體:

      {
          "roleName":"測試新增角色",
          "roleKey":"wds",
          "roleSort":0,
          "status":"0",
          "menuIds":[
              "1",
              "100"
          ],
          "remark":"我是角色備注"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.21 修改角色

      5.21.1 需求

      ? 需要提供修改角色的功能。修改角色時(shí)可以修改角色所關(guān)聯(lián)的菜單權(quán)限

      5.21.2 接口設(shè)計(jì)

      5.21.2.1 角色信息回顯接口
      請求方式 請求路徑 是否需求token頭
      Get system/role/

      Path格式請求參數(shù):

      id: 角色id

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"id":"11",
      		"remark":"嘎嘎嘎",
      		"roleKey":"aggag",
      		"roleName":"嘎嘎嘎",
      		"roleSort":"5",
      		"status":"0"
      	},
      	"msg":"操作成功"
      }
      
      5.21.2.2 加載對應(yīng)角色菜單列表樹接口
      請求方式 請求路徑 是否需求token頭
      Get /system/menu/roleMenuTreeselect/

      Path格式請求參數(shù):

      id: 角色id

      響應(yīng)格式:

      字段介紹

      ? menus:菜單樹。

      ? checkedKeys:角色所關(guān)聯(lián)的菜單權(quán)限id列表。

      {
      	"code":200,
      	"data":{
      		"menus":[
      			{
      				"children":[],
      				"id":"2023",
      				"label":"寫博文",
      				"parentId":"0"
      			},
      			{
      				"children":[
      					{
      						"children":[
      							{
      								"children":[],
      								"id":"1001",
      								"label":"用戶查詢",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1002",
      								"label":"用戶新增",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1003",
      								"label":"用戶修改",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1004",
      								"label":"用戶刪除",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1005",
      								"label":"用戶導(dǎo)出",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1006",
      								"label":"用戶導(dǎo)入",
      								"parentId":"100"
      							},
      							{
      								"children":[],
      								"id":"1007",
      								"label":"重置密碼",
      								"parentId":"100"
      							}
      						],
      						"id":"100",
      						"label":"用戶管理",
      						"parentId":"1"
      					},
      					{
      						"children":[
      							{
      								"children":[],
      								"id":"1008",
      								"label":"角色查詢",
      								"parentId":"101"
      							},
      							{
      								"children":[],
      								"id":"1009",
      								"label":"角色新增",
      								"parentId":"101"
      							},
      							{
      								"children":[],
      								"id":"1010",
      								"label":"角色修改",
      								"parentId":"101"
      							},
      							{
      								"children":[],
      								"id":"1011",
      								"label":"角色刪除",
      								"parentId":"101"
      							},
      							{
      								"children":[],
      								"id":"1012",
      								"label":"角色導(dǎo)出",
      								"parentId":"101"
      							}
      						],
      						"id":"101",
      						"label":"角色管理",
      						"parentId":"1"
      					},
      					{
      						"children":[
      							{
      								"children":[],
      								"id":"1013",
      								"label":"菜單查詢",
      								"parentId":"102"
      							},
      							{
      								"children":[],
      								"id":"1014",
      								"label":"菜單新增",
      								"parentId":"102"
      							},
      							{
      								"children":[],
      								"id":"1015",
      								"label":"菜單修改",
      								"parentId":"102"
      							},
      							{
      								"children":[],
      								"id":"1016",
      								"label":"菜單刪除",
      								"parentId":"102"
      							}
      						],
      						"id":"102",
      						"label":"菜單管理",
      						"parentId":"1"
      					}
      				],
      				"id":"1",
      				"label":"系統(tǒng)管理",
      				"parentId":"0"
      			},
      			{
      				"children":[
      					{
      						"children":[],
      						"id":"2019",
      						"label":"文章管理",
      						"parentId":"2017"
      					},
      					{
      						"children":[
      							{
      								"children":[],
      								"id":"2028",
      								"label":"導(dǎo)出分類",
      								"parentId":"2018"
      							}
      						],
      						"id":"2018",
      						"label":"分類管理",
      						"parentId":"2017"
      					},
      					{
      						"children":[
      							{
      								"children":[],
      								"id":"2024",
      								"label":"友鏈新增",
      								"parentId":"2022"
      							},
      							{
      								"children":[],
      								"id":"2025",
      								"label":"友鏈修改",
      								"parentId":"2022"
      							},
      							{
      								"children":[],
      								"id":"2026",
      								"label":"友鏈刪除",
      								"parentId":"2022"
      							},
      							{
      								"children":[],
      								"id":"2027",
      								"label":"友鏈查詢",
      								"parentId":"2022"
      							}
      						],
      						"id":"2022",
      						"label":"友鏈管理",
      						"parentId":"2017"
      					},
      					{
      						"children":[],
      						"id":"2021",
      						"label":"標(biāo)簽管理",
      						"parentId":"2017"
      					}
      				],
      				"id":"2017",
      				"label":"內(nèi)容管理",
      				"parentId":"0"
      			}
      		],
      		"checkedKeys":[
      			"1001"  
      		]
      	},
      	"msg":"操作成功"
      }
      
      5.21.2.3 更新角色信息接口
      請求方式 請求路徑 是否需求token頭
      PUT system/role

      請求體:

      {
          "id":"13",
          "remark":"我是角色備注",
          "roleKey":"wds",
          "roleName":"測試新增角色",
          "roleSort":0,
          "status":"0",
          "menuIds":[
              "1",
              "100",
              "1001"
          ]
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.22 刪除角色

      5.22.1 需求

      ? 刪除固定的某個(gè)角色(邏輯刪除)

      5.22.2 接口設(shè)計(jì)

      ?

      請求方式 請求路徑 是否需求token頭
      DELETE system/role/

      Path請求參數(shù):

      id:要?jiǎng)h除的角色id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.23 用戶列表

      5.23.1 需求

      ? 需要用戶分頁列表接口。

      ? 可以根據(jù)用戶名模糊搜索。

      ? 可以進(jìn)行手機(jī)號的搜索。

      ? 可以進(jìn)行狀態(tài)的查詢。

      5.23.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      GET system/user/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      userName:用戶名

      phonenumber:手機(jī)號

      status:狀態(tài)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"avatar":"http://r7yxkqloa.bkt.clouddn.com/2022/03/05/75fd15587811443a9a9a771f24da458d.png",
      				"createTime":"2022-01-05 17:01:56",
      				"email":"23412332@qq.com",
      				"id":"1",
      				"nickName":"sg3334",
      				"phonenumber":"18888888888",
      				"sex":"1",
      				"status":"0",
      				"updateBy":"1",
      				"updateTime":"2022-03-13 21:36:22",
      				"userName":"sg"
      			}
      		],
      		"total":"1"
      	},
      	"msg":"操作成功"
      }
      

      5.24 新增用戶!!!

      5.24.1 需求

      ? 需要新增用戶功能。新增用戶時(shí)可以直接關(guān)聯(lián)角色。

      ? 注意:新增用戶時(shí)注意密碼加密存儲(chǔ)。

      ? 用戶名不能為空,否則提示:必需填寫用戶名

      ? 用戶名必須之前未存在,否則提示:用戶名已存在

      ? 手機(jī)號必須之前未存在,否則提示:手機(jī)號已存在

      ? 郵箱必須之前未存在,否則提示:郵箱已存在

      5.24.2 接口設(shè)計(jì)

      5.24.2.1 查詢角色列表接口

      注意:查詢的是所有狀態(tài)正常的角色

      請求方式 請求路徑 是否需求token頭
      GET /system/role/listAllRole

      響應(yīng)格式:

      {
      	"code":200,
      	"data":[
      		{
      			"createBy":"0",
      			"createTime":"2021-11-12 18:46:19",
      			"delFlag":"0",
      			"id":"1",
      			"remark":"超級管理員",
      			"roleKey":"admin",
      			"roleName":"超級管理員",
      			"roleSort":"1",
      			"status":"0",
      			"updateBy":"0"
      		},
      		{
      			"createBy":"0",
      			"createTime":"2021-11-12 18:46:19",
      			"delFlag":"0",
      			"id":"2",
      			"remark":"普通角色",
      			"roleKey":"common",
      			"roleName":"普通角色",
      			"roleSort":"2",
      			"status":"0",
      			"updateBy":"0",
      			"updateTime":"2022-01-02 06:32:58"
      		},
      		{
      			"createTime":"2022-01-06 22:07:40",
      			"delFlag":"0",
      			"id":"11",
      			"remark":"嘎嘎嘎",
      			"roleKey":"aggag",
      			"roleName":"嘎嘎嘎",
      			"roleSort":"5",
      			"status":"0",
      			"updateBy":"1",
      			"updateTime":"2022-09-12 10:00:25"
      		},
      		{
      			"createTime":"2022-01-16 14:49:30",
      			"delFlag":"0",
      			"id":"12",
      			"roleKey":"link",
      			"roleName":"友鏈審核員",
      			"roleSort":"1",
      			"status":"0",
      			"updateTime":"2022-01-16 16:05:09"
      		}
      	],
      	"msg":"操作成功"
      }
      
      5.24.2.2 新增用戶
      請求方式 請求路徑 是否需求token頭
      POST system/user

      請求體:

      {
          "userName":"wqeree",
          "nickName":"測試新增用戶",
          "password":"1234343",
          "phonenumber":"18889778907",
          "email":"233@sq.com",
          "sex":"0",
          "status":"0",
          "roleIds":[
              "2"
          ]
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.25 刪除用戶

      5.25.1 需求

      刪除固定的某個(gè)用戶(邏輯刪除)

      5.25.2 接口設(shè)計(jì)

      不能刪除當(dāng)前操作的用戶

      請求方式 請求路徑 是否需求token頭
      DELETE /system/user/

      Path請求參數(shù):

      id:要?jiǎng)h除的用戶id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.26 修改用戶

      5.26.1 需求

      需要提供修改用戶的功能。修改用戶時(shí)可以修改用戶所關(guān)聯(lián)的角色。

      5.26.2 接口設(shè)計(jì)

      5.26.2.1 根據(jù)id查詢用戶信息回顯接口
      請求方式 請求路徑 是否需求token頭
      Get /system/user/

      Path格式請求參數(shù):

      id: 用戶id

      響應(yīng)格式:

      roleIds:用戶所關(guān)聯(lián)的角色id列表

      roles:所有角色的列表

      user:用戶信息

      {
      	"code":200,
      	"data":{
      		"roleIds":[
      			"11"
      		],
      		"roles":[
      			{
      				"createBy":"0",
      				"createTime":"2021-11-12 18:46:19",
      				"delFlag":"0",
      				"id":"1",
      				"remark":"超級管理員",
      				"roleKey":"admin",
      				"roleName":"超級管理員",
      				"roleSort":"1",
      				"status":"0",
      				"updateBy":"0"
      			},
      			{
      				"createBy":"0",
      				"createTime":"2021-11-12 18:46:19",
      				"delFlag":"0",
      				"id":"2",
      				"remark":"普通角色",
      				"roleKey":"common",
      				"roleName":"普通角色",
      				"roleSort":"2",
      				"status":"0",
      				"updateBy":"0",
      				"updateTime":"2022-01-02 06:32:58"
      			},
      			{
      				"createTime":"2022-01-06 22:07:40",
      				"delFlag":"0",
      				"id":"11",
      				"remark":"嘎嘎嘎",
      				"roleKey":"aggag",
      				"roleName":"嘎嘎嘎",
      				"roleSort":"5",
      				"status":"0",
      				"updateBy":"1",
      				"updateTime":"2022-09-11 20:34:49"
      			},
      			{
      				"createTime":"2022-01-16 14:49:30",
      				"delFlag":"0",
      				"id":"12",
      				"roleKey":"link",
      				"roleName":"友鏈審核員",
      				"roleSort":"1",
      				"status":"0",
      				"updateTime":"2022-01-16 16:05:09"
      			}
      		],
      		"user":{
      			"email":"weq@2132.com",
      			"id":"14787164048663",
      			"nickName":"sg777",
      			"sex":"0",
      			"status":"0",
      			"userName":"sg777"
      		}
      	},
      	"msg":"操作成功"
      }
      
      5.26.2.2 更新用戶信息接口
      請求方式 請求路徑 是否需求token頭
      PUT /system/user

      請求體:

      {
          "email":"weq@2132.com",
          "id":"14787164048663",
          "nickName":"sg777",
          "sex":"1",
          "status":"0",
          "userName":"sg777",
          "roleIds":[
              "11"
          ]
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.27 分頁查詢分類列表

      5.27.1 需求

      ? 需要分頁查詢分類列表。

      ? 能根據(jù)分類名稱進(jìn)行模糊查詢。

      ? 能根據(jù)狀態(tài)進(jìn)行查詢。

      5.27.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      GET content/category/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      name:分類名

      status: 狀態(tài)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"description":"wsd",
      				"id":"1",
      				"name":"java",
      				"status":"0"
      			},
      			{
      				"description":"wsd",
      				"id":"2",
      				"name":"PHP",
      				"status":"0"
      			}
      		],
      		"total":"2"
      	},
      	"msg":"操作成功"
      }
      

      5.28 新增分類

      5.28.1 需求

      ? 需要新增分類功能

      5.28.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      POST /content/category

      請求體:

      {
          "name":"威威",
          "description":"是的",
          "status":"0"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.29 修改分類

      5.29.1 需求

      ? 需要提供修改分類的功能

      5.29.2 接口設(shè)計(jì)

      5.29.2.1 根據(jù)id查詢分類
      請求方式 請求路徑 是否需求token頭
      Get content/category/

      Path格式請求參數(shù):

      id: 分類id

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"description":"qwew",
      		"id":"4",
      		"name":"ww",
      		"status":"0"
      	},
      	"msg":"操作成功"
      }
      
      5.29.2.2 更新分類
      請求方式 請求路徑 是否需求token頭
      PUT /content/category

      請求體:

      {
          "description":"是的",
          "id":"3",
          "name":"威威2",
          "status":"0"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.30 刪除分類

      5.30.1 需求

      ? 刪除某個(gè)分類(邏輯刪除)

      5.30.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      DELETE /content/category/

      Path請求參數(shù):

      id:要?jiǎng)h除的分類id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.31 分頁查詢友鏈列表

      5.31.1 需求

      ? 需要分頁查詢友鏈列表。

      ? 能根據(jù)友鏈名稱進(jìn)行模糊查詢。

      ? 能根據(jù)狀態(tài)進(jìn)行查詢。

      5.31.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      GET /content/link/list

      Query格式請求參數(shù):

      pageNum: 頁碼

      pageSize: 每頁條數(shù)

      name:友鏈名

      status:狀態(tài)

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"rows":[
      			{
      				"address":"https://www.baidu.com",
      				"description":"sda",
      				"id":"1",						       "logo":"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn1.itc.cn%2Fimg8%2Fwb%2Frecom%2F2016%2F05%2F10%2F146286696706220328.PNG&refer=http%3A%2F%2Fn1.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646205529&t=f942665181eb9b0685db7a6f59d59975",
      				"name":"sda",
      				"status":"0"
      			}
      		],
      		"total":"1"
      	},
      	"msg":"操作成功"
      }
      

      5.32 新增友鏈

      5.32.1 需求

      ? 需要新增友鏈功能

      5.32.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      POST /content/link

      請求體:

      {
          "name":"sda",
          "description":"weqw",
          "address":"wewe",
          "logo":"weqe",
          "status":"2"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.33 修改友鏈

      5.33.1 需求

      ? 需要提供修改友鏈的功能

      5.33.2 接口設(shè)計(jì)

      5.33.2.1 根據(jù)id查詢友聯(lián)
      請求方式 請求路徑 是否需求token頭
      Get content/link/

      Path格式請求參數(shù):

      id: 友鏈id

      響應(yīng)格式:

      {
      	"code":200,
      	"data":{
      		"address":"wewe",
      		"description":"weqw",
      		"id":"4",
      		"logo":"weqe",
      		"name":"sda",
      		"status":"2"
      	},
      	"msg":"操作成功"
      }
      
      5.33.2.2 修改友鏈
      請求方式 請求路徑 是否需求token頭
      PUT /content/link

      請求體:

      {
          "address":"https://www.qq.com",
          "description":"dada2",
          "id":"2",
          "logo":"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn1.itc.cn%2Fimg8%2Fwb%2Frecom%2F2016%2F05%2F10%2F146286696706220328.PNG&refer=http%3A%2F%2Fn1.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646205529&t=f942665181eb9b0685db7a6f59d59975",
          "name":"sda",
          "status":"0"
      }
      

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      

      5.34 刪除友鏈

      5.34.1 需求

      ? 刪除某個(gè)友鏈(邏輯刪除)

      5.34.2 接口設(shè)計(jì)

      請求方式 請求路徑 是否需求token頭
      DELETE /content/link/

      Path請求參數(shù):

      id:要?jiǎng)h除的友鏈id

      響應(yīng)格式:

      {
      	"code":200,
      	"msg":"操作成功"
      }
      
      posted @ 2023-08-03 21:20  二價(jià)亞鐵  閱讀(307)  評論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 中文无码av一区二区三区| 亚洲肥老太bbw中国熟女| 男女一级国产片免费视频| 精品人妻伦九区久久aaa片69| 蜜臀视频在线观看一区二区| 亚洲午夜爱爱香蕉片| 国产精品久久蜜臀av| 久久久亚洲欧洲日产国码αv| 国产成AV人片在线观看天堂无码| 99久久国产一区二区三区| 秋霞在线观看秋| 精品黄色av一区二区三区| 女人高潮流白浆视频| 蜜桃视频一区二区三区四| 草草浮力影院| 欧美肥老太牲交大战| 伦理片午夜视频在线观看| 亚洲精品一区国产精品| 亚洲成av人片不卡无码手机版| 越西县| 免费无码中文字幕A级毛片| 日本大片在线看黄a∨免费| 亚洲熟妇乱色一区二区三区| av色蜜桃一区二区三区| 美女人妻激情乱人伦| 亚洲国产美国产综合一区| 色九九视频| 高清中文字幕国产精品| 92精品国产自产在线观看481页| 免费无码黄网站在线观看| 色吊丝一区二区中文字幕| 日韩人妻中文字幕精品| 国产午夜精品一区理论片| 国产精品一区二区久久岳| 人人澡人人透人人爽| 午夜福利免费视频一区二区| 国产四虎永久免费观看| 老司机午夜精品视频资源| 亚洲欧美另类激情综合区蜜芽| 九九热精品免费视频| 九九色这里只有精品国产|