1. 程式人生 > 實用技巧 >springcloud專案實戰之線上教育網站開發

springcloud專案實戰之線上教育網站開發

線上教育

1、專案環境部署

  • 專案結構:
  • online_parent:根目錄(父工程),管理四個子模組:
    • canal_client:canal資料庫表同步模組(統計同步資料)
    • common:公共模組父節點
      • common_util:工具類模組,所有模組都可以依賴於它
      • service_base:service服務的base包,包含service服務的公共配置類,所有service模組依賴於它
      • spring_security:認證與授權模組,需要認證授權的service服務依賴於它
    • infrastructure:基礎服務模組父節點
      • api_gateway:api閘道器服務
    • service
      :api介面服務父節點
      • service_edu:教學相關api介面服務
      • service_oss:阿里雲oss api介面服務
      • service_acl:使用者許可權管理api介面服務(使用者管理、角色管理和許可權管理等)
      • service_cms:cms api介面服務
      • service_sms:簡訊api介面服務
      • service_trade:訂單和支付相關api介面服務
      • service_statistics:統計報表api介面服務
      • service_ucenter:會員api介面服務
      • service_vod:視訊點播api介面服務

Ⅰ、父專案依賴online_parent

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cxd</groupId>
    <artifactId>online_parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>online_parent</name>
    <description>online edu</description>

    <properties>
        <java.version>1.8</java.version>
        <mybatis-plus.version>3.3.1.tmp</mybatis-plus.version>
        <velocity.version>2.2</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <aliyun.oss.version>3.1.0</aliyun.oss.version>
        <jodatime.version>2.10.5</jodatime.version>
        <commons-fileupload.version>1.3.3</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.version>
        <commons-lang.version>3.9</commons-lang.version>
        <httpclient.version>4.5.1</httpclient.version>
        <jwt.version>0.7.0</jwt.version>
        <aliyun-java-sdk-core.version>4.5.1</aliyun-java-sdk-core.version>
        <aliyun-java-sdk-vod.version>2.15.10</aliyun-java-sdk-vod.version>
        <aliyun-sdk-vod-upload.version>1.4.12</aliyun-sdk-vod-upload.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <commons-dbutils.version>1.7</commons-dbutils.version>
        <canal.client.version>1.1.0</canal.client.version>
        <docker.image.prefix>zx</docker.image.prefix>
        <alibaba.easyexcel.version>2.1.1</alibaba.easyexcel.version>
        <apache.xmlbeans.version>3.1.0</apache.xmlbeans.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus 持久層-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!-- velocity 模板引擎, Mybatis Plus 程式碼生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>
            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--aliyunOSS-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun.oss.version}</version>
            </dependency>
            <!--日期時間工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>
            <!--檔案上傳-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons-fileupload.version}</version>
            </dependency>
            <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>
            <!--commons-lang3-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang.version}</version>
            </dependency>
            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>
            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <!--     aliyun-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun-java-sdk-core.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-vod</artifactId>
                <version>${aliyun-java-sdk-vod.version}</version>
            </dependency>
            <!--            <dependency>-->
            <!--                <groupId>com.aliyun</groupId>-->
            <!--                <artifactId>aliyun-sdk-vod-upload</artifactId>-->
            <!--                <version>${aliyun-sdk-vod-upload.version}</version>-->
            <!--            </dependency>-->
            <!--  json-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${alibaba.easyexcel.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.xmlbeans</groupId>
                <artifactId>xmlbeans</artifactId>
                <version>${apache.xmlbeans.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Ⅰ-Ⅱ、子工程依賴common

    <parent>
        <artifactId>online_parent</artifactId>
        <groupId>com.cxd</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>service_base</module>
    </modules>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!--lombok用來簡化實體類:需要安裝lombok外掛-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <!-- spring boot redis快取引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- lecttuce 快取連線池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

Ⅰ-Ⅱ-Ⅰ、子包依賴common_util

    <parent>
        <artifactId>common</artifactId>
        <groupId>com.cxd</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common_util</artifactId>

    <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
    </dependencies>

Ⅰ-Ⅱ-Ⅱ、子包依賴service_base

    <parent>
        <artifactId>common</artifactId>
        <groupId>com.cxd</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service_base</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.cxd</groupId>
            <artifactId>common_util</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

Ⅰ-Ⅲ、子工程依賴service

    <parent>
        <artifactId>online_parent</artifactId>
        <groupId>com.cxd</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>service_edu</module>
    </modules>

    <dependencies>
        <!--服務容錯-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--服務呼叫-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--服務註冊-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.cxd</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- velocity 模板引擎, Mybatis Plus 程式碼生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <!--日期時間工具-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <!--lombok用來簡化實體類:需要安裝lombok外掛-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
        </dependency>
        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <!--commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Ⅰ-Ⅲ-Ⅰ、子包service_edu

    <parent>
        <artifactId>service</artifactId>
        <groupId>com.cxd</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service_edu</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
        </dependency>
    </dependencies>

    <build>
        <!-- 專案打包時會將java目錄中的*.xml檔案也進行打包 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

2、edu模組介面開發

  • application.yml
server:
  port: 8110 # 服務埠
spring:
  profiles:
    active: dev
  application:
    name: service_edu # 服務名稱
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver # mysql資料庫連線
    url: jdbc:mysql://localhost:3306/db_guli_edu?serverTimezone=GMT%2B8
    username: root
    data-password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

# mybatis日誌
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Ⅰ、mp生成後端程式碼

package com.cxd.service.edu.test;

public class CodeGenerator {
    @Test
    public void genCode() {
        String prefix = "db_guli_";
        String moduleName = "edu";
        // 1、建立程式碼生成器
        AutoGenerator mpg = new AutoGenerator();
        // 2、全域性配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("cxd");
        gc.setOpen(true); //生成後是否開啟資源管理器
        gc.setFileOverride(true); //重新生成時檔案是否覆蓋
        gc.setServiceName("%sService"); //去掉Service介面的首字母I
        gc.setIdType(IdType.ASSIGN_ID); //主鍵策略
        gc.setDateType(DateType.ONLY_DATE);//定義生成的實體類中日期型別
        gc.setSwagger2(true);//開啟Swagger2模式
        mpg.setGlobalConfig(gc);
        // 3、資料來源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/" + prefix + moduleName + "?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);
        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(moduleName); //模組名
        pc.setParent("com.cxd.service");
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);
        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);//資料庫表對映到實體的命名
        strategy.setTablePrefix(moduleName + "_");//設定表字首不生成
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//資料庫表字段對映
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true)
        strategy.setLogicDeleteFieldName("is_deleted");//邏輯刪除欄位名
        strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布林值的is_字首
        //自動填充
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(gmtCreate);
        tableFills.add(gmtModified);
        strategy.setTableFillList(tableFills);
        strategy.setRestControllerStyle(true); //restful api風格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中駝峰轉連字元
        mpg.setStrategy(strategy);

        //設定BaseEntity
        strategy.setSuperEntityClass("BaseEntity");
        // 填寫BaseEntity中的公共欄位
        strategy.setSuperEntityColumns("id", "gmt_create", "gmt_modified");
        // 6、執行
        mpg.execute();
    }
}

