Go实现http同步文件操作 - 增删改查
http同步文件操作 - 增删改查
- http同步文件操作 - 增删改查
- 1. 前置要求
- 1.1. 构建结构体 文件名 + 文件内容
- 1.1.1. 页面结构体
- 1.1.2. 为`Page`结构体绑定方法:`Save`
- 1.1.3. 对`Page`结构体支持页面内容查看方法,同时提供页面文件是否存在的方法
- 1.2. 简单验证上面的结构体是否可以直接改变值
- 使用HTTP来完成对页面的增删改查
- 1. 增加对页面文件保存的操作
- 增加mian方法,进行测试
- 2. 增加对页面文件进行查看内容的操作
- 增加mian方法,进行测试
- 3. 增加对页面文件删除操作
- 增加mian方法,进行测试
- 4. 增加对页面文件内容更新的操作
- 增加mian方法,进行测试
- 增删改查的操作保存在一个文件里,内容如下
- other
http同步文件操作 - 增删改查
1. 前置要求
1.1. 构建结构体 文件名 + 文件内容
1.1.1. 页面结构体
type Page struct {Title string `json:"title"`Body string `json:"body"`
}
// ``符号为结构体的标准,可忽略
1.1.2. 为Page
结构体绑定方法:Save
// SavePage 页面文件保存方法,保存到数据库,这里保存到页面文件中
// 使用bufio操作文件
// notice: 延时关闭 flush
// 绑定Page指针变量
func (page *Page) SavePage(title string, body string) (err error) {title += ".txt"// todo 1. 拿到页面文件句柄file, err := os.OpenFile(title, os.O_CREATE|os.O_RDWR, fs.ModePerm)// todo final stepdefer func() { _ = file.Close() }()if err != nil {fmt.Println("无法获取到页面文件,error:", err)return err}// todo 2. 打开页面文件writer := bufio.NewWriter(file)// todo 3. 往页面文件写入内容nByteNum, err := writer.Write([]byte(body))if err != nil {fmt.Println("页面文件保存失败,error:", err)return err}// todo 4. 刷写数据出去_ = writer.Flush()fmt.Println("写入的内容大小为:", nByteNum, "字节")return nil
}
-
内容添加文件后缀,固定为
.txt
-
使用bufio操作文件,可以更换其它方式
-
*指针变量
-
注意事项:
-
延时关闭
defer func() { _ = file.Close() }()
-
数据刷写
_ = writer.Flush()
-
1.1.3. 对Page
结构体支持页面内容查看方法,同时提供页面文件是否存在的方法
// 判断是否存在该页面文件名,不需要加后缀!已经手动添加;也可以优化,多加入参数表示后缀即可
func ExistFile(fileName string) bool {if _, err := os.Stat(fileName + ".txt"); err != nil {if os.IsNotExist(err) {return false}}return true
}// 查看指定的页面文件内容
// 使用os操作文件
func ViewPage(title string) (page *Page) {page = &Page{}page.Title = titleif ExistFile(title) {fileContent, err := os.ReadFile(title + ".txt")if err != nil {fmt.Println("读取页面文件出错, error is :", err)} else {page.Body = string(fileContent)}}return page
}
- 上面内容为一个文件,该
entity/entity.go
文件的全部代码为:
package entityimport ("bufio""fmt""io/fs""os"
)type Page struct {Title string `json:"title"`Body string `json:"body"`
}// SavePage 页面文件保存方法,保存到数据库,这里保存到页面文件中
// 使用bufio操作文件
// notice: 延时关闭 flush
func (page *Page) SavePage(title string, body string) (err error) {title += ".txt"// todo 1. 拿到页面文件句柄file, err := os.OpenFile(title, os.O_CREATE|os.O_RDWR, fs.ModePerm)// todo final stepdefer func() { _ = file.Close() }()if err != nil {fmt.Println("无法获取到页面文件,error:", err)return err}// todo 2. 打开页面文件writer := bufio.NewWriter(file)// todo 3. 往页面文件写入内容nByteNum, err := writer.Write([]byte(body))if err != nil {fmt.Println("页面文件保存失败,error:", err)return err}// todo 4. 刷写数据出去_ = writer.Flush()fmt.Println("写入的内容大小为:", nByteNum, "字节")return nil
}// 判断是否存在该页面文件名,不需要加后缀!
func ExistFile(fileName string) bool {if _, err := os.Stat(fileName + ".txt"); err != nil {if os.IsNotExist(err) {return false}}return true
}// 查看指定的页面文件内容
// 使用os操作文件
func ViewPage(title string) (page *Page) {page = &Page{}page.Title = titleif ExistFile(title) {fileContent, err := os.ReadFile(title + ".txt")if err != nil {fmt.Println("读取页面文件出错, error is :", err)} else {page.Body = string(fileContent)}}return page
}
1.2. 简单验证上面的结构体是否可以直接改变值
func main() {// 验证你的结构体是否发生改变 - 指针变量page := entity.ViewPage("TestPage")fmt.Println(page)page.Body = "change"fmt.Println(page)
}
使用HTTP来完成对页面的增删改查
1. 增加对页面文件保存的操作
// http方式保存page
// 默认从请求体中获取内容作为body传入文件
// 直接调用/savePage的body默认值为:this is a default value
// 更新操作也会调用该方法,body值为 上传的请求体内容
func saveHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/savePage/"):]// todo 2. 填充 body内容page := &entity.Page{}// important 本人在后面update会遇到坑,默认从请求体中获取body// todo 2.1. 优先考虑从请求体获取bodybody := request.FormValue("body")// todo 2.2. 请求体为空串,则赋值默认值if body == "" {body = "this is a default value"}// todo 3. 将body值写入到页面文件err := page.SavePage(title, body)// todo 3.1. 展示写入结果if err != nil {_, _ = responseWriter.Write([]byte("页面文件保存不成功"))fmt.Println("页面文件保存不成功,error is :", err)return} else {message := "页面文件保存成功"_, _ = responseWriter.Write([]byte(message))fmt.Println(message)}
}
增加mian方法,进行测试
func main() {// url路径要对应,当然你也可以更改;// 若修改viewPage,上面的viewHandler方法也要对应改变http.HandleFunc("/savePage/", saveHandler)err := http.ListenAndServe(":8989", nil)if err != nil {fmt.Println("端口异常")}
}
-
运行程序,然后到浏览器访问:localhost:8989/viewPage/myFirst2
-
结果会展示
-
-
页面也会展示成功的提示
-
-
项目目录下会生成
myFirst2.txt
文件,内容为:this is a default value
-
2. 增加对页面文件进行查看内容的操作
// http 方式查看特定的page内容
// 使用硬编码页面
func viewHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/viewPage/"):]// todo 2. 判断是否已经存在该页面文件if !entity.ExistFile(title) {// todo 2.1. 不存在该页面文件就结束fmt.Printf("不存在%v页面文件", title)_, _ = responseWriter.Write([]byte("不存在该页面文件"))return}// todo 2.2. 获取该页面的内容page := entity.ViewPage(title)body := []byte(page.Body)// todo 3. 页面展示内容_, _ = responseWriter.Write(body)
}
增加mian方法,进行测试
func main() {// url路径要对应,当然你也可以更改;// 若修改viewPage,上面的viewHandler方法也要对应改变http.HandleFunc("/savePage/", saveHandler)http.HandleFunc("/viewPage/", viewHandler)err := http.ListenAndServe(":8989", nil)if err != nil {fmt.Println("端口异常")}
}
-
运行程序,到浏览器输入 localhost:8989/viewPage/myFirst2
-
会查看当前项目下是否存在
myFirst2.txt
文件,如果存在,则会返回页面文件内容。反之会展示不存在该页面文件
内容-
我们前面生成了
myFirst2.txt
文件,效果为:-
-
页面展示的内容为文件的内容
-
-
我们访问没有对应的文件url:localhost:8989/viewPage/myFirst3
-
效果为:
-
即页面展示不存在该文件。你也可以再利用savePage来生成一个
myFirst3.txt
文件
-
-
-
3. 增加对页面文件删除操作
// http方式删除页面文件
func deleteHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/deletePage/"):]// todo 2. 判断是否存在该页面文件if !entity.ExistFile(title) {_, _ = responseWriter.Write([]byte("页面文件不存在,无法删除"))return} else {// todo 3. 移除页面文件err := os.Remove(title + ".txt")if err != nil {_, _ = responseWriter.Write([]byte("页面文件删除异常,请检查你的权限,或者联系管理员"))return}_, _ = responseWriter.Write([]byte("页面文件删除成功"))}
}
增加mian方法,进行测试
func main() {// url路径要对应,当然你也可以更改;// 若修改viewPage,上面的viewHandler方法也要对应改变http.HandleFunc("/savePage/", saveHandler)http.HandleFunc("/viewPage/", viewHandler)http.HandleFunc("/deletePage/", deleteHandler)err := http.ListenAndServe(":8989", nil)if err != nil {fmt.Println("端口异常")}
}
-
运行程序,到浏览器输入 localhost:8989/deletePage/myFirst2
-
程序会删除
myFirst2
文件,效果为:-
项目下的文件已经被删除,若此时再次执行则会提示无法删除
-
-
4. 增加对页面文件内容更新的操作
// http方式更新页面文件
func updateHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/updatePage/"):]// todo 2. 查看该页面文件page := entity.ViewPage(title)// todo 3. 填充html页面,硬编码fmt.Fprintf(responseWriter, "<h1>Editing %s</h1>"+// 直接调用了 saveHandler// todo 4. 再次保存该页面文件"<form action=\"/savePage/%s\" method=\"POST\">"+"<textarea name=\"body\">%s</textarea><br>"+"<input type=\"submit\" value=\"Save\">"+"</form>",page.Title, page.Title, page.Body)
}
- notice :todo的第四步,保存后会出发
/savePage
的访问动作
增加mian方法,进行测试
func main() {// url路径要对应,当然你也可以更改;// 若修改viewPage,上面的viewHandler方法也要对应改变http.HandleFunc("/savePage/", saveHandler)http.HandleFunc("/viewPage/", viewHandler)http.HandleFunc("/deletePage/", deleteHandler)http.HandleFunc("/updatePage/", updateHandler)err := http.ListenAndServe(":8989", nil)if err != nil {fmt.Println("端口异常")}
}
-
运行程序,到浏览器输入 localhost:8989/updatePage/myFirst2
-
页面会返回一个html页面,效果为:
-
尝试在文本框输入内容,这里输入
hello,java页 可乐 唱跳rap
,然后点击Save进行保存。-
然后页面会进行跳转到保存页面,效果图为:
-
-
-
增删改查的操作保存在一个文件里,内容如下
package mainimport ("fmt""goland-setting/test/webapp/entity""net/http""os"
)// http 方式查看特定的page
// 使用硬编码页面
func viewHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/viewPage/"):]// todo 2. 判断是否已经存在该页面文件if !entity.ExistFile(title) {// todo 2.1. 不存在该页面文件就结束fmt.Printf("不存在%v页面文件", title)_, _ = responseWriter.Write([]byte("不存在该页面文件"))return}// todo 2.2. 获取该页面的内容page := entity.ViewPage(title)body := []byte(page.Body)// todo 3. 页面展示内容_, _ = responseWriter.Write(body)
}// http方式保存page
// 默认从请求体中获取body
// 直接调用/savePage的body默认值为:this is a default value
// 更新操作也会调用该方法,body值为 上传的请求体内容
func saveHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/savePage/"):]// todo 2. 填充 body内容page := &entity.Page{}// important 本人在后面update会遇到坑,默认从请求体中获取body// todo 2.1. 优先考虑从请求体获取bodybody := request.FormValue("body")// todo 2.2. 请求体为空串,则赋值默认值if body == "" {body = "this is a default value"}// todo 3. 将body值写入到页面文件err := page.SavePage(title, body)// todo 3.1. 展示写入结果if err != nil {_, _ = responseWriter.Write([]byte("页面文件保存不成功"))fmt.Println("页面文件保存不成功,error is :", err)return} else {message := "页面文件保存成功"_, _ = responseWriter.Write([]byte(message))fmt.Println(message)}
}// http方式删除页面文件
func deleteHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/deletePage/"):]// todo 2. 判断是否存在该页面文件if !entity.ExistFile(title) {_, _ = responseWriter.Write([]byte("页面文件不存在,无法删除"))return} else {// todo 3. 移除页面文件err := os.Remove(title + ".txt")if err != nil {_, _ = responseWriter.Write([]byte("页面文件删除异常,请检查你的权限,或者联系管理员"))return}_, _ = responseWriter.Write([]byte("页面文件删除成功"))}
}// http方式更新页面文件
func updateHandler(responseWriter http.ResponseWriter, request *http.Request) {// todo 1. 在url中获取 page的title值title := request.URL.Path[len("/updatePage/"):]// todo 2. 查看该页面文件page := entity.ViewPage(title)// todo 3. 填充html页面fmt.Fprintf(responseWriter, "<h1>Editing %s</h1>"+// 直接调用了 saveHandler// todo 4. 再次保存该页面文件"<form action=\"/savePage/%s\" method=\"POST\">"+"<textarea name=\"body\">%s</textarea><br>"+"<input type=\"submit\" value=\"Save\">"+"</form>",page.Title, page.Title, page.Body)
}func main() {http.HandleFunc("/savePage/", saveHandler)http.HandleFunc("/deletePage/", deleteHandler)http.HandleFunc("/updatePage/", updateHandler)http.HandleFunc("/viewPage/", viewHandler)err := http.ListenAndServe(":8989", nil)if err != nil {fmt.Println("端口异常")}
}
一个简单的webApp就完成了。
other
- 对于更新操作,服务器硬编码html太难了,可以使用
html/template
来渲染,这样会好很多。
相关文章:

Go实现http同步文件操作 - 增删改查
http同步文件操作 - 增删改查 http同步文件操作 - 增删改查1. 前置要求1.1. 构建结构体 文件名 文件内容1.1.1. 页面结构体1.1.2. 为Page结构体绑定方法:Save1.1.3. 对Page结构体支持页面内容查看方法,同时提供页面文件是否存在的方法 1.2. 简单验证上面…...

Spring Boot整合 Spring Security
Spring Boot整合 1、RBAC 权限模型 RBAC模型(Role-Based Access Control:基于角色的访问控制) 在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限,它们之间的关系如下图所示 SELECT…...
浅谈低代码
低代码开发是近年来迅速崛起的软件开发方法,让编写应用程序变得更快、更简单。有人说它是美味的膳食,让开发过程高效而满足,但也有人质疑它是垃圾食品,缺乏定制性与深度。你认为低代码到底是美以下方向仅供参考。味的膳食还是垃圾…...

Innodb-ruby深入探索Innodb存储结构
达在之前已经分享过Innodb数据存储结构知识,但是都是基于理论原理知识理解,今天利用Innodb文件解析工具ruby进行探索Innodb真实的存储结构。 索引原理过程:【Mysql】 InnoDB引擎深入 - 数据页 | 聚集索引_innodb的聚集索引的数据插入_Surviv…...

