国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

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

這篇具有很好參考價(jià)值的文章主要介紹了項(xiàng)目實(shí)戰(zhàn)-前后端分離博客系統(tǒng)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

項(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整合配置測(cè)試

①創(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;
    //評(píng)論數(shù)
    private Integer commentCount;
    //訪問量
    private Long viewCount;
    //是否允許評(píng)論 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測(cè)試接口

? 注意思考這些文件應(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依賴方便測(cè)試

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ī)號(hào)已存在"), 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è)置允許跨域請(qǐng)求的域名
                .allowedOriginPatterns("*")
                // 是否允許cookie
                .allowCredentials(true)
                // 設(shè)置允許的請(qǐng)求方式
                .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中字段比較多。

? 我們?cè)陧?xiàng)目中一般最后還要把VO來接受查詢出來的結(jié)果。一個(gè)接口對(duì)應(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)對(duì)象
        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 需求

? 頁面上需要展示分類列表,用戶可以點(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 需求

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

? 首頁:查詢所有的文章

? 分類頁面:查詢對(duì)應(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);
        // 對(duì)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)換消息的對(duì)象
        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ì)

請(qǐng)求方式 請(qǐng)求路徑
Get /article/

響應(yīng)格式:文章來源地址http://www.zghlxwxcb.cn/news/detail-623593.html

{
  "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ì)

請(qǐng)求方式 請(qǐng)求路徑
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ì)

請(qǐng)求方式 請(qǐng)求路徑
POST /login

請(qǐng)求體:

{
    "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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/login").anonymous()
                // 除上面外的所有請(qǐng)求全部不需要認(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 {
        //獲取請(qǐng)求頭中的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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/login").anonymous()
                //jwt過濾器測(cè)試用,如果測(cè)試沒有問題吧這里刪除了
                .antMatchers("/link/getAllLink").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/login").anonymous()
                //jwt過濾器測(cè)試用,如果測(cè)試沒有問題吧這里刪除了
                .antMatchers("/link/getAllLink").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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í)際我們?cè)陂_發(fā)過程中可能需要做很多的判斷校驗(yàn),如果出現(xiàn)了非法情況我們是期望響應(yīng)對(duì)應(yīng)的提示的。但是如果我們每次都自己手動(dòng)去處理就會(huì)非常麻煩。我們可以選擇直接拋出異常的方式,然后對(duì)異常進(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);
        //從異常對(duì)象中獲取提示信息封裝返回
        return ResponseResult.errorResult(e.getCode(),e.getMsg());
    }


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

3.9 退出登錄接口

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

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /logout 需要token請(qǐng)求頭

響應(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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/login").anonymous()
            	//注銷接口需要認(rèn)證才能訪問
                .antMatchers("/logout").authenticated()
                //jwt過濾器測(cè)試用,如果測(cè)試沒有問題吧這里刪除了
                .antMatchers("/link/getAllLink").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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 查詢?cè)u(píng)論列表接口

3.10.1 需求

? 文章詳情頁面要展示這篇文章下的評(píng)論列表。

? 效果如下:

3.10.2 評(píng)論表分析

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

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

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

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

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /comment/commentList 不需要token請(qǐng)求頭

Query格式請(qǐng)求參數(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 不考慮子評(píng)論

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) {
        //查詢對(duì)應(yīng)文章的根評(píng)論
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        //對(duì)articleId進(jìn)行判斷
        queryWrapper.eq(Comment::getArticleId,articleId);
        //根評(píng)論 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;
    //根評(píng)論id
    private Long rootId;
    //評(píng)論內(nèi)容
    private String content;
    //所回復(fù)的目標(biāo)評(píng)論的userid
    private Long toCommentUserId;
    private String toCommentUserName;
    //回復(fù)目標(biāo)評(píng)論id
    private Long toCommentId;

    private Long createBy;

    private Date createTime;

    private String username;
}