Ⅱ、配置mp分頁外掛

package com.cxd.service.base.config;

@EnableTransactionManagement
@Configuration
@MapperScan("com.cxd.service.*.mapper")
public class MybatisPlusConfig {
    /**
    * 分頁外掛
    * */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }
}

Ⅲ、配置swagger生成介面文件([http://localhost:8110/swagger-ui.html#!/])

package com.cxd.service.base.config;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(webApiInfo())
                .groupName("webApi")
                .select()
                .paths(Predicates.and(PathSelectors.regex("/api/.*")))
                .build();
    }

    @Bean
    public Docket adminApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(adminApiInfo())
                .groupName("adminApi")
                .select()
                .paths(Predicates.and(PathSelectors.regex("/admin/.*")))
                .build();
    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("網站的API文件")
                .description("api介面定義")
                .version("1.0")
//                .contact(new Contact("cxd","http://www.qq.com","[email protected]"))
                .build();
    }

    private ApiInfo adminApiInfo(){
        return new ApiInfoBuilder()
                .title("後臺的API文件")
                .description("後臺api介面定義")
                .version("1.0")
//                .contact(new Contact("cxd","http://www.qq.com","[email protected]"))
                .build();
    }
}

Ⅳ、編寫BaseEntity類(其他實體類均繼承此類)

package com.cxd.service.base.model;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class BaseEntity implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "講師ID")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;

    @ApiModelProperty(value = "建立時間", example = "2020-11-11 8:00:00")
    @TableField(fill = FieldFill.INSERT)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime gmtCreate;

    @ApiModelProperty(value = "更新時間", example = "2020-11-11 8:00:00")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime gmtModified;
}

