本文还有配套的精品资源,点击获取
简介:在Java Web开发中,了解Servlet的doGet()和doPost()方法的区别至关重要。doGet()方法用于获取资源,请求参数可见,长度有限,适合小数据量的获取请求,且可被缓存,但不宜用于敏感数据。doPost()方法用于提交数据到服务器,请求参数隐藏,数据量无限制,且不被缓存,适合敏感信息的传输。选择合适的方法取决于具体需求,还应注意编程实践中的幂等性、安全性和重定向等问题。
1. Servlet中doGet()和doPost()方法的区别
简介
在Java Servlet API中, doGet() 和 doPost() 是两个非常关键的方法,它们代表了HTTP协议中的两种基本请求类型:GET和POST。这两个方法被用来处理来自客户端的不同请求,它们在使用上和设计上有着本质的区别。
主要区别
doGet() 方法通常用于从服务器检索数据,这意味着它应该设计为幂等的,即多次请求同一个 doGet() 方法应当产生相同的结果,且不应该产生任何副作用。此外,GET请求通常将参数附加在URL后面,因此适用于数据量不大的情况。
而 doPost() 方法则用于向服务器提交数据,它可以处理大量的数据,且它的请求体可以包含任意类型的数据。POST请求不是幂等的,因为它可能会改变服务器上的状态或数据库中的数据。同时,使用 doPost() 方法可以避免将敏感数据暴露在URL中,从而提高了传输的安全性。
使用场景
在实际的Web开发中,选择使用 doGet() 或 doPost() 方法取决于具体的应用场景。例如,在表单提交时,通常会使用 doPost() ,因为它能够处理表单中的数据,并将其安全地发送到服务器。相反,在获取资源的URL时,比如查看文章或图片,使用 doGet() 方法会更为合适。
接下来的章节将详细探讨GET和POST请求的特性,应用场景以及如何在不同的开发场景中做出合适的方法选择。
2. GET请求的特性及应用场景
2.1 GET请求的参数传递机制
2.1.1 URL编码与解码过程
当GET请求的参数通过URL进行传递时,参数值必须进行URL编码。这是因为URL中的某些字符可能具有特定的意义,例如问号 ? 用于指示URL的查询字符串的开始,而等号 = 用于分隔参数名和参数值。此外,URL中的空格等特殊字符需要转换为 + 或者使用百分号编码(例如空格会被编码为 %20 )。解码则是编码的逆过程,将这些编码后的字符转换回原始字符。
URL编码通常由客户端或者服务器端在发送请求之前进行,确保传输过程中参数值的正确性。例如,在浏览器地址栏中输入的URL会自动进行编码。
String encodedUrl = URLEncoder.encode("value with spaces", "UTF-8");
String decodedUrl = URLDecoder.decode(encodedUrl, "UTF-8");
上面的Java代码示例中, URLEncoder 类用于对包含空格的字符串进行编码,而 URLDecoder 类则用于将已编码的字符串解码。编码与解码的字符集参数需要保持一致,通常为UTF-8,以确保不同系统间的兼容性。
2.1.2 参数在URL中的表现形式
在URL中,GET请求的参数通常以键值对的形式存在,通过 & 符号来分隔不同的参数。每个键值对包括一个参数名和一个参数值,以 = 连接。例如:
http://example.com/search?q=example&lang=en
在这个URL中,有两个GET参数,一个是 q ,其值为 example ;另一个是 lang ,其值为 en 。
2.2 GET请求的数据长度与安全性
2.2.1 HTTP协议对GET请求长度的限制
HTTP协议本身并没有明确定义GET请求的最大长度限制。然而,实际上会受到多种因素的限制。在Web浏览器中,GET请求的参数通常是通过URL传递的,由于URL的长度受限于不同的浏览器和服务器配置,一般推荐不超过2048个字符。服务器端配置,如IIS或Apache的限制,也可能对URL长度进行限制。
当URL长度超过这些限制时,客户端可能会收到错误消息,或者部分数据可能被截断。在服务器端进行限制主要是为了防止超出缓冲区的大小,从而避免缓冲区溢出攻击。
2.2.2 GET请求的安全性考量
由于GET请求通过URL传递数据,这些数据会被包含在浏览器历史记录和服务器日志中。这导致GET请求不适合传递敏感信息,如密码或认证令牌,因为这些信息可能会被第三方通过历史记录或日志轻易获取。通常建议使用POST请求来处理包含敏感信息的数据传输。
此外,GET请求可能会因为重复提交而造成意外的副作用,例如重复下单或发送多次邮件。在设计Web应用时,开发者应该注意区分哪些操作是安全的幂等性操作,哪些则不是。
2.3 GET请求的缓存与历史记录特性
2.3.1 浏览器缓存对GET请求的影响
浏览器缓存机制可能会存储GET请求的响应结果,以提高页面加载速度和减少服务器负载。然而,这可能会导致用户看到过时的数据,特别是在GET请求参数未发生变化,但服务器上的内容已经更新的情况下。开发者通常通过设置HTTP响应头来控制缓存行为,例如通过 Cache-Control 来指定缓存策略。
2.3.2 GET请求与历史记录的关联性
当用户通过浏览器访问一个URL时,该URL连同其参数会被记录在浏览器的历史记录中。这意味着用户可以通过浏览器的后退按钮返回到之前的GET请求页面。这在某些应用中可能会带来便利,比如搜索历史或导航历史的实现。然而,这也可能造成隐私泄露的风险,因此在处理敏感数据时应当避免使用GET请求。
在实际开发中,可以通过JavaScript的 history.pushState 方法在不进行实际页面跳转的情况下修改浏览器的地址栏和历史记录,这可以用于实现单页应用的导航而不影响GET请求的特性。
3. POST请求的特性及应用场景
3.1 POST请求的数据传输方式
3.1.1 数据封装在请求体中
在HTTP协议中, POST 请求通常用于向服务器提交数据,这些数据被封装在HTTP请求体中。请求体中的数据可以是任何格式,但最常见的格式是 application/x-www-form-urlencoded 和 multipart/form-data 。以下是一个 application/x-www-form-urlencoded 格式的POST请求数据封装示例:
POST /submitForm HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
name=John&age=30&gender=male
在这个例子中,提交的数据包括一个名字(name),一个年龄(age)和性别(gender)。数据被编码为键值对,并用 & 符号分隔。
另一种常见的格式是 multipart/form-data ,它经常用于上传文件。 multipart/form-data 格式允许提交任意类型的数据,包括文本和二进制文件。它使用了边界(boundary)字符串来分隔不同的部分。
代码块解释:
// 示例代码:使用Java Servlet来处理multipart/form-data格式的POST请求
// 导入需要的类
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
@WebServlet("/uploadFile")
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
InputStream fileContent = filePart.getInputStream();
// 处理文件内容
// 其他字段处理
String name = request.getParameter("name");
String age = request.getParameter("age");
String gender = request.getParameter("gender");
// 逻辑处理代码...
}
}
上述代码段展示了在Servlet中处理文件上传请求的逻辑。 Part 对象用于获取上传的文件,同时可以使用 getParameter 方法获取其他表单字段。这里需要注意的是,处理 multipart/form-data 的代码通常比处理 application/x-www-form-urlencoded 的代码要复杂,因为需要处理文件流和文件名等信息。
3.1.2 数据传输的安全性与保密性
相较于 GET 请求, POST 请求的数据传输具有更好的安全性与保密性。由于数据不会显示在URL中,它们不易被浏览器历史记录、服务器日志或第三方网站捕获。此外,传输数据时可以使用SSL/TLS协议进行加密,确保数据在传输过程中的安全。
使用 POST 请求时,数据保密性的保护取决于开发者如何实现。例如,敏感信息应该使用HTTPS传输,并在服务器端进行安全存储。此外,对于包含敏感数据的表单,开发者还应该使用 Secure 和 HttpOnly 标志来增强Cookie的安全性。
3.2 POST请求的容量与限制
3.2.1 无固定数据量限制的原因
POST 请求没有像 GET 请求那样的固定数据量限制,因为 GET 请求的参数是通过URL传递的,而URL的长度受到浏览器和服务器的限制。相比之下, POST 请求的数据是放在请求体中的,对于请求体的大小,HTTP规范并没有硬性规定,其上限由客户端和服务器的配置决定。
例如,Apache服务器默认限制POST请求体的大小为2GB。而在客户端,浏览器也可能限制提交的表单的大小,这通常通过JavaScript进行控制,以避免用户提交过大的数据。
3.2.2 实际应用中可能遇到的限制
尽管 POST 请求没有固定的数据量限制,但在实际应用中,开发者需要意识到各种限制因素。例如,网络带宽限制可能影响大文件的上传速度,服务器配置不当可能导致处理大体积请求时出错。为了避免这些问题,开发者需要在客户端和服务器端设置合理的限制,并在必要时提供清晰的错误信息给用户。
表格展示:POST请求限制的常见因素
限制因素 影响 解决方法 网络带宽 上传下载速度受限 压缩数据、优化网络结构 服务器内存 处理大型请求时内存溢出 优化代码、扩展服务器资源 配置限制 如Apache的RequestSize限制 修改服务器配置或使用流式处理
通过表格我们可以看到,解决这些限制的通用方法是优化系统配置和算法效率,确保服务器能够有效地处理数据传输请求。
4. 选择GET或POST的实践指南
4.1 判断请求类型的基本原则
在Web开发中,选择合适的HTTP方法是确保应用安全性、效率和正确实现业务逻辑的关键。在实际的项目开发中,了解何时使用GET方法,何时使用POST方法,以及它们之间如何相互关联,是每一个开发人员必须掌握的技能。
4.1.1 根据数据的安全性需求选择
安全性是决定请求类型的一个重要因素。GET方法不适用于需要保密的请求,因为它的参数在URL中是可见的。这意味着所有通过GET发送的数据都可以被搜索引擎索引,甚至被存储在浏览器历史记录中,或者在客户端的URL栏中显示。因此,敏感数据应避免通过GET方法传输。
相比之下,POST请求将数据封装在请求体中,使得数据不会出现在URL中,提高了数据的保密性。它适用于提交用户表单数据、上传文件、执行数据库操作等场景,其中数据的保密性至关重要。
4.2 不同应用场景下的方法选择
在实际开发过程中,不同的应用场景决定了我们应当选择哪种类型的HTTP请求。正确的选择能够帮助我们更好地组织代码,保证应用的安全性,以及优化性能。
4.2.1 数据获取与展示:GET的适用场景
当需要从服务器获取资源时,GET方法是一个理想的选择。例如,在一个博客网站上,用户点击“查看文章详情”时,浏览器会发出一个GET请求,服务器随后返回该文章的数据。这种场景下,数据的获取并不会对服务器状态产生改变,且适合缓存和搜索引擎索引。
GET请求的幂等性保证了用户不论请求多少次同一个资源,其结果都是相同的,并不会产生副作用。这一点对于数据获取和展示尤其重要,因为用户可能在不知情的情况下重复点击链接。
4.3 性能考虑与实际案例分析
在Web开发中,性能是一个不可忽视的因素。GET和POST方法在性能上的差异会影响到应用的整体表现和用户体验。
4.3.1 GET与POST在性能上的差异
由于GET请求通常用于数据获取,它支持缓存和快速重载,而这些特点使得GET请求在性能上往往优于POST。GET请求的参数是通过URL传递的,这意味着它们可以被浏览器缓存,也可以被存储在历史记录和书签中。此外,GET请求通常更适合使用CDN(内容分发网络)进行加速。
然而,POST请求由于其安全性以及用于修改服务器资源的特点,通常不具备这些性能优势。服务器需要处理请求体中的数据,这可能涉及到更复杂的逻辑,并且每次请求都可能创建新的资源,不适合缓存。
4.3.2 实际案例:选择GET或POST的考量
例如,在一个电子商务网站上,用户浏览商品列表时,网站使用GET请求从服务器检索商品数据。这些数据可以被缓存,以提高响应速度。当用户决定购买一个商品并提交订单时,网站使用POST请求将订单信息发送到服务器,以创建一个新的订单资源。
在这个案例中,GET和POST的正确使用保证了网站的性能和功能性,同时遵守了HTTP协议的最佳实践。服务器后端处理逻辑也更加清晰,因为GET请求和POST请求的用途被明确区分开。
在下一章中,我们将探讨在编程实践中需要注意的其他事项。
5. 编程实践中的注意事项
5.1 幂等性的重要性与实现
5.1.1 什么是幂等性
幂等性是分布式系统设计中的一个重要概念。在HTTP协议中,幂等性指的是对于同一个请求,无论其执行多少次,都只会产生一次相同的影响。例如,对于一个创建订单的操作,无论是执行一次还是多次,结果都应当是创建了一个订单,并不会因为执行多次而产生多个订单。
5.1.2 如何确保请求的幂等性
为了确保请求的幂等性,开发者可以采取以下措施:
使用唯一事务ID :每次请求都带上一个唯一的事务ID,服务器端使用这个ID作为判断是否已经处理过该请求的依据。 数据库操作 :在数据库层面进行操作时,使用事务,并适当设置约束和索引,以防止重复操作。 状态码管理 :在处理请求时,检查操作的状态码。如果请求已成功处理,则不再进行操作。 逻辑控制 :在业务逻辑中加入控制,比如通过标志位判断某个操作是否已经执行。
// 伪代码示例:确保幂等性的事务控制
public void createOrder(Order order) {
boolean orderExists = checkOrderExists(order.getId());
if (!orderExists) {
// 创建订单前先检查是否已存在
createOrderInDB(order); // 数据库操作封装在方法中
// ...
} else {
// 订单已存在,不执行创建操作
log.info("Order already exists, skipping creation for order ID: " + order.getId());
}
}
确保幂等性不仅仅是代码层面的事情,它还需要在架构设计和数据库设计层面得到充分的考虑和实现。
5.2 安全性问题的防范
5.2.1 SQL注入与跨站脚本攻击
SQL注入和跨站脚本攻击(XSS)是Web应用常见的安全问题。要防范这些攻击,需要采取以下措施:
输入验证 :对所有输入进行严格的验证,不允许执行任何非预期的输入。 使用预处理语句和参数化查询 :这种方法可以有效防止SQL注入,因为SQL语句和数据被分开处理。 使用合适的转义函数 :对输入数据使用适当的转义函数,尤其是对输出到HTML的内容。 设置合适的HTTP头 :比如使用 Content-Security-Policy 来防止XSS攻击。
-- 伪代码示例:使用预处理语句防止SQL注入
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ?';
EXECUTE stmt USING @username;
DEALLOCATE PREPARE stmt;
5.2.2 输入验证与错误处理
良好的输入验证和错误处理机制是防止安全漏洞的重要措施:
数据类型检查 :确保输入数据符合预期的数据类型。 长度限制 :限制输入数据的最大长度,防止缓冲区溢出。 错误提示信息 :避免向用户暴露敏感信息,如数据库错误、路径等信息。 日志记录 :记录关键操作的日志,并确保日志的安全存储。
// 伪代码示例:输入验证和错误处理
public void validateInput(String input) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("Input cannot be null or empty");
}
if (input.length() > 100) {
throw new IllegalArgumentException("Input is too long");
}
// ...其他验证逻辑
}
5.3 重定向与表单提交
5.3.1 301与302重定向的区别
HTTP状态码301和302都表示重定向,但它们有本质的区别:
301 Moved Permanently :这是一个永久重定向的状态码,意味着资源已经永久移动到了新的位置。 302 Found :这是一个临时重定向的状态码,意味着资源暂时在新的位置,将来可能改变。
在设计Web应用时,需要根据实际情况选择适当的重定向类型。
5.3.2 POST-REDIRECT-GET模式的使用
为了防止用户刷新页面导致重复提交表单,可以使用POST-REDIRECT-GET模式:
用户提交表单后 :服务器接收到POST请求后,处理请求(比如保存数据到数据库)。 服务器重定向响应 :处理完成后,服务器响应302重定向到一个GET请求的URL,这个URL可以是一个展示处理结果的页面。 用户刷新页面 :用户刷新这个GET请求的页面时,不会重新提交表单数据,而是重新加载页面。
// 伪代码示例:POST-REDIRECT-GET模式
public void handleSubmitForm(FormData formData) {
// 保存表单数据
saveFormData(formData);
// 重定向到结果页面
String resultUrl = "/result";
redirect(resultUrl);
}
使用POST-REDIRECT-GET模式能够避免重复提交问题,并在用户刷新页面时提供更好的用户体验。
6. HTTP请求方法的扩展与创新
6.1 其他HTTP方法简介
HTTP协议定义了多种不同的请求方法,除了GET和POST,还有PUT、DELETE、OPTIONS、HEAD和TRACE等方法。每种方法都有其特定的用途和场景。
6.1.1 PUT与DELETE方法的应用场景
PUT和DELETE方法被设计用来实现创建和删除资源的操作。在RESTful API设计原则中,PUT方法通常用于更新或创建资源,而DELETE方法用于删除资源。例如,一个Web服务可能会允许使用PUT方法向服务器发送包含新资源信息的数据,如果该资源不存在,服务器应该创建它;如果资源已存在,服务器则更新该资源。同理,DELETE方法用于从服务器上删除一个指定的资源。
6.1.2 OPTIONS、HEAD与TRACE方法的作用
OPTIONS方法用于获取服务器支持的所有HTTP方法。 HEAD方法用于请求资源的元数据,例如响应头,但不返回具体的内容。 TRACE方法允许客户端看到请求/响应链上中间节点对请求消息的处理情况。
6.2 新兴技术对HTTP方法的影响
随着新兴技术的发展,如RESTful API、WebSockets以及HTTP/2,传统的HTTP方法得到了新的应用和挑战。
6.2.1 RESTful API设计中的HTTP方法使用
RESTful API采用无状态的通信方式,强调使用标准的HTTP方法来实现数据的CRUD操作。HTTP方法被用作资源操作的动词,例如GET用于读取,POST用于创建,PUT用于更新,DELETE用于删除。
6.2.2 WebSockets与HTTP/2对传统方法的挑战
WebSockets提供了一种全双工通信机制,允许服务端向客户端推送消息,这挑战了传统基于请求/响应模型的HTTP方法。HTTP/2通过连接复用、服务器推送等特性,提高了数据传输的效率,但它与RESTful API的设计原则可能需要一些权衡。
6.3 实践中的方法创新
开发者通过实践,创新HTTP方法的使用,以满足日益复杂的Web应用需求。
6.3.1 自定义HTTP方法的实践案例
在一些场景下,标准的HTTP方法不能完全满足特定的需求。自定义HTTP方法允许开发者根据自己的业务逻辑扩展HTTP协议。例如,一个Web服务可以定义一个PATCH方法来实现部分更新资源的功能。
6.3.2 方法创新在微服务架构中的应用
微服务架构鼓励细粒度的服务划分,而这种架构下,HTTP方法可以被用于服务之间的通信。例如,服务A可以通过一个PUT请求向服务B发送数据,服务B通过一个GET请求调用服务C提供的资源等。方法创新和组合使用可以提升微服务间通信的灵活性和效率。
代码块展示
例如,我们可以通过使用Spring框架,定义一个HTTP PATCH方法的控制器:
@RestController
@RequestMapping("/api")
public class ResourceController {
//...
@PatchMapping(value = "/resources/{id}", consumes = "application/json")
public ResponseEntity> updateResource(@PathVariable String id, @RequestBody Resource resource) {
// 更新资源的逻辑
return ResponseEntity.ok().build();
}
}
在这个代码块中,我们定义了一个PATCH方法的控制器,允许更新指定ID的资源。注意,我们通过 @PatchMapping 注解定义了方法,并指定了它所接受的内容类型为JSON。
mermaid流程图展示
下面是一个简化的流程图,展示了一个HTTP请求的处理过程:
graph LR
A[客户端] -->|HTTP请求| B(服务器)
B -->|解析请求| C{请求类型}
C -->|GET| D[获取资源]
C -->|POST| E[创建资源]
C -->|PUT| F[更新资源]
C -->|DELETE| G[删除资源]
D --> H[响应]
E --> H
F --> H
G --> H
H --> I[客户端]
这个流程图简单描述了根据不同的HTTP请求方法,服务器采取不同的处理动作,最后向客户端返回响应。
7. 总结与展望
7.1 doGet()和doPost()方法的未来趋势
在Web开发的世界里, doGet() 和 doPost() 方法一直是构建基于HTTP请求应用程序的基石。随着互联网技术的不断进步,对这些基础方法的使用也呈现出了一些新的趋势和变化。
7.1.1 方法使用趋势分析
随着对Web应用性能和安全性的要求越来越高,开发者们对 doGet() 和 doPost() 方法的使用也在不断地进行优化。例如,越来越多的Web应用开始采用RESTful API的设计原则,推崇使用符合资源定位、创建、更新、删除(CRUD)操作的HTTP方法,如PUT、DELETE等,以提供更加清晰和直观的API结构。
同时,对 doGet() 方法的使用,由于其简单、高效、易于缓存等特性,仍然是进行无副作用的数据请求(比如数据查询)的首选方法。但开发者被提醒注意GET请求的URL长度限制和安全性问题,尤其是在传递敏感信息时。
doPost() 方法则被广泛用于需要提交数据到服务器的场景,如表单提交、文件上传等。它不受URL长度限制,且由于数据包含在请求体中,更加适合于处理大量数据。
7.1.2 新标准对传统方法的影响
随着HTTP/2和WebSockets等新技术的出现,传统方法也在受到挑战。HTTP/2通过多路复用等特性提高了传输效率,这意味着在某些情况下, doGet() 和 doPost() 方法的性能差距可能会进一步缩小。而WebSockets提供了一种全新的实时双向通信方式,这可能会让开发者重新考虑何时使用传统的HTTP请求方法,何时采用新的协议。
7.2 开发者在实际工作中的选择与实践
在实际的工作中,开发者需要根据具体需求、项目背景、技术栈等因素综合考量,选择使用 doGet() 还是 doPost() 方法,或者考虑其他HTTP方法。
7.2.1 面对不同需求的综合考量
开发者在选择HTTP请求方法时,需要综合考虑如下因素: - 安全性 : 对于包含敏感信息的请求,开发者需考虑使用更为安全的方法,比如HTTPS。 - 幂等性 : 当需要多次执行相同操作而不会引起状态改变时,应该使用幂等的方法。 - 缓存 : doGet() 方法更适合于那些可以被缓存且不改变服务器状态的请求。 - 数据大小 : 超出URL长度限制的数据应该通过POST方法提交。 - 标准与兼容性 : 考虑到不同浏览器和服务器对HTTP方法的实现可能存在的差异,开发者需确保所选方法在目标环境中的兼容性。
7.2.2 案例分享:如何在实际工作中做出选择
为了更具体地说明如何在实际工作中做出选择,可以举出一个具体案例。假设我们需要构建一个在线购物网站,其中用户可以浏览商品列表和查看商品详情(GET请求),以及提交订单(POST请求)。
在实现浏览和查看商品的请求时,由于它们是纯粹的数据获取操作,不会引起服务器状态的改变,并且数据量通常不大,使用GET请求是非常合适的。对于提交订单操作,因为涉及到大量的数据传输(如购物车内容、用户信息等),并且需要在服务器端创建新的订单记录,所以使用POST请求更为合适。
在实际开发中,开发者需具体分析每个需求,并根据上述提到的考量因素做出合理的选择。在有些场景下,可能还需要考虑如何与其他技术相结合,例如在实现高交互性的Web应用时可能需要使用WebSockets,或是在使用API网关时需要考虑符合RESTful原则的HTTP方法。
通过这些实际的案例分享,开发者能够更清晰地理解在面对不同应用场景时如何做出决策,并且可以将这些决策应用到自己的工作中。
本文还有配套的精品资源,点击获取
简介:在Java Web开发中,了解Servlet的doGet()和doPost()方法的区别至关重要。doGet()方法用于获取资源,请求参数可见,长度有限,适合小数据量的获取请求,且可被缓存,但不宜用于敏感数据。doPost()方法用于提交数据到服务器,请求参数隐藏,数据量无限制,且不被缓存,适合敏感信息的传输。选择合适的方法取决于具体需求,还应注意编程实践中的幂等性、安全性和重定向等问题。
本文还有配套的精品资源,点击获取