3.10.4.2 查詢子評(píng)論

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommentVo {
    private Long id;
    //文章id
    private Long articleId;
    //根評(píng)論id
    private Long rootId;
    //評(píng)論內(nèi)容
    private String content;
    //所回復(fù)的目標(biāo)評(píng)論的userid
    private Long toCommentUserId;
    private String toCommentUserName;
    //回復(fù)目標(biāo)評(píng)論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) {
        //查詢對(duì)應(yīng)文章的根評(píng)論
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        //對(duì)articleId進(jìn)行判斷
        queryWrapper.eq(Comment::getArticleId,articleId);
        //根評(píng)論 rootId為-1
        queryWrapper.eq(Comment::getRootId,-1);

        //分頁查詢
        Page<Comment> page = new Page(pageNum,pageSize);
        page(page,queryWrapper);

        List<CommentVo> commentVoList = toCommentVoList(page.getRecords());

        //查詢所有根評(píng)論對(duì)應(yīng)的子評(píng)論集合,并且賦值給對(duì)應(yīng)的屬性
        for (CommentVo commentVo : commentVoList) {
            //查詢對(duì)應(yīng)的子評(píng)論
            List<CommentVo> children = getChildren(commentVo.getId());
            //賦值
            commentVo.setChildren(children);
        }

        return ResponseResult.okResult(new PageVo(commentVoList,page.getTotal()));
    }

    /**
     * 根據(jù)根評(píng)論的id查詢所對(duì)應(yīng)的子評(píng)論的集合
     * @param id 根評(píng)論的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ā)表評(píng)論接口

3.11.1 需求

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

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

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

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /comment 需要token頭
請(qǐng)求體:

回復(fù)了文章:

{"articleId":1,"type":0,"rootId":-1,"toCommentId":-1,"toCommentUserId":-1,"content":"評(píng)論了文章"}

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

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

如果是友鏈評(píng)論,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) {
        //評(píng)論內(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)評(píng)論列表

3.12.1 需求

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

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

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /comment/linkCommentList 不需要token請(qǐng)求頭

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

pageNum: 頁碼

pageSize: 每頁條數(shù)

響應(yīng)格式:

{
    "code": 200,
    "data": {
        "rows": [
            {
                "articleId": "1",
                "children": [
                    {
                        "articleId": "1",
                        "content": "回復(fù)友鏈評(píng)論3",
                        "createBy": "1",
                        "createTime": "2022-01-30 10:08:50",
                        "id": "23",
                        "rootId": "22",
                        "toCommentId": "22",
                        "toCommentUserId": "1",
                        "toCommentUserName": "sg333",
                        "username": "sg333"
                    }
                ],
                "content": "友鏈評(píng)論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 修改了之前的文章評(píng)論列表接口,并且增加了新的友聯(lián)評(píng)論接口

    @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è)常量

    /**
     * 評(píng)論類型為:文章評(píng)論
     */
    public static final String ARTICLE_COMMENT = "0";
    /**
     * 評(píng)論類型為:友聯(lián)評(píng)論
     */
    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è)評(píng)論類型的添加。

    @Override
    public ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {
        //查詢對(duì)應(yīng)文章的根評(píng)論
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        //對(duì)articleId進(jìn)行判斷
        queryWrapper.eq(SystemConstants.ARTICLE_COMMENT.equals(commentType),Comment::getArticleId,articleId);
        //根評(píng)論 rootId為-1
        queryWrapper.eq(Comment::getRootId,-1);

        //評(píng)論類型
        queryWrapper.eq(Comment::getType,commentType);

        //分頁查詢
        Page<Comment> page = new Page(pageNum,pageSize);
        page(page,queryWrapper);

        List<CommentVo> commentVoList = toCommentVoList(page.getRecords());

        //查詢所有根評(píng)論對(duì)應(yīng)的子評(píng)論集合,并且賦值給對(duì)應(yīng)的屬性
        for (CommentVo commentVo : commentVoList) {
            //查詢對(duì)應(yīng)的子評(pí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ì)

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /user/userInfo 需要token請(qǐng)求頭

不需要參數(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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/login").anonymous()
                //注銷接口需要認(rèn)證才能訪問
                .antMatchers("/logout").authenticated()
            	//個(gè)人信息接口必須登錄后才能訪問
                .antMatchers("/user/userInfo").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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對(duì)象存儲(chǔ)服務(wù))存儲(chǔ)圖片或視頻。

3.14.2.2 七牛云基本使用測(cè)試

秘鑰

3.14.2.3 七牛云測(cè)試代碼編寫

①添加依賴

        <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 對(duì)象的配置類
        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ì)

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /upload 需要token

參數(shù):

? img,值為要上傳的文件

請(qǐng)求頭:

? 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();
        //對(duì)原始文件名進(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 對(duì)象的配置類
        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ì)對(duì)個(gè)人資料進(jìn)行更新。

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