Ⅴ、在common包下定義結果返回狀態碼以及統一返回結果

  • R.class
@Data
@NoArgsConstructor
@ApiModel("全域性統一返回結果")
public class R {
    @ApiModelProperty(value = "是否成功")
    private Boolean success;

    @ApiModelProperty(value = "返回碼")
    private Integer code;

    @ApiModelProperty(value = "返回訊息")
    private String message;

    @ApiModelProperty(value = "返回資料")
    private Map<String, Object> data = new HashMap<>();

    public static R ok() {
        R r = new R();
        r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        return r;
    }

    public static R error() {
        R r = new R();
        r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
        r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
        r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
        return r;
    }

    public static R setResult(ResultCodeEnum resultCodeEnum) {
        R r = new R();
        r.setSuccess(resultCodeEnum.getSuccess());
        r.setCode(resultCodeEnum.getCode());
        r.setMessage(resultCodeEnum.getMessage());
        return r;
    }

    public R success(Boolean success) {
        this.success(success);
        return this;
    }

    public R message(String message) {
        this.setMessage(message);
        return this;
    }

    public R code(Integer code) {
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map) {
        this.setData(map);
        return this;
    }
}

  • ResultCodeEnum.class
@Getter
@ToString
public enum ResultCodeEnum {

    SUCCESS(true, 20000, "成功"),
    UNKNOWN_REASON(false, 20001, "未知錯誤"),

    BAD_SQL_GRAMMAR(false, 21001, "sql語法錯誤"),
    JSON_PARSE_ERROR(false, 21002, "json解析異常"),
    PARAM_ERROR(false, 21003, "引數不正確"),

    FILE_UPLOAD_ERROR(false, 21004, "檔案上傳錯誤"),
    FILE_DELETE_ERROR(false, 21005, "檔案刪除錯誤"),
    EXCEL_DATA_IMPORT_ERROR(false, 21006, "Excel資料匯入錯誤"),

    VIDEO_UPLOAD_ALIYUN_ERROR(false, 22001, "視訊上傳至阿里雲失敗"),
    VIDEO_UPLOAD_TOMCAT_ERROR(false, 22002, "視訊上傳至業務伺服器失敗"),
    VIDEO_DELETE_ALIYUN_ERROR(false, 22003, "阿里雲視訊檔案刪除失敗"),
    FETCH_VIDEO_UPLOADAUTH_ERROR(false, 22004, "獲取上傳地址和憑證失敗"),
    REFRESH_VIDEO_UPLOADAUTH_ERROR(false, 22005, "重新整理上傳地址和憑證失敗"),
    FETCH_PLAYAUTH_ERROR(false, 22006, "獲取播放憑證失敗"),

    URL_ENCODE_ERROR(false, 23001, "URL編碼失敗"),
    ILLEGAL_CALLBACK_REQUEST_ERROR(false, 23002, "非法回撥請求"),
    FETCH_ACCESSTOKEN_FAILD(false, 23003, "獲取accessToken失敗"),
    FETCH_USERINFO_ERROR(false, 23004, "獲取使用者資訊失敗"),
    LOGIN_ERROR(false, 23005, "使用者名稱或密碼不正確"),

    COMMENT_EMPTY(false, 24006, "評論內容必須填寫"),

    PAY_RUN(false, 25000, "支付中"),
    PAY_UNIFIEDORDER_ERROR(false, 25001, "統一下單錯誤"),
    PAY_ORDERQUERY_ERROR(false, 25002, "查詢支付結果錯誤"),

    ORDER_EXIST_ERROR(false, 25003, "課程已購買"),

    GATEWAY_ERROR(false, 26000, "服務不能訪問"),

    CODE_ERROR(false, 28000, "驗證碼錯誤"),
    LOGIN_DISABLED_ERROR(false, 28002, "該使用者已被禁用"),
    REGISTER_MOBLE_ERROR(false, 28003, "手機號已被註冊"),
    LOGIN_AUTH(false, 28004, "請先進行登入"),
    LOGIN_ACL(false, 28005, "沒有許可權"),
    SMS_SEND_ERROR(false, 28006, "簡訊傳送失敗"),
    SMS_SEND_ERROR_BUSINESS_LIMIT_CONTROL(false, 28007, "簡訊傳送過於頻繁");

