[add]异常处理任务
This commit is contained in:
parent
f14455c72e
commit
7c81e75331
@ -1,18 +1,18 @@
|
||||
### 任务名称: 创建spring boot初始项目
|
||||
#### 目标
|
||||
## 任务名称: 创建spring boot初始项目
|
||||
### 目标
|
||||
- 通过spring start创建一个spring boot项目,选择依赖spring web, h2, mysql, jpa, lombok
|
||||
- 添加依赖: springdoc openapi
|
||||
- 添加配置文件: application.yml
|
||||
- 配置JPA、H2数据库、springdoc openapi、logging
|
||||
- 将项目提交到Gitea
|
||||
#### 预备知识
|
||||
### 预备知识
|
||||
- 项目构建工具gradle
|
||||
- spring boot框架项目基本目录结构和默认配置文件
|
||||
- yaml配置文件
|
||||
- git操作
|
||||
|
||||
#### 操作步骤
|
||||
1、使用Spring Initializr创建一个Spring Boot项目。
|
||||
### 操作步骤
|
||||
#### 1、使用Spring Initializr创建一个Spring Boot项目。
|
||||
|
||||
在浏览器中访问:https://start.spring.io/
|
||||
|
||||
@ -27,21 +27,25 @@
|
||||
|
||||
![](./images/spring-boot-start.png)
|
||||
|
||||
2、将生成项目中的所有文件和目录拷贝到自己的GIT项目中。
|
||||
|
||||
3、开发工具IDEA的基本配置
|
||||
|
||||
#### 2、将生成项目中的所有文件和目录拷贝到自己的GIT项目中。
|
||||
|
||||
#### 3、开发工具IDEA的基本配置
|
||||
|
||||
- 在idea中打开项目,设置项目使用的jdk版本为17。设置gradle的JDK版本为17。
|
||||
|
||||
4、构建工具Gradle的相关配置
|
||||
#### 4、构建工具Gradle的相关配置
|
||||
|
||||
- 修改gradle/wrapper/gradle-wrapper.properties文件,将distributionUrl的值修改为:
|
||||
- **使用国内的下载地址,提高下载gradle的速度**
|
||||
|
||||
修改gradle/wrapper/gradle-wrapper.properties文件,将distributionUrl的值修改为:
|
||||
```text
|
||||
https:\//mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip
|
||||
```
|
||||
使用国内的下载地址,提高下载gradle的速度
|
||||
|
||||
- 修改build.gradle文件中的repositories,设置gradle使用国内镜像
|
||||
|
||||
- **修改build.gradle文件中的repositories,设置gradle使用国内镜像**
|
||||
|
||||
```groovy
|
||||
repositories {
|
||||
@ -52,13 +56,34 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
```
|
||||
- 重新加载gradle项目
|
||||
- **重新加载gradle项目**
|
||||
![](./images/gradle-reload.png)
|
||||
等待gralde将所有依赖加载完成.
|
||||
|
||||
5、项目的相关配置
|
||||
等待gradle将所有依赖加载完成.
|
||||
|
||||
- 在目录src/main/resources下新建application.yml文件用作项目的主要配置文件。
|
||||
#### 5、项目的相关配置
|
||||
|
||||
- 在目录src/main/resources下新建空文件application.yml文件用作项目的主要配置文件。
|
||||
|
||||
> Spring Boot的默认配置文件是`application.properties`或`application.yml`。这两个文件中的任何一个都可以作为默认配置文件使用,具体取决于项目中存在哪个或者个人偏好。
|
||||
> `application.properties`更传统,适合不熟悉键值对的配置风格,而`application.yml`则采用更简洁的yaml格式,便于层次结构化数据表达。
|
||||
>
|
||||
> Spring Boot在启动时会自动查找以下位置的这些配置文件:
|
||||
>
|
||||
> > 项目根目录下的config文件夹:file:./config/
|
||||
> >
|
||||
> > 项目根目录本身:file:./
|
||||
> >
|
||||
> > 类路径下的config文件夹:classpath:/config/
|
||||
> >
|
||||
> > 类路径根目录:classpath:/
|
||||
>
|
||||
> 这些位置的优先级从高到低排列,意味着如果在同一位置有多个配置文件或不同位置有相同的配置设置,高优先级的配置将会覆盖低优先级的配置。
|
||||
>
|
||||
>
|
||||
> 放置在src/main/resources下面的配置文件属于类路径根目录类别,即classpath:/。
|
||||
> 如果你将application.properties或application.yml直接放在src/main/resources目录下,Spring Boot 启动时会自动加载这些配置文件。
|
||||
> 这是最常见也是推荐的放置配置文件的位置,因为这样的配置文件会随着项目的构建被包含进最终的可执行文件(如JAR包)中。
|
||||
|
||||
- 集成springdoc openapi
|
||||
|
||||
@ -113,31 +138,31 @@ springdoc:
|
||||
</appender>
|
||||
|
||||
<!--根据指定的包名设置开关级别-->
|
||||
<logger name="com.lk.demo" level="DEBUG"/>
|
||||
<logger name="org.springframework.web.servlet" level="INFO"/>
|
||||
<logger name="com.lk.paopao" level="DEBUG"/>
|
||||
<logger name="org.springframework.web.servlet" level="DEBUG"/>
|
||||
|
||||
<root level="WARN">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
```
|
||||
6、配置h2数据库和JPA
|
||||
#### 6、配置h2数据库和JPA
|
||||
|
||||
项目使用h2数据库作为默认的数据库,并使用JPA作为ORM框架。
|
||||
|
||||
- 在application.yml中添加配置:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
|
||||
datasource:
|
||||
url: jdbc:h2:file:./paopao.h2
|
||||
url: jdbc:h2:file:./paopao.h2 # 使用文件存储
|
||||
driverClassName: org.h2.Driver
|
||||
username: root
|
||||
password: root
|
||||
# initialization-mode: always
|
||||
h2:
|
||||
console: # 开启console 访问 默认false
|
||||
console: # 开启console访问 默认false
|
||||
enabled: true
|
||||
settings:
|
||||
trace: true # 开启h2 console 跟踪 方便调试 默认 false
|
||||
@ -149,27 +174,39 @@ spring:
|
||||
defer-datasource-initialization: true
|
||||
database-platform: org.hibernate.dialect.H2Dialect
|
||||
hibernate:
|
||||
ddl-auto: update # update create-drop
|
||||
ddl-auto: create-drop
|
||||
# 可选值:create-drop,create,update,none. create-drop:每次启动项目都会删除表,然后重新创建表,适合开发环境;create:每次启动项目都会创建表,适合开发环境;update:每次启动项目都会更新表,适合开发环境;none:不执行任何操作,适合生产环境。
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.H2Dialect
|
||||
```
|
||||
|
||||
#### 7. 项目java包结构设计
|
||||
创建包:controller、entity、repository、service、excepiton、security、dto、 dto.rest.response、dto.rest.request
|
||||
|
||||
7、运行项目
|
||||
- controller:控制器,处理请求,调用service层进行业务处理。
|
||||
- entity:实体类,对应数据库中的表。
|
||||
- repository:数据访问层,使用JPA进行数据访问。
|
||||
- service:业务层,调用repository层进行数据访问。
|
||||
- exception:异常处理类,处理业务异常。
|
||||
- security:安全配置类,用于配置用户认证、授权等。
|
||||
- dto:数据传输对象,用于封装请求参数和响应结果。
|
||||
- dto.rest.response:响应结果数据传输对象,用于封装响应结果。
|
||||
- dto.rest.request:请求参数数据传输对象,用于封装请求参数。
|
||||
|
||||
- 使用gradle的任务 `bootRun`运行项目,在控制台可以看到spring boot项目启动成功的信息,并生成了openapi文档。
|
||||
#### 8、运行项目
|
||||
|
||||
- **运行方式1:**使用gradle的任务 `bootRun`运行项目,在控制台可以看到spring boot项目启动成功的信息,并生成了openapi文档。
|
||||
|
||||
openapi文档访问地址: http://localhost:8080/api-docs
|
||||
|
||||
![](./images/spring-openapi.png)
|
||||
|
||||
- 在idea中运行项目,在控制台可以看到spring boot项目启动成功,并生成了openapi文档。
|
||||
- **运行方式2:** 在idea中运行项目,在控制台可以看到spring boot项目启动成功,并生成了openapi文档。
|
||||
|
||||
在项目的入口类上右键,在右键菜单中选择 "Run ..."
|
||||
|
||||
8、将项目中新添加文件同步到Gitea上的对应仓库
|
||||
#### 9、将项目中新添加文件同步到Gitea上的对应仓库
|
||||
|
||||
- 可在idea的控制台中依次执行命令:
|
||||
```shell
|
||||
@ -180,22 +217,22 @@ git push origin master
|
||||
- 或在idea中使用git菜单,依次完成 add、commit、push
|
||||
|
||||
|
||||
#### 技术/工具需求
|
||||
### 技术/工具需求
|
||||
- intellij idea 社区版 2023 及以上
|
||||
- gradle 8.5及及上
|
||||
- spring boot 3.2.3以上
|
||||
- jdk 17 及以上
|
||||
|
||||
#### 成功标准
|
||||
### 成功标准
|
||||
- 项目可正常运行,无错误
|
||||
- 可访问openapi文档 http://localhost:8080/api-docs
|
||||
- 项目代码提交到Gitea上
|
||||
|
||||
#### 扩展学习(可选)
|
||||
### 扩展学习(可选)
|
||||
- [提供一些额外学习资源或挑战性任务,鼓励学有余力的学生进一步探索。]
|
||||
|
||||
#### 评估与反馈
|
||||
### 评估与反馈
|
||||
- [说明如何提交作业、代码审查的标准、或任何反馈收集机制。]
|
||||
|
||||
#### 时间估算
|
||||
### 时间估算
|
||||
- [给出预计完成该任务所需的时间,帮助学生合理安排学习计划。]
|
@ -1,18 +1,17 @@
|
||||
### 任务名称: Restful接口回应数据的统一封装
|
||||
#### 目标:
|
||||
## 任务名称: Restful接口回应数据的统一封装
|
||||
### 目标:
|
||||
- 掌握Spring Boot框架基本层次结构
|
||||
- 掌握接口的统一返回格式的设计和实现
|
||||
- 泛型的使用
|
||||
- lombok的使用
|
||||
- spring配置文件中自定义属性
|
||||
#### 预备知识:
|
||||
### 预备知识:
|
||||
- 泛型
|
||||
|
||||
#### 操作步骤:
|
||||
##### 1. 项目java包结构设计
|
||||
创建包:controller、entity、repository、service、dto、 dto.rest.response
|
||||
### 操作步骤:
|
||||
|
||||
#### 1. 创建类Result
|
||||
|
||||
##### 2. 实现类Result
|
||||
```java
|
||||
package com.lk.paopao.dto.rest.response;
|
||||
|
||||
@ -22,7 +21,9 @@ import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
public class Result implements Serializable {
|
||||
// 状态码
|
||||
int code;
|
||||
// 信息
|
||||
String msg;
|
||||
public Result(int code, String message) {
|
||||
this.code = code;
|
||||
@ -32,8 +33,26 @@ public class Result implements Serializable {
|
||||
|
||||
```
|
||||
|
||||
##### 3. 实现类DataResult
|
||||
#### 2. 创建类DataResult
|
||||
DataResult继承Result,添加data属性,通过泛型来确定data的类型。
|
||||
|
||||
在编程中,使用泛型来设计 `DataResult` 继承自 `Result` 并添加 `data` 属性的目的是为了提供类型安全的数据处理和返回。以下是这种设计的几个关键目的:
|
||||
|
||||
**类型安全**:通过使用泛型,编译器可以在编译时检查 `data` 属性的类型,确保只有正确类型的数据被处理,从而避免类型错误。
|
||||
|
||||
**清晰的API**:为API添加泛型参数可以清晰地表明返回的数据类型,使得API的使用方式更加明确,易于理解和使用。
|
||||
|
||||
**重用性**:`Result` 类可以被设计为处理通用结果的状态和信息,而 `DataResult` 作为它的子类,可以专注于处理数据内容,这样可以在不同的上下文中重用 `Result` 类的基本功能。
|
||||
|
||||
**解耦**:将数据和结果状态分离,可以让调用者更灵活地处理结果,而不必担心数据类型的问题。
|
||||
|
||||
**扩展性**:如果需要添加额外的信息到结果中(如错误信息、状态码等),可以在 `Result` 类中实现,而 `DataResult` 可以保持专注于数据。
|
||||
|
||||
**数据封装**:`DataResult` 可以封装对数据的访问,提供一种控制数据访问和修改的方式。
|
||||
|
||||
**简化错误处理**:在异常处理或错误处理中,可以统一使用 `Result` 类型来传递错误信息,而 `DataResult` 用于传递成功状态的数据。
|
||||
|
||||
|
||||
```java
|
||||
package com.lk.paopao.dto.rest.response;
|
||||
|
||||
@ -53,11 +72,11 @@ public class DataResult<T> extends Result implements Serializable {
|
||||
}
|
||||
|
||||
```
|
||||
##### 4. 实现类DataResult
|
||||
#### 3. 实现工具类ResultUtil
|
||||
|
||||
通过工具类ResultUtil,实现ok、fail方法, 返回DataResult对象。
|
||||
|
||||
此工具类简化创建DataResult对象的过程。
|
||||
此工具类可以简化创建DataResult对象的过程。
|
||||
|
||||
```java
|
||||
package com.lk.paopao.dto.rest.response;
|
||||
@ -83,7 +102,7 @@ public class ResultUtil {
|
||||
}
|
||||
```
|
||||
|
||||
##### 5. 自定义API接口版本号
|
||||
#### 5. 自定义API接口版本号
|
||||
在application.yml配置文件中添加如下配置:
|
||||
```yaml
|
||||
|
||||
@ -96,17 +115,17 @@ app:
|
||||
```
|
||||
|
||||
|
||||
#### 技术/工具需求:
|
||||
### 技术/工具需求:
|
||||
- [列出完成任务所需的技术栈、工具、软件版本等。]
|
||||
|
||||
#### 成功标准:
|
||||
### 成功标准:
|
||||
- [明确完成任务的评判标准,如代码功能实现、性能指标、测试通过条件等。]
|
||||
|
||||
#### 扩展学习(可选):
|
||||
### 扩展学习(可选):
|
||||
- [提供一些额外学习资源或挑战性任务,鼓励学有余力的学生进一步探索。]
|
||||
|
||||
#### 评估与反馈:
|
||||
### 评估与反馈:
|
||||
- [说明如何提交作业、代码审查的标准、或任何反馈收集机制。]
|
||||
|
||||
#### 时间估算:
|
||||
### 时间估算:
|
||||
- [给出预计完成该任务所需的时间,帮助学生合理安排学习计划。]
|
197
docs/tasks/任务3-实现统一异常处理.md
Normal file
197
docs/tasks/任务3-实现统一异常处理.md
Normal file
@ -0,0 +1,197 @@
|
||||
## 任务名称: Restful接口回应数据的统一封装
|
||||
### 目标
|
||||
- 掌握Spring Boot框架的异常处理
|
||||
|
||||
### 预备知识
|
||||
- java的异常机制
|
||||
|
||||
### 操作步骤
|
||||
#### 1. 自定义异常的基类
|
||||
|
||||
实现一个项目的异常基类,设计为抽象类避免直接使用此类。基类继承自RuntimeException,并添加一个code属性,为每个异常类型分配一个唯一的code值。
|
||||
|
||||
自定义异常时定义一个基类可以带来多个好处,以下是一些主要优势:
|
||||
|
||||
> **代码复用**:基类可以包含所有自定义异常的通用属性和方法,减少了代码重复。
|
||||
>
|
||||
> **统一管理**:通过基类可以统一管理异常处理,比如定义一个通用的异常处理机制,简化异常的捕获和处理。
|
||||
>
|
||||
> **清晰的层次结构**:基类提供了一个清晰的异常层次结构,使得异常更加有组织,易于理解和使用。
|
||||
>
|
||||
> **维护方便**:当需要对异常进行修改或添加新特性时,只需在基类中进行一次修改,所有派生异常都会自动获得这些更改。
|
||||
>
|
||||
> **异常编码**:如你所提到的,基类可以包含一个错误码(code属性),为每个异常类型分配一个唯一值,这有助于快速识别和处理特定类型的异常。
|
||||
>
|
||||
> **信息丰富**:基类可以定义一些通用的构造函数,允许派生异常类在创建时提供额外的信息,如错误消息、原始异常等。
|
||||
>
|
||||
> **控制异常的传播**:基类可以决定异常是否应该被检查(checked)或不被检查(unchecked)。通过继承 `RuntimeException`,可以创建非检查型异常,这通常用于程序运行时错误,调用者不需要强制捕获。
|
||||
|
||||
|
||||
```java
|
||||
package com.lk.paopao.exception;
|
||||
|
||||
/**
|
||||
* 定义一个基础异常类,用于封装业务异常信息。
|
||||
* 这个类是 RuntimeException 的子类,意味着它是一个非检查(Unchecked)异常。
|
||||
* 它添加了一个错误码(code)来详细描述异常的类型。
|
||||
*/
|
||||
public abstract class BaseException extends RuntimeException{
|
||||
// 异常的错误码
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* 构造函数,用于创建一个带有错误码和消息的基础异常实例。
|
||||
*
|
||||
* @param code 异常的错误码,用于标识异常的类型。
|
||||
* @param message 异常的详细信息,帮助理解异常的原因。
|
||||
*/
|
||||
public BaseException(int code, String message){
|
||||
// 调用父类构造函数,将消息传递给父类,以初始化异常的详细信息。
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,用于创建一个带有错误码、消息和导致异常的原因的基础异常实例。
|
||||
*
|
||||
* @param code 异常的错误码,用于标识异常的类型。
|
||||
* @param message 异常的详细信息,帮助理解异常的原因。
|
||||
* @param cause 导致异常的原因,便于问题追踪和处理。
|
||||
*/
|
||||
public BaseException(int code, String message, Throwable cause) {
|
||||
// 调用父类构造函数,将消息和原因传递给父类,以初始化异常的详细信息和原因。
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2. 实现三个具体的自定义异常类
|
||||
|
||||
ResourceNotFoundException: 找不到资源,用于统一处理找不到资源的情况。
|
||||
|
||||
```java
|
||||
package com.lk.paopao.exception;
|
||||
|
||||
public class ResourceNotFoundException extends BaseException{
|
||||
|
||||
public ResourceNotFoundException(String resourceName, String condition){
|
||||
super(404, "根据条件:【"+condition+"】无法找到"+resourceName+"资源");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
ResourceExistedException: 资源已存在,用于统一处理资源已存在的情况。
|
||||
```java
|
||||
package com.lk.paopao.exception;
|
||||
|
||||
public class ResourceExistedException extends BaseException{
|
||||
public ResourceExistedException(String resourceName, String condition){
|
||||
super(409, "根据条件:【"+condition+"】可找到"+resourceName+"资源");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
AuthFailedException: 认证失败,用于统一处理认证失败的情况。
|
||||
```java
|
||||
package com.lk.paopao.exception;
|
||||
|
||||
public class AuthFailedException extends BaseException{
|
||||
public AuthFailedException(String message) {
|
||||
super(401, message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 实现异常的统一处理
|
||||
|
||||
在Spring框架中,`@ControllerAdvice` 是一个核心的类级别的注解,用于定义一个全局异常处理器。当一个Spring Boot应用中的控制器(即一个带有`@RestController`或`@Controller`注解的类)抛出异常时,Spring会检查所有的`@ControllerAdvice`注解的类,寻找匹配的异常处理器。
|
||||
|
||||
以下是`@ControllerAdvice`注解的一些关键点:
|
||||
|
||||
1. **全局异常处理**:`@ControllerAdvice` 注解的类可以包含一个或多个 `@ExceptionHandler` 方法,这些方法可以捕获并处理控制器层抛出的异常。
|
||||
|
||||
2. **注解方法**:在`@ControllerAdvice`类中,通过使用`@ExceptionHandler`注解的方法可以指定处理特定类型的异常。方法的参数可以是异常类型或者是`Throwable`类型。
|
||||
|
||||
3. **响应体**:`@ControllerAdvice`类中的方法通常使用`@ResponseBody`注解,这表明方法的返回值将直接作为HTTP响应的正文返回给客户端。
|
||||
|
||||
4. **日志记录**:在异常处理方法中,通常会记录异常信息,这有助于开发人员调试和监控应用。
|
||||
|
||||
5. **统一响应格式**:`@ControllerAdvice` 允许你定义一个统一的响应格式,无论哪种异常发生,客户端都会收到相同结构的响应,这有助于客户端统一处理不同异常情况。
|
||||
|
||||
6. **顺序**:如果有多个`@ControllerAdvice`注解的类,它们可以通过`@Order`注解或`Ordered`接口来指定处理顺序。
|
||||
|
||||
7. **作用域**:`@ControllerAdvice`注解的类可以是单例的(singleton),也可以是请求作用域的(request),具体取决于异常处理器的需要。
|
||||
|
||||
```java
|
||||
package com.lk.paopao.exception;
|
||||
|
||||
import com.lk.paopao.dto.rest.response.Result;
|
||||
import com.lk.paopao.dto.rest.response.ResultUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* 全局异常处理器,负责处理Controller层抛出的异常,提供统一的异常响应格式。
|
||||
*/
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理自定义异常BaseException,返回相应的错误结果。
|
||||
*
|
||||
* @param ex 自定义异常BaseException实例
|
||||
* @return 返回封装了错误信息的Result对象
|
||||
*/
|
||||
@ExceptionHandler(BaseException.class)
|
||||
@ResponseBody
|
||||
public Result handleBaseException(BaseException ex) {
|
||||
// 记录未预期异常信息
|
||||
log.info(ex.getMessage(),ex);
|
||||
return ResultUtil.fail(ex.getCode(),ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未预期的异常,返回通用错误结果。
|
||||
*
|
||||
* @param ex 异常实例
|
||||
* @return 返回封装了错误信息的Result对象
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public Result handleUnexpectedException(Exception ex) {
|
||||
// 记录未预期异常信息
|
||||
log.warn(ex.getMessage(),ex);
|
||||
// 返回通用错误结果, 500表示服务器内部错误
|
||||
return ResultUtil.fail(500,ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 技术/工具需求:
|
||||
- [列出完成任务所需的技术栈、工具、软件版本等。]
|
||||
|
||||
### 成功标准:
|
||||
- [明确完成任务的评判标准,如代码功能实现、性能指标、测试通过条件等。]
|
||||
|
||||
### 扩展学习(可选):
|
||||
- [提供一些额外学习资源或挑战性任务,鼓励学有余力的学生进一步探索。]
|
||||
|
||||
### 评估与反馈:
|
||||
- [说明如何提交作业、代码审查的标准、或任何反馈收集机制。]
|
||||
|
||||
### 时间估算:
|
||||
- [给出预计完成该任务所需的时间,帮助学生合理安排学习计划。]
|
Loading…
Reference in New Issue
Block a user