?

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
PUT /user/userInfo 需要token請(qǐng)求頭

參數(shù)

請(qǐng)求體中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 用戶注冊(cè)

3.16.1 需求

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

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

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

?

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /user/register 不需要token請(qǐng)求頭

參數(shù)

請(qǐng)求體中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) {
        //對(duì)數(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);
        }
        //對(duì)數(shù)據(jù)進(jìn)行是否存在的判斷
        if(userNameExist(user.getUserName())){
            throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
        }
        if(nickNameExist(user.getNickName())){
            throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
        }
        //...
        //對(duì)密碼進(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ī)號(hào)已存在"), EMAIL_EXIST(503, "郵箱已存在"),
    REQUIRE_USERNAME(504, "必需填寫用戶名"),
    CONTENT_NOT_NULL(506, "評(píng)論內(nèi)容不能為空"),
    FILE_TYPE_ERROR(507, "文件類型錯(cuò)誤,請(qǐng)上傳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í)日志打印格式如下:

3.17.2 思路分析

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

?

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

日志打印格式

        log.info("=======Start=======");
        // 打印請(qǐng)求 URL
        log.info("URL            : {}",);
        // 打印描述信息
        log.info("BusinessName   : {}", );
        // 打印 Http method
        log.info("HTTP Method    : {}", );
        // 打印調(diào)用 controller 的全路徑以及執(zhí)行方法
        log.info("Class Method   : {}.{}", );
        // 打印請(qǐng)求的 IP
        log.info("IP             : {}",);
        // 打印請(qǐng)求入?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)對(duì)應(yīng)博客瀏覽量的增加。

3.18.2 思路分析

? 我們只需要在每次用戶瀏覽博客時(shí)更新對(duì)應(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接口,并且把對(duì)應(yīng)的bean注入容器。把相關(guān)初始化的代碼重新到需要重新的方法中。

? 這樣就會(huì)在應(yīng)用啟動(dòng)的時(shí)候執(zhí)行對(duì)應(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ù),讓大家先對(duì)定時(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)行測(cè)試,代表從0秒開始,每隔5秒執(zhí)行一次。

? 注意:對(duì)應(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í)對(duì)應(yīng)的Cron語法的,這樣可以更有利于我們書寫Cron表達(dá)式。

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

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

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

星號(hào)表示任意值,例如:

* * * * * ?

表示 “ 每年每月每天每時(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í)不使用?都是不對(duì)的

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

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

W

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

0 0 0 31W * ?

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

L

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

在日期中表示每月最后一天,在一月份中表示31號(hào),在六月份中表示30號(hào)

也可以表示每月倒是第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ì)

?

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
PUT /article/updateViewCount/ 不需要token請(qǐng)求頭

參數(shù)

? 請(qǐng)求路徑中攜帶文章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中對(duì)應(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)建的開源工具,可以幫助我們?cè)O(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 測(cè)試

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

4.4 具體配置

4.4.1 Controller配置

4.4.1 @Api 注解

屬性介紹:

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

description 設(shè)置描述信息

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

4.4.2 接口配置

4.4.2.1 接口描述配置@ApiOperation
    @GetMapping("/linkCommentList")
    @ApiOperation(value = "友鏈評(píng)論列表",notes = "獲取一頁友鏈評(píng)論")
    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 = "友鏈評(píng)論列表",notes = "獲取一頁友鏈評(píng)論")
    @ApiImplicitParams({
           @ApiImplicitParam(name = "pageNum",value = "頁號(hào)"),
           @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 = "添加評(píng)論dto")
public class AddCommentDto{
    //..
}
4.4.3.2 實(shí)體的屬性的描述配置@ApiModelProperty

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

    @ApiModelProperty(notes = "評(píng)論類型(0代表文章評(píng)論,1代表友鏈評(píng)論)")
    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測(cè)試接口

? 注意思考這些文件應(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()
                // 對(duì)于登錄接口 允許匿名訪問
//                .antMatchers("/login").anonymous()
//                //注銷接口需要認(rèn)證才能訪問
//                .antMatchers("/logout").authenticated()
//                .antMatchers("/user/userInfo").authenticated()
//                .antMatchers("/upload").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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 {
        //獲取請(qǐng)求頭中的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ì)

請(qǐng)求方式 請(qǐng)求路徑
POST /user/login

請(qǐng)求體:

{
    "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

請(qǐng)求地址修改為/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接口

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /getInfo 需要token請(qǐng)求頭

請(qǐng)求參數(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接口
請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /getRouters 需要token請(qǐng)求頭

請(qǐng)求參數(shù):

響應(yīng)格式:

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

? 注意:返回的菜單數(shù)據(jù)需要體現(xiàn)父子菜單的層級(jí)關(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表對(duì)于的類

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ì)

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /user/logout 需要token請(qǐng)求頭

響應(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中對(duì)應(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()
                // 對(duì)于登錄接口 允許匿名訪問
                .antMatchers("/user/login").anonymous()
//                //注銷接口需要認(rèn)證才能訪問
//                .antMatchers("/logout").authenticated()
//                .antMatchers("/user/userInfo").authenticated()
//                .antMatchers("/upload").authenticated()
                // 除上面外的所有請(qǐng)求全部不需要認(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 需求

? 為了方便后期對(duì)文章進(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ì)

請(qǐng)求方式 請(qǐng)求路徑
Get content/tag/list

Query格式請(qǐng)求參數(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ì)

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
POST /content/tag 需要token請(qǐng)求頭

請(qǐng)求體格式:

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

響應(yīng)格式:

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

5.5.2 測(cè)試

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

5.6 刪除標(biāo)簽

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

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
DELETE /content/tag/ 需要token請(qǐng)求頭

請(qǐng)求參數(shù)在path中

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

響應(yīng)格式:

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

5.6.2 測(cè)試

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

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

5.7 修改標(biāo)簽

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

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

請(qǐng)求參數(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)簽接口
請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
PUT /content/tag 需要token請(qǐng)求頭

請(qǐng)求體格式:

{"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 查詢所有分類接口
請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /content/category/listAllCategory 需要token請(qǐng)求頭

請(qǐng)求參數(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)簽接口
請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /content/tag/listAllTag 需要token請(qǐng)求頭

請(qǐng)求參數(shù):

? 無

響應(yīng)格式:

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

參數(shù):

? img,值為要上傳的文件

請(qǐng)求頭:

? Content-Type :multipart/form-data;

響應(yīng)格式:

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

請(qǐng)求體格式:

{
    "title":"測(cè)試新增博文",
    "thumbnail":"https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/21/4ceebc07e7484beba732f12b0d2c43a9.png",
    "isTop":"0",
    "isComment":"0",
    "content":"# 一級(jí)標(biāo)題\n## 二級(jí)標(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;
    //是否允許評(píng)論 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ì)

?

請(qǐng)求方式 請(qǐng)求地址 請(qǐng)求頭
GET /content/category/export 需要token請(qǐng)求頭

請(qǐng)求參數(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è)置下載文件的請(qǐng)求頭
            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 需求

? 需要對(duì)導(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){
        //如果是超級(jí)管理員  直接返回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è)置下載文件的請(qǐng)求頭
            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 需求

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

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

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get /content/article/list

Query格式請(qǐng)求參數(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)目級(jí)前端分離認(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 查詢文章詳情接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get content/article/

Path格式請(qǐng)求參數(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 更新文章接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT content/article

請(qǐng)求體參數(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ì)

?

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
DELETE content/article/

Path請(qǐng)求參數(shù):

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

響應(yīng)格式:

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

5.14 菜單列表

5.14.1 需求

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

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

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

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET system/menu/list

Query請(qǐng)求參數(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ì)

?

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
POST content/article

請(qǐng)求體參數(shù):

? Menu類對(duì)應(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ù)
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get system/menu/

Path格式請(qǐng)求參數(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 更新菜單
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT system/menu

請(qǐng)求體參數(shù):

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

響應(yīng)格式:

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

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

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

5.17 刪除菜單

5.17.1 需求

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求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 需求

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

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

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

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET system/role/list

Query格式請(qǐng)求參數(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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT system/role/changeStatus

請(qǐng)求體:

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

響應(yīng)格式:

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

5.20 新增角色?。?/h3>

5.20.1 需求

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

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

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

無需請(qǐng)求參數(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 新增角色接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
POST system/role

請(qǐng)求體:

{
    "roleName":"測(cè)試新增角色",
    "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 角色信息回顯接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get system/role/

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

id: 角色id

響應(yīng)格式:

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

Path格式請(qǐng)求參數(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 更新角色信息接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT system/role

請(qǐng)求體:

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

響應(yīng)格式:

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

5.22 刪除角色

5.22.1 需求

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

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

?

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
DELETE system/role/

Path請(qǐng)求參數(shù):

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

響應(yīng)格式:

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

5.23 用戶列表

5.23.1 需求

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

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

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

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET system/user/list

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

pageNum: 頁碼

pageSize: 每頁條數(shù)

userName:用戶名

phonenumber:手機(jī)號(hào)

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ī)號(hào)必須之前未存在,否則提示:手機(jī)號(hào)已存在

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

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

5.24.2.1 查詢角色列表接口

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET /system/role/listAllRole

響應(yīng)格式:

{
	"code":200,
	"data":[
		{
			"createBy":"0",
			"createTime":"2021-11-12 18:46:19",
			"delFlag":"0",
			"id":"1",
			"remark":"超級(jí)管理員",
			"roleKey":"admin",
			"roleName":"超級(jí)管理員",
			"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 新增用戶
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
POST system/user

請(qǐng)求體:

{
    "userName":"wqeree",
    "nickName":"測(cè)試新增用戶",
    "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)前操作的用戶

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
DELETE /system/user/

Path請(qǐng)求參數(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查詢用戶信息回顯接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get /system/user/

Path格式請(qǐng)求參數(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":"超級(jí)管理員",
				"roleKey":"admin",
				"roleName":"超級(jí)管理員",
				"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 更新用戶信息接口
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT /system/user

請(qǐng)求體:

{
    "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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET content/category/list

Query格式請(qǐng)求參數(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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
POST /content/category

請(qǐng)求體:

{
    "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查詢分類
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get content/category/

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

id: 分類id

響應(yīng)格式:

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

請(qǐng)求體:

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

響應(yīng)格式:

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

5.30 刪除分類

5.30.1 需求

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

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

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
DELETE /content/category/

Path請(qǐng)求參數(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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
GET /content/link/list

Query格式請(qǐng)求參數(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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
POST /content/link

請(qǐng)求體:

{
    "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)
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
Get content/link/

Path格式請(qǐng)求參數(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 修改友鏈
請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
PUT /content/link

請(qǐng)求體:

{
    "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ì)

請(qǐng)求方式 請(qǐng)求路徑 是否需求token頭
DELETE /content/link/

Path請(qǐng)求參數(shù):

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

響應(yīng)格式:

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

到了這里,關(guān)于項(xiàng)目實(shí)戰(zhàn)-前后端分離博客系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 二:Spring Boot后端與數(shù)據(jù)庫連接

    SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 二:Spring Boot后端與數(shù)據(jù)庫連接

    系列文章: SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 一:Vue前端設(shè)計(jì) SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 二:Spring Boot后端與數(shù)據(jù)庫連接 SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 三:Spring Boot后端與Vue前端連接 SpringBoot + Vue前后端分離項(xiàng)目實(shí)戰(zhàn) || 四:用戶管理功能實(shí)現(xiàn) SpringBoot + Vue前后

    2024年02月11日
    瀏覽(54)
  • 前后端分離實(shí)現(xiàn)博客系統(tǒng)

    前后端分離實(shí)現(xiàn)博客系統(tǒng)

    基于servlet+jdbc進(jìn)行后端開發(fā),設(shè)計(jì)了一個(gè)可以發(fā)布博客,查看博客詳情,刪除博客,登錄注銷功能的簡易博客系統(tǒng)。 1.1 登陸頁面 1.2 博客列表頁面 1.3 博客詳情頁面 1.4 博客編輯頁面 2.1.1 創(chuàng)建maven項(xiàng)目 我們?cè)趇dea里面點(diǎn)擊new project創(chuàng)建一個(gè)maven項(xiàng)目,然后一路next選擇好項(xiàng)目位置

    2024年02月14日
    瀏覽(21)
  • 博客系統(tǒng)(前后端分離)

    博客系統(tǒng)(前后端分離)

    ??作者:銀河罐頭 ??系列專欄:JavaEE ?? “種一棵樹最好的時(shí)間是十年前,其次是現(xiàn)在” 創(chuàng)建項(xiàng)目,引入依賴,把之前寫的前端頁面拷貝進(jìn)去。 依賴主要是:servlet, mysql, jackson 在當(dāng)前的博客系統(tǒng)中,主要涉及到 2 個(gè)實(shí)體,博客 和 用戶。 創(chuàng)建 2 張表,來表示博客和用戶。

    2023年04月14日
    瀏覽(22)
  • 個(gè)人博客系統(tǒng)(前后端分離)

    個(gè)人博客系統(tǒng)(前后端分離)

    努力經(jīng)營當(dāng)下,直至未來明朗! 普通小孩也要熱愛生活! 個(gè)人博客系統(tǒng)采用前后端分離的方法來實(shí)現(xiàn),同時(shí)使用了數(shù)據(jù)庫來存儲(chǔ)相關(guān)的數(shù)據(jù),同時(shí)使用tomcat進(jìn)行項(xiàng)目的部署。前端主要有四個(gè)頁面構(gòu)成:登錄頁、列表頁、詳情頁以及編輯頁,以上模擬實(shí)現(xiàn)了最簡單個(gè)博客列表

    2024年02月02日
    瀏覽(22)
  • 博客系統(tǒng)(使用前后端分離)

    博客系統(tǒng)(使用前后端分離)

    這篇博客相當(dāng)于是,根據(jù)前面的所學(xué)的知識(shí),來做一個(gè)綜合練習(xí) 登錄頁: 列表詳情頁: 博客詳情頁 博客編輯頁 因?yàn)槭遣┛凸芾硐到y(tǒng),我們涉及數(shù)據(jù)庫的話,這個(gè)兩個(gè)表的結(jié)構(gòu)如下: 列出數(shù)據(jù)庫的基本代碼. 我們會(huì)對(duì)數(shù)據(jù)庫有基本的操作. Blog表 =Blog類對(duì)應(yīng)的Blog的一個(gè)對(duì)象,就對(duì)應(yīng)表中

    2024年02月16日
    瀏覽(16)
  • 【基于前后端分離的博客系統(tǒng)】Servlet版本

    【基于前后端分離的博客系統(tǒng)】Servlet版本

    ? ?????? 點(diǎn)進(jìn)來你就是我的人了 博主主頁: ?????? 戳一戳,歡迎大佬指點(diǎn)! 歡迎志同道合的朋友一起加油喔 ?????? 目錄 一. 項(xiàng)目簡介? 1. 項(xiàng)目背景 2. 項(xiàng)目用到的技術(shù) 3. 項(xiàng)目功能簡單介紹? 二. 博客系統(tǒng)頁面設(shè)計(jì)? 三. 項(xiàng)目準(zhǔn)備工作 前后端交互約定內(nèi)容的分析? 1. 接口

    2024年02月08日
    瀏覽(23)
  • 【Java EE】-博客系統(tǒng)一(前后端分離)

    【Java EE】-博客系統(tǒng)一(前后端分離)

    作者 :學(xué)Java的冬瓜 博客主頁 :?冬瓜的主頁?? 專欄 :【JavaEE】 分享 : 謎一樣的 沉默著的 故事你真的在聽嗎 ——《平凡之路》 主要內(nèi)容 :準(zhǔn)備工作:創(chuàng)建maven,引入依賴。設(shè)計(jì)數(shù)據(jù)庫并編寫數(shù)據(jù)庫代碼。前后端分離功能的實(shí)現(xiàn)。博客列表,博客詳情,登錄功能,注冊(cè)功

    2024年02月12日
    瀏覽(24)
  • 【Java EE】-博客系統(tǒng)二(前后端分離)

    【Java EE】-博客系統(tǒng)二(前后端分離)

    作者 :學(xué)Java的冬瓜 博客主頁 :?冬瓜的主頁?? 專欄 :【JavaEE】 分享 : 徘徊著的 在路上的 你要走嗎 易碎的 驕傲著 那也曾是我的模樣 ——《平凡之路》 主要內(nèi)容 :顯示用戶信息、上傳頭像、新增博客、刪除博客、修改博客。 分兩個(gè)子功能:列表頁顯示登錄用戶信息;

    2024年02月11日
    瀏覽(24)
  • 后端SpringBoot+前端Vue前后端分離的項(xiàng)目(二)

    后端SpringBoot+前端Vue前后端分離的項(xiàng)目(二)

    前言:完成一個(gè)列表,實(shí)現(xiàn)表頭的切換,字段的篩選,排序,分頁功能。 目錄 一、數(shù)據(jù)庫表的設(shè)計(jì) 二、后端實(shí)現(xiàn) 環(huán)境配置 model層 ?mapper層 service層 ?service層單元測(cè)試 controller層 三、前端實(shí)現(xiàn) interface接口 接口api層 主要代碼 效果展示 引入mybatis-plus依賴 MerchandiseDetailsService文

    2024年02月09日
    瀏覽(94)
  • 后端SpringBoot+前端Vue前后端分離的項(xiàng)目(一)

    后端SpringBoot+前端Vue前后端分離的項(xiàng)目(一)

    前言:后端使用SpringBoot框架,前端使用Vue框架,做一個(gè)前后端分離的小項(xiàng)目,需求:實(shí)現(xiàn)一個(gè)表格,具備新增、刪除、修改的功能。 目錄 一、數(shù)據(jù)庫表的設(shè)計(jì) 二、后端實(shí)現(xiàn) 環(huán)境配置 數(shù)據(jù)處理-增刪改查 model層 mapper層 XML配置 Service層 controller層 單元測(cè)試 三、前后端交互 配置

    2024年02月10日
    瀏覽(95)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包