    private final Boolean success;

    private final Integer code;

    private final String message;

    ResultCodeEnum(Boolean success, Integer code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }
}

Ⅵ、TeacherController實現CRUD

/**
 * <p>
 * 講師 前端控制器
 * </p>
 *
 * @author cxd
 * @since 2020-11-15
 */
@Api(tags = "講師管理")
@RestController
@RequestMapping("admin/edu/teacher")
public class TeacherController {

    @Autowired
    private TeacherService teacherService;


    @ApiOperation(value = "查詢所有老師列表")
    @GetMapping("list")
    public R listAll() {
        List<Teacher> list = teacherService.list();
        return R.ok().data("items", list);
    }

    @ApiOperation(value = "根據ID刪除講師", notes = "根據id刪除,邏輯刪除")
    @DeleteMapping("remove/{id}")
    public R removeById(@ApiParam("講師ID") @PathVariable String id) {
        boolean result = teacherService.removeById(id);
        if (result) {
            return R.ok().message("刪除成功");
        } else {
            return R.error().message("教師不存在");
        }
    }

    @ApiOperation("講師分頁列表")
    @GetMapping("list/{page}/{limit}")
    public R listPage(@ApiParam(value = "當前頁碼", required = true) @PathVariable long page,
                      @ApiParam(value = "每頁數量", required = true) @PathVariable long limit,
                      @ApiParam("查詢物件") TeacherQueryVo teacherQueryVo) {
        Page<Teacher> pageParam = new Page<>(page, limit);
        IPage<Teacher> pageModel = teacherService.selectPage(pageParam, teacherQueryVo);
        List<Teacher> records = pageModel.getRecords();
        long total = pageModel.getTotal();
        return R.ok().data("data", total).data("row", records);
    }

    @ApiOperation("新增導師")
    @PostMapping("save")
    public R save(@ApiParam(value = "講師物件", required = true) @RequestBody Teacher teacher) {
        boolean result = teacherService.save(teacher);
        if (result){
            return R.ok().message("儲存成功");
        }else{
            return R.error().message("操作失敗");
        }
    }

    @ApiOperation("更新導師")
    @PutMapping("update")
    public R updateById(@ApiParam(value = "講師物件",required = true) @RequestBody Teacher teacher) {
        boolean result = teacherService.updateById(teacher);
        if (result) {
            return R.ok().message("更新成功");
        } else {
            return R.error().message("資料不存在失敗");
        }
    }

    @ApiOperation("根據id獲取導師資訊")
    @GetMapping("get/{id}")
    public R getById(@ApiParam(value = "導師物件",required = true) @PathVariable String id) {
        Teacher teacher = teacherService.getById(id);
        if (teacher != null) {
            return R.ok().data("item", teacher);
        } else {
            return R.error().message("資料不存在");
        }
    }

Ⅶ、配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">

    <contextName>logback</contextName>

    <property name="log.path" value="./logger/edu"/>

    <!--控制檯日誌格式:彩色日誌-->
    <!-- magenta:洋紅 -->
    <!-- boldMagenta:粗紅-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋紅 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>

    <!--檔案日誌格式-->
    <property name="FILE_LOG_PATTERN"
              value="%date{yyyy-MM-dd HH:mm:ss} |%-5level |%thread |%file:%line |%logger |%msg%n"/>

    <!--編碼-->
    <property name="ENCODING" value="UTF-8"/>

    <!--輸出到控制檯-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!--日誌級別-->
            <level>INFO</level>
        </filter>
        <encoder>
            <!--日誌格式-->
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!--日誌字符集-->
            <charset>${ENCODING}</charset>
        </encoder>
    </appender>

    <!--輸出到檔案-->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日誌過濾器:此日誌檔案只記錄INFO級別的-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_info.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset>
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日誌歸檔路徑以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日誌過濾器:此日誌檔案只記錄WARN級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_warn.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset> <!-- 此處設定字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日誌過濾器:此日誌檔案只記錄ERROR級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_error.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset> <!-- 此處設定字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <!--開發環境-->
    <springProfile name="dev">
        <!--可以靈活設定此處,從而控制日誌的輸出-->
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>

    <!--生產環境-->
    <springProfile name="pro">
        <root level="ERROR">
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>
</configuration>

Ⅷ、service_base包中配置handler

@Component
@Slf4j
public class CommonMetaObjectHandler implements MetaObjectHandler {