Echarts的使用 笔记
1.数据可视化前言 1.1.什么是数据可视化 数据可视化: 就是把数据以更加直观的方式进行呈现. 1.2.数据可视化的好处 清晰有效地传达与沟通信息更容易洞察隐藏在数据中的信息 2.ECharts的基本使用 2.1.ECharts官网 ECharts是百度公司开源的一个使用 JavaScript 实…...
信息系统工程的基本概念
系统是由相互作用和相互依赖的若干部分,按一定规律结合成的、具有特定功能的有机整体。系统有下述特性: (1)集合性。系统是由许多元素有机地组成的整体。每个元素服从整体,追求全局最优。 (2)相…...
SAP UI5 walkthrough step10 Descriptor for Applications
在这一步,我们将会把所有的应用相关的描述性的文件独立放到manifest.json 新建一个manifest.json文件 webapp/manifest.json (New) {"_version": "1.58.0","sap.app": {"id": "ui5.walkthrough","i18n&q…...

打造专属小程序,乔拓云模板平台助力商家抢占先机
打造专属小程序,乔拓云模板平台助力商家抢占先机!该平台涵盖全行业小程序模板,一键复制即可上线。 想要快速创建高效实用的小程序,乔拓云小程序模板开发平台为您提供了解决方案!我们为您提供一系列精心设计的小程序模板…...
Vue2学习(组件的使用)
Vue中使用组件的三个步骤: 一、定义组件(或者叫创建组件) 使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别; 区别如下: 1.el不要写,为什么&#x…...
基于Spring、SpringMVC、MyBatis开发的游乐场管理系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Spring、SpringMVC、MyBatis开发的游…...

数据清洗、特征工程和数据可视化、数据挖掘与建模的应用场景
1.5 数据清洗、特征工程和数据可视化、挖掘建模的应用场景 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.5节内容。本书已正式出版上市,当当、京东、淘宝等平台热销中,搜索书名即可。内容涵盖数据科学应…...

Qt简介、工程文件分离、创建Qt工程、Qt的帮助文档
QT 简介 core:核心模块,非图形的接口类,为其它模块提供支持 gui:图形用户接口,qt5之前 widgets:图形界面相关的类模块 qt5之后的 database:数据库模块 network:网络模块 QT 特性 开…...

机器学习与低代码开发:创新驱动的双剑合璧
引言 随着科技的日新月异,机器学习和低代码开发已经成为引领技术行业变革的两大重要趋势。机器学习通过模拟人类的学习方式,让计算机具备了自我学习和预测的能力,打破了传统计算机程序的局限性。而低代码开发则以简化软件开发过程为目标&…...

企业博客SEO:优化SOP,助您提升搜索引擎可见性
企业博客是互联网时代企业与用户沟通的重要渠道之一,引流成本也比较低。然而,依然有企业会处在3种状态: 1. 有博客,但内容更新不积极或搁置 2. 有博客,但内容散乱 3. 根本就没有博客 如果是这几种状态,…...

[HITCON 2017]SSRFme perl语言的 GET open file 造成rce
这里记录学习一下 perl的open缺陷 这里首先本地测试一下 发现这里使用open打开 的时候 如果通过管道符 就会实现命令执行 然后这里注意的是 perl 中的get 调用了 open的参数 所以其实我们可以通过管道符实现命令执行 然后这里如果file可控那么就继续可以实现命令执行 这里就…...

华为配置Smart Link主备备份示例
定义 Smart Link,又叫做备份链路。一个Smart Link由两个接口组成,其中一个接口作为另一个的备份。Smart Link常用于双上行组网,提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案,它通过监控设备的上行接口…...
harmonyOS开发技巧(一)——封装hilog日志
1. 创建sharedLibrary共享hsp包commonLib:功能工具类。 import hilog from ohos.hilog;class Logger {private DOMAIN: number 0x0000;private APP_NAME: string Myapplication;public info(tag: string, ...args: string[]) {hilog.info(this.DOMAIN, [${this.A…...
npm、yarn常用命令
1、设置npm路径 #全局安装路径 npm config set prefix "D:\Program Files\nodejs\node_global" #缓存路径 npm config set cache "D:\Program Files\nodejs\node_cache"2、设置镜像 #1,淘宝镜像源 npm config set registry https://registry.npmmirror.…...

编译和使用WPS-ghrsst-to-intermediate生成SST
一、下载 V1.0 https://github.com/bbrashers/WPS-ghrsst-to-intermediate/tree/masterV1.5(使用过程报错,原因不详,能正常使用的麻烦告知一下方法) https://github.com/dmitryale/WPS-ghrsst-to-intermediate二、修改makefile…...

通过静态HTTP实现负载均衡
在当今的互联网环境中,随着用户数量的不断增加和业务需求的不断扩大,单台服务器往往无法承受所有的访问压力。为了确保网站的可用性和性能,负载均衡成为了一种常见的解决方案。本文将探讨如何通过静态HTTP实现负载均衡,以提升网站…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...

门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
k8s从入门到放弃之Pod的容器探针检测
k8s从入门到放弃之Pod的容器探针检测 在Kubernetes(简称K8s)中,容器探测是指kubelet对容器执行定期诊断的过程,以确保容器中的应用程序处于预期的状态。这些探测是保障应用健康和高可用性的重要机制。Kubernetes提供了两种种类型…...