    @Override
    //預設填充欄位
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
    }
}

Ⅸ、全域性異常處理

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    //全域性異常處理
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e){
        log.error(ExceptionUtils.getMessage(e));
        return R.error();
    }

    //指定異常處理
    @ExceptionHandler(BadSqlGrammarException.class)
    @ResponseBody
    public R error(BadSqlGrammarException e){
        log.error(ExceptionUtils.getMessage(e));
        return R.setResult(ResultCodeEnum.BAD_SQL_GRAMMAR);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public R error(HttpMessageNotReadableException e){
        log.error(ExceptionUtils.getMessage(e));
        return R.setResult(ResultCodeEnum.JSON_PARSE_ERROR);
    }
}

Ⅹ、配置logback日誌

  • logback-spring.xml(預設日誌的名字,必須是這個名字)
<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">

    <contextName>logback</contextName>
    <property name="log.path" value="D:/project/helen/guli_log/edu" />

    <!--控制檯日誌格式:彩色日誌-->
    <!-- magenta:洋紅 -->
    <!-- boldMagenta:粗紅-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋紅 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>

    <!--檔案日誌格式-->
    <property name="FILE_LOG_PATTERN"
              value="%date{yyyy-MM-dd HH:mm:ss} |%-5level |%thread |%file:%line |%logger |%msg%n" />

    <!--編碼-->
    <property name="ENCODING"
              value="UTF-8" />

    <!--輸出到控制檯-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!--日誌級別-->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <!--日誌格式-->
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!--日誌字符集-->
            <charset>${ENCODING}</charset>
        </encoder>
    </appender>

    <!--輸出到檔案-->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日誌過濾器:此日誌檔案只記錄INFO級別的-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_info.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset>
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日誌歸檔路徑以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日誌過濾器:此日誌檔案只記錄WARN級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_warn.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset> <!-- 此處設定字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日誌過濾器:此日誌檔案只記錄ERROR級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/log_error.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${ENCODING}</charset> <!-- 此處設定字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <!--開發環境-->
    <springProfile name="dev">
        <!--可以靈活設定此處,從而控制日誌的輸出-->
        <root level="DEBUG">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>

    <!--生產環境-->
    <springProfile name="pro">
        <root level="ERROR">
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>
</configuration>

類上添加註解 @Slf4j 修改異常輸出語句 log.error(e.getMessage());

2-1、edu模組前端開發

  • 匯入前端檔案到vscode

1、頁面模組

1、定義路由模組

src/router/index.js

配置講師管理相關路由

2、定義api模組

建立檔案 src/api/teacher.js

// @ 符號在build/webpack.base.conf.js 中配置 表示 'src' 路徑
import request from '@/utils/request'

export default {

  list() {
    return request({
      url: '/admin/edu/teacher/list',
      method: 'get'
    })
  }
}

3、定義頁面元件指令碼

src/views/teacher/list.vue

<script>
import teacherApi from '@/api/teacher'
export default {
  // 定義資料模型
  data() {
    return {
      list: [] // 講師列表
    }
  },

  // 頁面渲染成功後獲取資料
  created() {
    this.fetchData()
  },

  // 定義方法
  methods: {
    fetchData() {
      // 呼叫api
      teacherApi.list().then(response => {
        this.list = response.data.items
      })
    }
  }
}
</script>

4、定義頁面元件模板
<!-- 表格 -->
<el-table :data="list" border stripe>
    <el-table-column type="index" width="50"/>
    <el-table-column prop="name" label="名稱" width="80" />
    <el-table-column label="頭銜" width="90">
        <template slot-scope="scope">
          <el-tag v-if="scope.row.level === 1" type="success" size="mini">高階講師</el-tag>
          <el-tag v-if="scope.row.level === 2" size="mini">首席講師</el-tag>
        </template>
    </el-table-column>
    <el-table-column prop="intro" label="簡介" />
    <el-table-column prop="sort" label="排序" width="60" />
    <el-table-column prop="joinDate" label="入駐時間" width="160" />
</el-table>

2、分頁查詢

1、定義api模組

src/api/teacher.js

pageList(page, limit, searchObj) {
    return request({
        url: `/admin/edu/teacher/list/${page}/${limit}`,
        method: 'get',
        params: searchObj
    })
}

2、定義頁面元件指令碼

src/views/teacher/list.vue,完善data定義

data() {// 定義資料
    return {
        list: null, // 資料列表
        total: 0, // 總記錄數
        page: 1, // 頁碼
        limit: 10, // 每頁記錄數
        searchObj: {}// 查詢條件
    }
}

修改fetchData方法

fetchData() {
    // 呼叫api
    teacherApi.pageList(this.page, this.limit, this.searchObj).then(response => {
        this.list = response.data.rows
        this.total = response.data.total
    })
}

3、定義頁面元件模板

在table元件下面新增分頁元件

<!-- 分頁元件 -->
<el-pagination
  :current-page="page"
  :total="total"
  :page-size="limit"
  :page-sizes="[5, 10, 20, 30, 40, 50, 100]"
  style="padding: 30px 0; text-align: center;"
  layout="total, sizes, prev, pager, next, jumper"
/>

4、改變每頁條數

元件註冊事件

@size-change="changePageSize"

定義事件指令碼

// 每頁記錄數改變,size:回撥引數,表示當前選中的“每頁條數”
changePageSize(size) {
    this.limit = size
    this.fetchData()
}

5、翻頁

元件註冊事件

@current-change="changeCurrentPage"

定義事件指令碼

// 改變頁碼,page:回撥引數,表示當前選中的“頁碼”
changeCurrentPage(page) {
    this.page = page
    this.fetchData()
},  

6、序號列
<el-table-column
  label="#"
  width="50">
  <template slot-scope="scope">
    {{ (page - 1) * limit + scope.$index + 1 }}
  </template>
</el-table-column>

7、查詢表單

在table元件上面新增查詢表單

<!--查詢表單-->
<el-form :inline="true">
    <el-form-item>
        <el-input v-model="searchObj.name" placeholder="講師"/>
    </el-form-item>

    <el-form-item>
        <el-select v-model="searchObj.level" clearable placeholder="頭銜">
            <el-option value="1" label="高階講師"/>
            <el-option value="2" label="首席講師"/>
        </el-select>
    </el-form-item>

    <el-form-item label="入駐時間">
        <el-date-picker
                        v-model="searchObj.joinDateBegin"
                        placeholder="開始時間"
                        value-format="yyyy-MM-dd" />
    </el-form-item>
    <el-form-item label="-">
        <el-date-picker
                        v-model="searchObj.joinDateEnd"
                        placeholder="結束時間"
                        value-format="yyyy-MM-dd" />
    </el-form-item>
    <el-form-item>
        <el-button type="primary" icon="el-icon-search" @click="fetchData()">查詢</el-button>
        <el-button type="default" @click="resetData()">清空</el-button>
    </el-form-item>
</el-form>

重置表單指令碼

// 重置表單
resetData() {
    this.searchObj = {}
    this.fetchData()
}

3、資料刪除

1、定義api模組

src/api/teacher.js

removeById(id) {
    return request({
        url: `/admin/edu/teacher/remove/${id}`,
        method: 'delete'
    })
}

2、定義頁面元件模板

在table元件中新增刪除列

<el-table-column label="操作" width="200" align="center">
    <template slot-scope="scope">
        <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeById(scope.row.id)">刪除</el-button>
    </template>
</el-table-column>

3、定義頁面元件指令碼
// 根據id刪除資料
removeById(id) {
    this.$confirm('此操作將永久刪除該記錄, 是否繼續?', '提示', {
        confirmButtonText: '確定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        return teacherApi.removeById(id)
    }).then((response) => {
        this.fetchData()
        this.$message.success(response.message)
    }).catch(error => {
        console.log('error', error)
        // 當取消時會進入catch語句:error = 'cancel'
        // 當後端服務丟擲異常時:error = 'error'
        if (error === 'cancel') {
            this.$message.info('取消刪除')
        }
    })
}

4、axios響應攔截器

1、關於code===20000

code!==20000的響應會被攔截,並轉到 error=>{} 處理

if (res.code !== 20000) {
    return Promise.reject('error')
}

2、關於response

code===20000時放行,前端頁面接收到response.data的值,而不是response

if (res.code !== 20000) {
    return Promise.reject('error')
} else {
    return response.data
}

5、新增講師

1、定義api模組

src/api/teacher.js

save(teacher) {
    return request({
        url: '/admin/edu/teacher/save',
        method: 'post',
        data: teacher
    })
}

2、定義頁面元件指令碼

src/views/teacher/form.vue,完善data定義

<script>
export default {
  data() {
    return {
      // 初始化講師預設資料
      teacher: {
        sort: 0,
        level: 1
      },
      saveBtnDisabled: false // 儲存按鈕是否禁用,防止表單重複提交
    }
  }
}
</script>

3、定義頁面元件模板

src/views/teacher/form.vue

<!-- 輸入表單 -->
<el-form label-width="120px">
    <el-form-item label="講師名稱">
        <el-input v-model="teacher.name" />
    </el-form-item>
    <el-form-item label="入駐時間">
        <el-date-picker v-model="teacher.joinDate" value-format="yyyy-MM-dd" />
    </el-form-item>
    <el-form-item label="講師排序">
        <el-input-number v-model="teacher.sort" :min="0"/>
    </el-form-item>
    <el-form-item label="講師頭銜">
        <el-select v-model="teacher.level">
            <!--
            資料型別一定要和取出的json中的一致,否則沒法回填
            因此,這裡value使用動態繫結的值,保證其資料型別是number
            -->
            <el-option :value="1" label="高階講師"/>
            <el-option :value="2" label="首席講師"/>
        </el-select>
    </el-form-item>
    <el-form-item label="講師簡介">
        <el-input v-model="teacher.intro"/>
    </el-form-item>
    <el-form-item label="講師資歷">
       <el-input v-model="teacher.career" :rows="10" type="textarea"/>
    </el-form-item>
    <!-- 講師頭像:TODO -->

    <el-form-item>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate()">儲存</el-button>
    </el-form-item>
</el-form>

4、實現新增功能

src/views/teacher/form.vue,引入teacher api模組:

import teacherApi from '@/api/teacher'

定義儲存方法

methods: {

  saveOrUpdate() {
    // 禁用儲存按鈕
    this.saveBtnDisabled = true
    this.saveData()
  },

  // 新增講師
  saveData() {
    // debugger
    teacherApi.save(this.teacher).then(response => {
      this.$message({
        type: 'success',
        message: response.message
      })
      this.$router.push({ path: '/teacher' })
    })
  }
}

6、顯示講師資訊

1、定義api模組

src/api/teacher.js

getById(id) {
    return request({
        url: `/admin/edu/teacher/get/${id}`,
        method: 'get'
    })
}

2、定義頁面元件指令碼

src/views/teacher/form.vue,methods中定義回顯方法

// 根據id查詢記錄
fetchDataById(id) {
    teacherApi.getById(id).then(response => {
        this.teacher = response.data.item
    })
}

頁面渲染成功獲取資料

因為已在路由中定義如下內容:path: 'edit/:id',因此可以使用 this.$route.params.id 獲取路由中的id

//頁面渲染成功
created() {
    if (this.$route.params.id) {
        this.fetchDataById(this.$route.params.id)
    }
}

3、定義頁面元件模板

src/views/teacher/list.vue,表格“操作”列中增加“修改”按鈕

<router-link :to="'/teacher/edit/'+scope.row.id">
    <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
</router-link>

7、更新講師

1、定義api模組

src/api/teacher.js

updateById(teacher) {
    return request({
        url: '/admin/edu/teacher/update',
        method: 'put',
        data: teacher
    })
}

2、定義頁面元件指令碼

src/views/teacher/form.vue,methods中定義updateData

// 根據id更新記錄
updateData() {
  // teacher資料的獲取
  teacherApi.updateById(this.teacher).then(response => {
      this.$message({
        type: 'success',
        message: response.message
      })
      this.$router.push({ path: '/teacher' })
  })
},

完善saveOrUpdate方法

saveOrUpdate() {
    // 禁用儲存按鈕
    this.saveBtnDisabled = true
    if (!this.teacher.id) {
        this.saveData()
    } else {
        this.updateData()
    }
}

8、元件重用問題

問題:vue-router導航切換 時,如果兩個路由都渲染同個元件,

元件的生命週期方法(created或者mounted)不會再被呼叫, 元件會被重用,顯示上一個路由渲染出來的自建

解決方案:可以簡單的在 router-view上加上一個唯一的key,來保證路由切換時都會重新觸發生命週期方法,確保元件被重新初始化。

修改 src/views/layout/components/AppMain.vue 檔案如下:

<router-view :key="key"></router-view>

computed: {
    key() {
        return this.$route.name !== undefined? this.$route.name + +new Date(): this.$route + +new Date()
    }
 }

3、oss模組開發